属性 attribute 总结

@propertyattribute 的中文翻译都是属性,所以本文就只用英文做区分了。property 的不同 attribute 大致包含下面这些

  1. strong //default
  2. weak
  3. copy
  4. assign //default
  5. unsafe_unretained
  6. atomic & nonatomic //default atomic
  7. readonly & readwrite //default readwrite

接下来依次说一下这些 attribute 的用处,使用方法还有使用时的可能注意事项

# strong

strongARC 引入的方便内存管理的一种 attribute,跟 MRC 属性中的retain 效果基本是一样的。区别在于编译器遇到 strong 修饰的变量的时候会自动为其在合适的地方插入一条 release 语句。相同的地方就是为了强引用属性。强引用的意思就是当前对象持有自己的属性对象,如果当前对象不释放的话这个属性也不会被释放,而当对象释放的时候,ARC 也会自动为我们处理属性的释放,不需要开发者关心。strong 是我们最常用的一个 attribute

几个说明:

  1. strong attribute 和变量前的修饰符 __strongARC 里的作用是一样的。只不过一个修饰属性一个修饰变量而已。参考 Property declarations (opens new window)

  2. strong 不能用来修饰非对象。否则编译器会报错

    Property with retain(or strong) attribute must be object type
    
  3. 一般如果某个属性前面不需要显式地写出 strong 编译器会自动认为这是 strong 类型的属性。

  4. strong 修饰符会自动处理以下两种 case,而引用计数不会出现异常。

    {// 自己生成并持有对象
        id __strong obj = [[NSObject alloc] init];
    }
    { // 非自己生成并持有对象.
        id __strong obj = [NSMutableArray array];
    }
    
  5. objc_storeStrong 方法的说明,当向一个 __strong 修饰符修饰的对象赋值的时候跟执行下面代码是一样的效果。说白了就是执行了 objc_storeStrong 的方法。

    //Precondition: object is a valid pointer to a __strong object which
    //is adequately aligned for a pointer. value is null or a pointer to a valid object.
    void objc_storeStrong(id *object, id value) {
      id oldValue = *object;
      value = [value retain];
      *object = value;
      [oldValue release];
    }
    

    值得说明的是 FRObject *obj = [FRObject frobj];FRObject *obj = temp; 这两句代码编译器的处理是不一样的。第二句会执行如上 objc_storeStrong 的方法,但是第一句不会。我个人理解原因是 obj 初始化的时候并没有 oldValue,所以没有必要这么做。

# weak

weak 也是 ARC 引入的方便内存管理的一种 attribute,跟 assign 效果基本是一样的。区别在于在对象被释放的时候,weak 机制会自动将对象置为 nil,保证后续访问这个对象不会因为野指针闪退。

真正引入 weak 的原因是 weak 能解决引起内存泄露的循环引用问题。

几个说明:

  1. weakstrong 一样只能用来修饰对象。
  2. weak attribute 和变量前的修饰符 __weakARC 里的作用是一样的。

# copy

copy 要求它修饰的属性必须实现 NSCopy 协议。本质上,当 copy 修饰的属性被赋值的时候,新值会收到一个 copyWithZone 方法,旧的值会被 release。

//clang 编译后的源码
static void _I_Fan_setName_(Fan * self, SEL _cmd, NSString *name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Fan, _name), (id)name, 1, 1);
}
//runtime 里的对应方法
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
//reallySetProperty 有对应 copy 属性的具体实现.

copy 的使用场景通常是你希望属性在赋值后一直保持值不变,而不是跟着它指向的对象一直变。讨论比较多的是 NSString 是应该用 copy 还是用 strong 去修饰

@property (atomic) NSString *name;

NSMutableString *nameString = [NSMutableString stringWithString:@"John"];
Fan *fan = [[Fan alloc] init];
fan.name = nameString;
NSLog(@"fan.name = %@ %p",fan.name,fan.name);
[nameString appendString:@"ny"];
NSLog(@"fan.name = %@ %p",fan.name,fan.name);
//输出结果如下
2018-01-11 20:35:18.763988+0800 TTTTT[71790:3384299] fan.name = John 0x10051e9c0
2018-01-11 20:35:18.764449+0800 TTTTT[71790:3384299] fan.name = Johnny 0x10051e9c0

