关于OC中的关键字,是平时使用离不开的,也是面试官最喜欢问的,不管是出于那种考虑吧,作为一个ios开发的程序员,都必须完全理解透彻。本文旨在罗列分析和对比这些关键字。copy,assign,strong,retain,weak,readonly,nonatomic
首先明确一些概念:
野指针:野指针就是指向垃圾内存的指针,这个指针地址不是NULL。指向的对象被释放了,但是这个指针还没有置为nil,再次访问指向的值的时候就会出现不可预期的错误
僵尸指针:“僵尸指针”就是野指针的一种情况,即该指针指向的对象已经被释放,但是却没有对当前指针赋值为nil。
僵尸对象:简单的来说,僵尸对象是已经被释放的对象。如果在程序中再度使用该对象,一般会出现报错:unrecognized selector sent to instance
内存泄漏:如果程序运行时一直分配内存而不及时释放无用的内存,程序占用的内存越来越大,直到把系统分配给该APP的内存消耗殚尽,程序因无内存可用导致崩溃,这样的情况我们称之为内存泄漏
property的属性默认是:readwrite,assign, atomic
assign修饰基本数据类型,(NSInteger,CGFloat)和C数据类型(int, float, double, char等),简单赋值,不更改引用计数。
@property (nonatomic, assign) int number;
@property (nonatomic, assign) id className;//id必须用assign
weak只是简单的指向某个对象(虚线指向),引用计数不会加一。声明为weak的指针,指针指向的地址一旦被释放,这些指针都将被赋值为nil。这样的好处能有效的防止野指针。
retain:在非ARC时代,你需要自己retain一个想要保持的对象(保其命),ARC环境下就不需要了。现在唯一要做的就是用一个指针指向这个对象,只要指针没有被重置为空,对象就会一直在堆上。当指针指向新值的时候,原来的对象就会被release一次。
strong:在ARC环境下,只要某一对象被一个strong指针指向,该对象就不会被销毁。如果对象没有被任何strong指针指向,那么就会被销毁。在默认情况下,所有的实例变量和局部变量都是strong类型的。可以说strong类型的指针在行为上跟非ARC下得retain是比较相似的。在ARC环境下,strong代替了retain,所以现在一般都是些strong。
copy:新建一个 对象,并且指向他,这个指向的功能和strong一样,强引用。
那么strong和copy的区别是什么?为什么需要copy这个关键字呢?下面娓娓道来首先理解strong的意义就是直接给指向的对象引用计数加1,而copy不是,他是另外开辟了一块新的内存空间,把赋值过来的内容复制到这个新的空间上,并且强引用他,那么这么做有什么意义呢?那就要从copy的意义说起了,在OC中,引进copy关键字的目的就是在改变备份的时候,不要影响到原始对象的值。仔细理解这句话的意义,就可以知道copy的意义了。
代码示例
还是结合代码来说明这个情况
@interface Person : NSObject
@property (strong, nonatomic) NSArray *bookArray1;
@property (copy, nonatomic) NSArray *bookArray2;
@end
@implementation Person
//省略setter方法
@end
//Person调用
main(){
NSMutableArray *books = [@[@"book1"] mutableCopy];
Person *person = [[Person alloc] init];
person.bookArray1 = books;
person.bookArray2 = books;
[books addObject:@"book2"];
NSLog(@"bookArray1:%@",person.bookArray1);
NSLog(@"bookArray2:%@",person.bookArray2);
}
我们看到,使用strong修饰的person.bookArray1输出是[book1,book2],而使用copy修饰的person.bookArray2输出是[book1]。这下可以看出来区别了吧。
备注:使用strong,则person.bookArray1与可变数组books指向同一块内存区域,books内容改变,导致person.bookArray1的内容改变,因为两者是同一个东西;而使用copy,person.bookArray2在赋值之前,将books内容复制,创建一个新的内存区域,所以两者不是一回事,books的改变不会导致person.bookArray2的改变。
其实出现上面的不同的根本原因是在c语言中,父类对象可以接受子类对象,例如可以把一个NSMutableArray赋值给一个NSArray类型的指针,(而反过来是不行的,因为加入把父类赋值给一个子类的时候,外部不知道这个子类真是的类型,调用子类的方法的时候,其实父类是不一定有这个方法的,那就会出现经典的crash,unRecognised seclector send to instance)这样的话当
改变NSMutableArray的时候,那么NSArray的指针指向的内容也变了(但是我们希望的是不会改变),因为他们指向的是同一块内存,同一个对象。本质原因:说到底,其实就是不同的修饰符,对应不同的setter方法,(先release旧值)
1. strong对应的setter方法,是将_property先release(_property release),然后将参数retain(property retain),最后是_property = property。
2. copy对应的setter方法,是将_property先release(_property release),然后拷贝参数内容(property copy),创建一块新的内存地址,最后_property = property。
深复制:复制内容,直接新开辟一块内存空间,把值赋值过去。
浅复制:指针复制,直接指向传过来的对象。
为什么会有这个深复制和浅复制呢?就是因为当把一个不可变的对象copy一次时,因为他本身就是不可变的,所以不用担心他的改变会影响原始对象的问题,那么系统为了节约内存空间,就直接来了一次指针赋值,也就是浅复制,这个时候其实就是和strong完全一样的效果,但是如果是把一个可变的对象进行copy的时候,因为是可以改变的,那就必须要重新开辟内存空间来处理了,这就是深复制。所以为了安全起见,类似NSString(NSMutablString),NSArray(NSMutableArray),NSDictionary(NSMutableDictionary)这种就直接全部用copy比较安全,但是如果完全理解其中的内部原理,那就选择适当的关键字来修饰了。
同时,也就不难理解为什么声明一个NSMutableArray时,如果用copy声明,那么后面调用addObject或者removeObject时,就会出现找不到方法的crash,因为当你创建数组时,self.array = [NSMutableArrry array];,这句话其实就是在调用他的set方法,根据上面说copy修饰的setter方法的实现,就会新开辟一个内存空间,把参数copy一份传过去,此时self.array其实是一个NSArray的拷贝,当然就找不到那些子类的方法了,但是如果是strong修饰的,那就是直接指向,也就是可变的数组,相应的方法就可以顺利调用了。(其实这里还是有点懵逼,[NSMutableArrry array]不是应该得到一个可变的数组吗?子类调用应该生成子类的对象啊,为毛copy出来的就是父类的对象呢???)
解释上面的懵逼:其实懵逼的原因是因为两个概念没搞清楚,第一:上面一直说的拷贝和副本,不要简单的理解成拷贝文件那样,这个副本是要看得到的结果的。可能这样说不说很明白,下面的代码解析会很清晰。第二:copy这个方法,只要调用就会产生一个不可变的结果,不管是谁调的。先说到这里,直接上代码,后面再总结:其实
@property (nonatomic, copy) NSMutableArray *array;
self.array = [NSMutableArray array];
的真正的意义是
@property (nonatomic, strong) NSMutableArray *array;
NSMutableArray *b = [NSMutableArray array];
self.array = [b copy];//注意了,问题就出在这里,这里其实等号右边的结果是一个不可变的,赋值给了self.array
这个问题出现的原因首先就是copy方法没有完全理解透彻,其实创建一个副本赋值给这个属性,这个副本不是就是简单的像拷贝文件一样产生一个一模一样的,他其实是调用的copy方法,这个时候第二个问题就来了,要明白调用copy方法得到的就是不可变的,原则:不管谁调用的,只有调用了copy方法就得到了一个不可变的副本,不管是是谁调用mutableCopy得到的就是一个可变的副本,和谁调用的没有关系,只和调用的方法有关系,注意理解这个副本不是仅仅就像拷贝一个文件一样,一毛一样的,这个要看调用产生的结果的!!!!!!!!!!!!!!!!!!!!!!!!!
至此,上面的懵逼也就烟消云散了,关键问题出在对copy的误解,而且对copy修饰的set方法的误解。这下扯淡透彻了!!!
readonly:表示这个属性是只读的,就是只生成getter方法,不会生成setter方法
readwrite:此标记说明属性会被当成读写的,这也是默认属性。存取器方法都需要在@implementation中实现(已经声明了)。如果使用@synthesize关键字,存取器方法都会被自动创建
nonatomic:指出访问器不是原子操作,而默认地,访问器是原子操作。实质上来说,应该考虑线程安全的,但是由于是移动端的开发,考虑到性能开销等,还是声明为nonatomic。因为开发中的很多方法和系统内部的设计,都是线程安全的。
unsafe_unretained:unsafe_unretained 就是ios5版本以下的 assign ,也就是 unsafe_unretained , weak, assign 三个效果都是一个样的。但是他是不安全的,指向的对象销毁后不会自动设置为nil,所以先基本上很少使用,直接使用weak就行了。
synthesize和dynamic:@property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = _var;
@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法
@dynamic告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。