【iOS】定义@property时常用的修饰词介绍

iOS编程中,定义成员变量常用格式如下:

@property (nonatomic, strong) UILabel *label; 

常用的修饰词有atomic,nonatomic,copy,assign,strong,weak,readonly,readwrite等。面试中也会常常被问到这些修饰词的含义及其之间的区别,其本质就是iOS的内存管理,下面会详细介绍每个词的特性和它们的区别,还会补充__block与__weak的相关知识。

atomic

  • 属性的默认属性,当前线程进行到一半其他线程来访问当前线程,可以保证先执行完毕当前线程,但只是保证setter/getter完整,不是线程安全的。
  • atomic虽然对属性的读和写是原子性的,但是仍然可能出现线程错误,比如:@property(atomic,strong)NSMutableArray *mArray;如果一个线程对mArray循环读数据,另一个线程循环写数据,那么[self.mArray objectAtIndex:index]就不是线程安全的。可以用加锁的方法来解决这个线程不安全的问题。
  • atomic的作用只是给getter和setter加了个锁,atomic只能保证代码进入getter或者setter函数内部时是安全的,一旦出了getter和setter,多线程安全只能靠程序员自己保障了。所以atomic属性和使用property的多线程安全并没什么直接的联系。另外,atomic由于加锁也会带来一些性能损耗,所以我们在编写iOS代码的时候,一般声明property为nonatomic,在需要做多线程安全的场景,自己去额外加锁做同步。

    nonatomic

  • 属性的非默认属性,需要编码者手动写。不仅是线程不安全的,也无法保证setter/getter的完整,多个线程同时访问这个属性,结果无法预计。
  • nonatomic速度比atomic速度快,所以通常用nonatomic。

    copy

  • 常见的copy声明的是NSString和block。
  • copy生成的对象是一个新的对象,不是引用原来的对象,对原对象的引用计数无影响。
  • 使用copy创建的新对象也是强引用,使用完成后需要负责释放该对象。
  • copy特性可以减少对象对上下文的依赖。新对象、原始对象中任一对象的值的改变不会影响另一个对象的值。
  • 以下代码可验证copy修饰(NSString的实现遵循了NSCopy协议,所以默认为copy)的各个对象值的改变互不影响。

    -(void)stringCopyTest{
    NSString * A = @"A";
    NSString * B = A;
    NSString * C = [A copy];
    NSLog(@"A === %@ B === %@ C === %@",A,B,C);
    A = @"AB";
    NSLog(@"A === %@ B === %@ C === %@",A,B,C);
    B = @"ABC";
    NSLog(@"A === %@ B === %@ C === %@",A,B,C);
    C = @"abcd";
    NSLog(@"A === %@ B === %@ C === %@",A,B,C);
    }

    assign

  • 只是引用,只返回指针。用于float、int等基本数据类型。
  • 如果用来修饰对象,编译会产生警告:Assigning retained object to unsafe property; object will be released after assignment。在释放之后,指针的地址还存在,也就是说指针并没有被置为nil,会造成野指针。对象一般分配在堆上的某块内存,如果后续的内存分配中用到了这块地址,就会造成crash。
  • 由于基本数据类型一般是分配在栈上的,栈的内存会由系统自己处理,不会造成野指针,所以assign可以用来修饰基本数据类型。

    strong

  • 属性的默认属性,iOS引入ARC之后,用strong代替了retain。
  • 创建一个强引用的指针,引用对象的引用计数加1。strong特性表示两个对象内存地址相同(建立一个指针,进行指针拷贝),内容会一直保持相同,直到更改一方的内存地址,或者将其置为nil。
  • 除了NSString之外的实例变量和局部变量都默认是strong。
  • 以下代码可以验证如果有多个strong对象同时引用一个属性,任一对象对该属性的修改都会影响其他对象获取的值。

    -(void)strongTest{
    NSMutableArray * marray1 = [NSMutableArray arrayWithArray:@[@"aaa"]];
    NSMutableArray * array = marray1;
    NSLog(@"1 === %@ 2 === %@",marray1,array);
    [marray1 addObject:@"bbb"];
    NSLog(@"1 === %@ 2 === %@",marray1,array);
    [array addObject:@"ccc"];
    NSLog(@"1 === %@ 2 === %@",marray1,array);
    }

    weak

  • 弱引用,对象引用技术不会加1。weak修饰的对象被释放之后,会自动置为nil,像nil发送消息,什么都不会执行,程序也不会crash。
  • 代理使用weak。delegate几乎一直持有代理对象,所以代理对象应该对代理使用weak,否则会形成循环引用。但也有例外,如果代理对象的生命周期比较短,代理对象也可以使用strong。

    readonly

  • 变量的非默认属性,只有可读方法,即只有getter方法。

    readwrite

  • 变量的默认属性,会生成setter方法和getter方法。
  • 如果希望一个属性只允许自己读写,而对所有外部文件都是只读的,可以在接口部分声明该属性为readonly类型,最后在私有接口部分重写该属性为readwrite类型。

    assign 与weak

  • assign适用于基本数据类型,weak适用于NSObject对象,并且是一个弱引用。
  • assign修饰的对象被释放后内存地址不会被释放,会造成野指针;weak修饰的对象被释放后,对象会被置为nil,不会出现内存泄漏的问题。

    strong 与copy

  • 通过上面的代码可能看出strong的两个指针会指向同一个内存地址;copy会在内存里拷贝一份对象,两个指针指向不同的内存地址。
  • block的循环引用并不是strong导致的,在ARC下,系统底层也会做一次copy操作使block从栈区复制一块内存空间到堆区,所以strong和copy在对block的修饰上是没有本质区别。

    strong与weak

  • strong是强引用,会让对象的引用计数+1;weak是弱引用,对象的引用计数不变。
  • 帮助理解二者区别的例子:假设对象是一条小狗,小狗想跑。strong类型就像是拴狗的绳子,只要有一条绳子栓住狗,它就不能跑走,如果有五条绳子拴着同一条狗(五个strong类型指向同一个对象),只有当五条绳子都释放狗才可以跑走。weak类型就像是小孩子看着小狗说:看这里有小狗。只要还有绳子拴着小狗,小孩子们就可以继续指着小狗说:看这里有小狗。当绳子释放了的时候,不管有多少小孩子依旧在指着小狗说:看这里的小狗。小狗都会跑掉。当最后一个strong指针不再指向这个对象,这个对象就会被释放,此时,所有指向这个对象的weak指针都将被清空且置为nil。 # __block与__weak
  • __block是用来修饰一个变量,这个变量可以在block中被修改。
  • 使用__block修饰的变量在block代码块中会被retain(ARC下会retain,MRC下不会retain)。
  • 使用__weak修饰的变量不会在block代码块中被retain。同时,在ARC下,要避免出现循环引用可以使用如下代码:

    __weak typedof(self) weakSelf = self;

    整理这些的时候也写了一些代码来验证,发现东西其实很多,往细处想只会发现自己不会的更多。面试的时候经常会听到的一句话就是说一下内存管理,以前都会很懵,感觉内存管理无从说起。现在慢慢理解,其实这些东西都是内存管理的体现,再遇到问这个问题的,这些都能说上来应该也不算太差。

上一篇:Mozilla发补丁修复火狐5个安全漏洞


下一篇:【iOS】浅拷贝和深拷贝