Objective-C是一门动态的语言
动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如众所周知的ECMAScript(JavaScript)便是一个动态语言。除此之外如Ruby、Python等也都属于动态语言,而C、C++等语言则不属于动态语言。
有三个名词容易混淆:DynamicProgrammingLanguage(动态语言或动态编程语言) Dynamically TypedLanguage(动态类型语言) Statically TypedLanguage(静态类型语言)
所谓的动态类型语言,意思就是类型的检查是在运行时做的。
而静态类型语言的类型判断是在运行前判断(如编译阶段),比如C#、Java就是静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继承、接口,而动态类型语言却不需要,所以一般动态语言都会采用dynamic typing,常出现于脚本语言中.需要明确说明一点,那就是,是不是动态类型语言与这门语言是不是类型安全的完全不相干的,不要将它们联系在一起!
优缺点:
静态类型语言的主要优点在于其结构非常规范,便于调试,方便类型安全;缺点是为此需要写更多的类型相关代码,导致不便于阅读、不清晰明了。动态类型语言的优点在于方便阅读,不需要写非常多的类型相关的代码;缺点自然就是不方便调试,命名不规范时会造成读不懂,不利于理解等。顺便说一下,现在有这样一种趋势,那就是合并动态类型与静态类型在一种语言中,这样可以在必要的时候取长补短,Boo就是一个很好的试验性例子。
Objective-C语言是C语言的一个子类,所以objective-c是一个静态语言,但是Objective-C的三大特性之一的多态性让其拥有了动态性。
oc的动态性让程序可以在运行时判断其该有的行为,而不是像c等静态语言一样在编译构建时就确定下来。它的动态性主要体现在一下三个方面:
如id类型。实际上静态类型因为其固定性和可预知性而使用得更加广泛。静态类型是强类型,而动态类型属于弱类型。运行时决定接收者。
这里补充讲一下强、弱类型:语言有无类型、强类型和弱类型三种。无类型的不做任何检查,甚至不区分指令和数据;弱类型的检查很弱,仅能区分指令和数据;强类型的严格在编译期进行检查。强类型语言在没有强制类型转化前,不允许两种不同类型的变量相互操作
让代码在运行时判断需要调用什么方法,而不是在编译时。与其他面向对象语言一样,方法调用和代码并没有在编译时连接在一起,而是在消息发送时才进行连接。运行时决定调用哪个方法。
让程序在运行时添加代码模块以及其他资源。用户可以根据需要加载一些可执行代码和资源,而不是在启动时就加载所有组件。可执行代码中可以含有和程序运行时整合的新类。
对象是运行时类的一个实例。在类里声明了的实例变量和方法,它的每个实例都在内存中拥有同样的实例变量,以及指向那些方法的指针。在oc中对象永远是通过指针来引用的。
MVC,MVP 和 MVVM 的图示
3.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
代理属性都用weak或是assign修饰, 现整理下weak(assign)与strong(retain)修饰区别:
@property (nonatomic, weak) iddelegate;
weak:指明该对象并不负责保持delegate这个对象,delegate这个对象的销毁由外部控制
@property (nonatomic, strong) iddelegate;
strong:该对象强引用delegate,外界不能销毁delegate对象,会导致循环引用(Retain MainVC)
Datasource 是在告诉使用者之前的view中都有什么东西,有什么属性啊,属性的值都是多少,是只关于数据的东西。
Delegate 是在告诉使用者之前的view有什么方法可以供我调用。
一个是数据,一个是操作.
首先两者作用是一样的,都是进行单一回调。不同的是,delegate是个对象,然后用过一个对象自己调用代理协议函数来完成整个流程。block是传递一个函数指针,利用函数指针执行来进行回调。还有在内存管理上需要注意,delegate不需要保存引用。block对引用数据有copy的处理。
4. 属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?
属性的组成: @property = ivar + getter + setter;
实例变量+get方法+set方法,也就是说使用@property 系统会自动生成setter和getter方法;
我们经常使用assign,weak,strong,copy,nonatomic,atomic,readonly等关键字,下面我们列个表格去归纳一下属性关键字具体作用:
@synthesize和@dynamic区别, 在声明property属性后,有2种实现选择:@synthesize编译器期间,让编译器自动生成getter/setter方法。当有自定义的存或取方法时,自定义会屏蔽自动生成该方法@dynamic告诉编译器,不自动生成getter/setter方法,避免编译期间产生警告,然后由自己实现存取方法。或存取方法在运行时动态创建绑定:主要使用在CoreData的实现NSManagedObject子类时使用,由CoreData框架在程序运行的时动态生成子类属性
对于基本数据类型默认关键字是atomic,readwrite,assign
对于普通的OC对象atomic,readwrite,strong
6.NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)
怎么用 copy 关键字?
如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
8.可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?
首先我们要先明白一个概念,什么是浅复制,单层深复制,完全复制(每一层都深复制)
浅复制也就是所说的指针复制,并没有进行对象复制;
单层深复制,也就是我们经常说的深复制,我这里说的单层深复制是对于集合类所说的(即NSArray,NSDictionary,NSSet),单层深复制指的是只复制了该集合类的最外层,里边的元素没有复制,(即这两个集合类的地址不一样,但是两个集合里所存储的元素的地址是一样的);
完全复制,指的是完全复制整个集合类,也就是说两个集合地址不一样,里边所存储的元素地址也不一样;
明白了这三个概念之后,我们就来说一下他们的区别所在:
[immutableObject copy]//浅复制[immutableObject mutableCopy]//深复制[mutableObject copy]//深复制[mutableObject mutableCopy]//深复制
结论:不可变进行copy是浅复制,mutableCopy是深复制,可变的copy,mutableCopy都是深复制
集合类(NSArray,NSDictionary, NSSet):
[immutableObject copy]//浅复制[immutableObject mutableCopy]//单层深复制[mutableObject copy]//单层深复制[mutableObject mutableCopy]//单层深复制
结论:不可变进行copy是浅复制,mutableCopy是单层深复制,可变的copy,mutableCopy都是单层深复制
那么如何实现多层复制呢?
以NSArray举例说明
NSArray*copyArray = [[NSArrayalloc] initWithArray:array copyItems:YES];// 完全复制
以上我们所说的两种情况默认都实现了NSCopying和NSMutableCopying协议
对于自定义继承自NSObject的类
copy需要实现NSCopying协议,然后实现以下方法,否则copy会crash
-(id)copyWithZone:(NSZone *)zone { CopyObject *copy= [[[selfclass] alloc] init];copy.name =self.name;copy.mobile =self.mobile;copy.company =self.company;copy.descInfo =self.descInfo;returncopy;}mutableCopy时,需要实现NSMutableCopying协议,否则mutableCopy会crash-(id)mutableCopyWithZone:(NSZone *)zone { MutableCopyObject *mutableCopy = [[[selfclass] alloc] init]; mutableCopy.name =self.name; mutableCopy.mobile =self.mobile; mutableCopy.company =self.company; mutableCopy.descInfo =self.descInfo;returnmutableCopy;}
9.为什么IBOutlet修饰的UIView也适用weak关键字?
因为当我们将控件拖到Storyboard上,相当于新创建了一个对象,而这个对象是加到视图控制器的view上,view有一个subViews属性,这个属性是一个数组,里面是这个view的所有子view,而我们加的控件就位于这个数组中,那么说明,实际上我们的控件对象是属于view的,也就是说view对加到它上面的控件是强引用。当我们使用Outlet属性的时候,我们是在viewController里面使用,而这个Outlet属性是有view来进行强引用的,我们在viewController里面仅仅是对其使用,并没有必要拥有它,所以是weak的。
如果将weak改为strong,也是没有问题的,并不会造成强引用循环。当viewController的指针指向其他对象或者为nil,这个viewController销毁,那么对控件就少了一个强引用指针。然后它的view也随之销毁,那么subViews也不存在了,那么控件就又少了一个强引用指针,如果没有其他强引用,那么这个控件也会随之销毁。
不过,既然没有必将Outlet属性设置为strong,那么用weak就好了;一个控件可以在viewController里面有多个Outlet属性,就相当于一个对象,可以有多个指针指向它(多个引用)。但是一个Outlet属性只能对应一个控件,也就是说,如果有button1和button2,button1在viewController里面有一个名为button的Outlet属性,此时button指向button1,但是如果用button2给button重新赋值,那么此时button指向button2。也就是说,后来的覆盖原来的。
一个控件可以在viewController里面触发多个IBAction。比如有一个button控件,在viewController里面有几个方法,那么点击button,会触发所有的这些方法。如果我有多个控件,比如button1,button2,button3,它们也可以同时绑定一个buttonClick方法,无论点击button1,button2还是button3,都会触发这个buttonClick方法。
上面说了,button1,button2,button3有可能都触发buttonClick方法,如果想在buttonClick方法里面区分到底是哪个button触发的可能有好几种做法。
可以给这三个button各设置一个Outlet属性,然后在buttonClick里面判断sender和哪个Outlet属性是同一对象,这样就可以区分了。但是很明显,这样并不合理,因为创建的三个属性有些浪费。
我们可以给三个button各加一个tag,在buttonClick里面通过switch(或者if…)判断,sender的tag和给各个button加上的tag是否一致,如果一致则为同一对象。要慎用tag。因为view有一个viewWithTag:方法,可以在view的子view里面找到和我们传入的tag相同的view,这样哪怕不给这个控件创建Outlet属性,也可以通过tag找到这个对象。但是很明显,这个方法要遍历子view,比较每个子view的tag,这样效率并不高,所以尽量要避免这种情况。
10.nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
在默认情况下,由编译器所合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备nonatomic特质,则不需要同步锁。
具备atomic特质的获取方法会通过锁定机制来确保其操作的原子性。也就是说,如果两个线程同时读取一个属性,那么不论何时,总能看到有效的属性值。
如果不加锁的话(或者说使用nonatomic语义),那么当其中一个线程正在改写某属性值的时候,另外一个线程也许会突然闯入,把尚未修改好的属性值读取出来。发证这种情况时,线程读取道德属性值肯能不对。
一般iOS程序中,所有属性都声明为nonatomic。这样做的原因是:
在iOS中使用同步锁的开销比较大, 这会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”(thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。
例如:一个线程在连续多次读取某个属性值的过程中有别的线程在同时改写该值,那么即便将属性声明为atomic,也还是会读取到不同的属性值。因此,ios程序一般都会使用nonatomic属性。但是在Mac OS X程序时, 使用atomic属性通常都不会有性能瓶颈;
nonatomic的内存管理语义是非原子性的,非原子性的操作本来就是线程不安全,而atomic的操作是原子性的,但并不意味着他就是线程安全的,它会增加正确的几率,能够更好的避免线程错误,但仍旧是不安全的。
为了说atomic与nonatomic的本质区别其实也就是在setter方法上的操作不。nonatomic的实现:
- (void)setCurrentImage:(UIImage *)currentImage{ if (_currentImage != currentImage) { [_currentImage release]; _currentImage = [currentImage retain]; //dosomething }}- (UIImage *)currentImage{ return _currentImage;} atomic的实现:- (void)setCurrentImage:(UIImage *)currentImage{ @synchronized(self) { if (_currentImage != currentImage) { [_currentImage release]; _currentImage = [currentImage retain]; //dosomething } }}- (UIImage *)currentImage{ @synchronized(self) { return _currentImage;}}Using the @synchronized DirectiveThe @synchronized directive is a convenient way tocreatemutex locksonthe flyinObjective-C code. The @synchronized directive does whatanyother mutexlockwoulddo—it prevents different threadsfromacquiring the samelockatthe sametime.Inthiscase, however, youdonothavetocreatethe mutexorlockobject directly. Instead, you simply useanyObjective-C objectasalocktoken,asshowninthe following example:
- (void)myMethod:(id)anObj{@synchronized(anObj) {//Everything between the bracesisprotectedbythe@synchronizeddirective. }}
The object passed to the @synchronized directive is a unique identifier used to distinguish the protected block. If you execute the preceding method in two different threads, passing a different object for the anObj parameter on each thread, each would take its lock and continue processing without being blocked by the other. If you pass the same object in both cases, however, one of the threads would acquire the lock first and the other would block until the first thread completed the critical section.
As a precautionary measure, the @synchronized block implicitly adds an exception handler to the protected code. This handler automatically releases the mutex in the event that an exception is thrown. This means that in order to use the @synchronized directive, you must also enable Objective-C exception handling in your code. If you do not want the additional overhead caused by the implicit exception handler, you should consider using the lock classes.
For more information about the @synchronized directive, see The Objective-C Programming Language.
当使用atomic时,虽然对属性的读和写是原子性的,但是仍然可能出现线程错误:当线程A进行写操作,这时其他线程的读或者写操作会因为等该操作而等待。当A线程的写操作结束后,B线程进行写操作,所有这些不同线程上的操作都将依次顺序执行——也就是说,如果一个线程正在执行 getter/setter,其他线程就得等待。如果有线程C在A线程读操作之前release了该属性,那么还会导致程序崩溃。所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全。
更准确的说应该是读写安全,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。
其实无论是否是原子性的只是针对于getter和setter而言,比如用atomic去操作一个NSMutableArray ,如果一个线程循环读数据,一个线程循环写数据,肯定会产生内存问题,这个就跟getter和setter就木有关系了。
11.UICollectionView自定义layout如何实现?
关于自定义UICollectionViewLayout的一点个人理解
Storyboard在某些角度上,是难以维护的。我所遇到过的实际情况是,公司一个项目的2.0版本,设计师希望替换原有字体。然而原来项目的每一个Label都是采用Storyboard来定义字体的,因此替换新字体需要在Storyboard中更改每一个Label。
幸亏我们知道Storyboard的源文件是XML,最终写了一个读取-解析-替换脚本来搞定这件事。
当项目达到一定的规模,即使是高性能的MacBook Pro,在打开Storyboard是也会有3-5秒的读取时间。无论是只有几个Scene的小东西,还是几十个Scene的庞然大物,都无法避免。Scene越多的文件,打开速度越慢(从另一个方面说明了分割大故事板的重要性)。
让人沮丧的是,这个造成卡顿的项目规模并不是太难达到。
我猜想是由于每一次打开都需要进行I/O操作造成的,Apple对这一块的缓存优化没有做到位。可能是由于Storyboard占用了太多内存,难以在内存中进行缓存。Whatever,这个问题总是让人困扰的。
然而需要指出的是,采用Storyboard开发或采用纯代码开发的App,在真机的运行效率上,并没有太大的区别。
Storyboard的初学者应该对此深有体会。排除BAD_EXCUSE错误不说,单单是有提示的错误,就足以让人在代码和Storyboard之间来回摸索,却无法找到解决方案。
一个典型的例子是,在代码中删除了IBOUTLET属性或者IBAction方法,但是却忘了在Storyboard中删除对应的连接,运行后crash。然而控制台只会输出一些模糊其词的错误描述。
*** Terminating app duetouncaught exception'NSUnknownKeyException',reason:'[ setValue:forUndefinedKey:]:thisclassisnotkeyvalue coding-compliantforthekeydrawButton.'
最后一方面是其提供的便利,另一方面是Apple对Storyboard的大力支持。这一点宏观上看,可以在以往对Storyboard的改进和增强上看出,微观上看,几乎所有iOS 8之后的simple code都或多或少采用了Storyboard作为界面开发工具;
那改如何避免这些弊端呢, 参考以下文章:
iOS项目开发实战——storyboard设置界面技巧与注意事项
进程和线程:
进程中所包含的一个或多个执行单元称为线程(thread)。比如一个应用程序就是一个进程, 而它又包含了多个线程;主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程;
在进行网络编程时,我们通常会看到同步、异步、阻塞、非阻塞四种调用方式以及他们的组合。
其中同步方式、异步方式主要是由客户端(client)控制的,具体如下:
所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。
根据这个定义,Java中所有方法都是同步调用,应为必须要等到结果后才会继续执行。我们在说同步、异步的时候,一般而言是特指那些需要其他端协作或者需要一定时间完成的任务。
简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。
例如:B/S模式中的表单提交,具体过程是:客户端提交请求->等待服务器处理->处理完毕返回,在这个过程中客户端浏览器不能做其他事。
异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。
总结来说,同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。
并行(parallellism)和并发(concurrency)的区别:
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
并发,是在同一个cpu上同时(不是真正的同时,而是看来是同时,因为cpu要在多个程序间切换)运行多个程序;
并发和并行的区别
使用全局变量主要由于多个线程可能更改全局变量,因此全局变量最好声明为violate
使用消息实现通信在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。
1)定义消息#defineWM_THREAD_SENDMSG=WM_USER+20;2)添加消息函数声明afx_msgintOnTSendmsg();3)添加消息映射ON_MESSAGE(WM_THREAD_SENDMSG,OnTSM)4)添加OnTSM()的实现函数;5)在线程函数中添加PostMessage消息Post函数
Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。
1)创建一个CEvent类的对象:CEvent threadStart;它默认处在未通信状态;2)threadStart.SetEvent();使其处于通信状态;3)调用WaitForSingleObject()来监视CEvent对象
线程间的通信、同步方式与进程间通信方式
15.GCD的一些常用的函数?(group,barrier,信号量,线程同步)
dispatch_groupdispatch_barrierdispatch_semaphore(信号量)附:iOSGCD线程同步方法
ios多线程——锁(解决多线程抢夺同一块资源的问题)
plist文件(属性列表)
preference(偏好设置)
NSKeyedArchiver(归档)
SQLite 3
CoreData
iOS中几种数据持久化方案
18.说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?
1. – (BOOL)application:(UIApplication)application didFinishLaunchingWithOptions:(NSDictionary)launchOptions NS_AVAILABLE_IOS(3_0);
当应用程序启动时(不包括已在后台的情况下转到前台),调用此回调。launchOptions是启动参数,假如用户通过点击push通知启动的应用,这个参数里会存储一些push通知的信息。
2. – (void)applicationDidBecomeActive:(UIApplication *)application;
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
当应用程序全新启动,或者在后台转到前台,完全激活时,都会调用这个方法。如果应用程序是以前运行在后台,这时可以选择刷新用户界面。
3. – (void)applicationDidEnterBackground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
当用户从前台状态转入后台时,调用此方法。使用此方法来释放资源共享,保存用户数据,无效计时器,并储存足够的应用程序状态信息的情况下被终止后,将应用 程序恢复到目前的状态。如果您的应用程序支持后台运行,这种方法被调用,否则调用applicationWillTerminate:用户退出。
4. – (void)applicationWillEnterForeground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
当应用在后台状态,将要进行动前台运行状态时,会调用此方法。
如果应用不在后台状态,而是直接启动,则不会回调此方法。
从后台到前台2, 4; 第一次启动1, 2; 从前台到后台3;
iOS APP启动时所有方法的调用顺序分析
NSCache胜过NSDictionary之处在于,当系统资源将要耗尽时,它可以自动删减缓存。如果采用普通的字典,那么就要自己编写挂钩,在系统发出“低内存”通知时手工删减缓存。
NSCache并不会“拷贝”键,而是会“保留”它。此行为用NSDictionary也可以实现,然而需要编写相当复杂的代码。NSCache对象不拷贝键的原因在于:很多时候,键都是不支持拷贝操作的对象来充当的。因此,NSCache不会自动拷贝键,所以说,在键不支持拷贝操作的情况下,该类用起来比字典更方便。另外,NSCache是线程安全的,而NSDictionary则绝对不具备此优势。
20.知不知道Designated Initializer?使用它的时候有什么需要注意的问题?
iOS: 聊聊 Designated Initializer(指定初始化函数)
正确编写Designated Initializer的几个原则
一般情况下,我们在使用NSLog 和 %@ 输出某个对象时,就会调用这个对象的 description 方法,它的返回值就是 NSString 字符串类型,所以 description 默认实现返回的格式是 <类名: 对象的内存地址>
如图:
以上输出实现的具体步骤为:
①调用对象p的-description方法②拿到-description方法的返回值(NSString*)显示到屏幕上③-description方法默认返回的是“类名+内存地址”
那么,既然description方法的默认实现是返回类名和对象的内存地址,所以在必要情况下,我们需要重写description方法以达到改变输出结果目的,覆盖description方法的默认实现,比如重写上述代码 Person 类的 description方法,返回_age和_name成员变量的值:
重写完description方法后,再调用NSLog(@”%@”,p)时输出结果不再是<类名: 内存地址>,而是返回的字符串:
MRC(manual retain-release)手动内存管理
ARC(automatic reference counting)自动引用计数
Garbage collection (垃圾回收)。但是iOS不支持垃圾回收, ARC作为LLVM3.0编译器的一项特性, 在iOS5.0 (Xcode4) 版本后推出的。
ARC的判断准则, 只要没有强指针指向对象, 对象就会被释放.
iOS开发系列—Objective-C之内存管理