可以看到 fan.name 在没有显式修改的时候被改掉了,这种情况出现在 NSString 指针子类 NSMutableString 的情况下,而 NSMutablString 的修改不会对本身地址产生影响。使用 copy 可以避免。个人觉得使用 copy 还是 strong 还是根据实际情况,如果出了问题知道为什么就好了。

# assign

值得注意的是 MRC 时代和 ARC 时代的 assign 使用多少有点区别的。MRCassign 是可以修饰对象还有普通基本类型,因为你根本也没得选。但是 ARC 下虽然 assgin 也都可以修饰基本类型和对象,但是通常修饰对象的话不会用 assign ,因为缺少了 weak 修饰变量被释放时候置为 nil 的特性,很有可能出现内存问题。所以我们现在用 assign 修饰基本类型就好了,需要弱引用的时候用 weak 用来修饰对象。规则也比较明确。

# unsafe_unretained

unsafe_unretained 其实就是阉割版本的 weak 实现,它缺少正是对象释放时候置为 nil 的特性。但是 unsafe_unretained 还能修饰基本数据类型,weak 不行。

看起来,我们使用 weak 就好了为啥要用 unsafe_unretained 这个属性呢。答案是因为 __weak 只支持 iOS 5.0OS X Mountain Lion 作为部署版本,如果是想要兼容更低的支持 ARC 的版本,比如你想部署回 iOS 4.0OS X Snow Leopark 就不得不使用 unsafe_unretained

还有一种说法是 weak 底层实现比较繁琐消耗性能,我认为与 weak 带来的好处相比,这点消耗可以忽略。

# atomic & nonatomic

atomicnonatomic 区别在于向属性对应成员变量赋值的时候是否为原子写入,即能不能够保证安全写入,从这一点上 atomic 确实是安全的。但是 atomic 并非是线程安全的,因为 atomic 控制的粒度太细了。

举个例子,A 线程向属性写入一个值,A 线程后续再次读这个值之前,可能 B 线程也向同样的属性里写入另外一个值,这样 A 线程读取的时候并非读到的是自己刚写入的值而是一个预期之外的值。

从这一点上看 atomic 似乎用处不是很大,同时 atomic 底层是用锁实现的,频繁写入会影响性能。个人认为最好的实践是,使用 nonatomic 然后自己去处理线程相关的东西。

几个说明:

  1. atomic 是默认属性。比如 @property NSString *name; 这种属性默认就是 atomic 修饰的。

  2. atomic 的底层实现里,赋值和读取值都有锁的保护,而且使用的都是一个锁。

  3. atomic 不允许开发者自己复写其 getter 方法,强制复写会得到一个警告。

    #Writable atomic property 'name' cannot pair a synthesized setter with a user defined getter
    

# readonly & readwrite

readonlyreadwrite 其实就是编译器级别帮你做了只读和读写的处理。 readonly 是告诉编译器不用生成 setter 方法,同时如果你对这个 readonly 属性赋值的时候编译器会报错

#Assignment to readonly property

readwrite 是默认的属性,它会告诉编译器自动生成 settergetter 方法。开发者可以随意复写这两个方法来满足自己的时机情况。

# 补充关于自动合成属性的声明。

Clang 提供了对已声明属性自动合成的支持。这个功能提供了没有被 @dynamic 修饰的属性的 gettersetter 方法,而不用用户手动添加。

Clang provides support for autosynthesis of declared properties. Using this feature, clang provides default synthesis of those properties not declared @dynamic and not having user provided backing getter and setter methods. __has_feature(objc_default_synthesize_properties) checks for availability of this feature in version of clang being used.

# 一些面试时常遇到的 QA

Q: assign, weak 和 unsafe_unretained 的区别? A: assign 和 unsafe_unretained 是等价的。weak 和它们区别是在对象释放的时候增加了将其置为 nil 的功能。

Q: atomic 是不是线程安全的? A: 参考上面说明.

Q: @property NSString *name; 这个属性的 attribute 是? A: strong,atomic,readwrite.

参考

  1. Transitioning to ARC Release Notes (opens new window)
  2. Objective-C Automatic Reference Counting (ARC) (opens new window)
  3. property 属性修饰符总结 (opens new window)
  4. Objective-C: Property Attribute Reference Guide (opens new window)
  5. Encapsulating Data (opens new window)
  6. Variable property attributes or Modifiers in iOS (opens new window)