@property
和 attribute
的中文翻译都是属性,所以本文就只用英文做区分了。property
的不同 attribute
大致包含下面这些
- strong //default
- weak
- copy
- assign //default
- unsafe_unretained
- atomic & nonatomic //default atomic
- readonly & readwrite //default readwrite
接下来依次说一下这些 attribute
的用处,使用方法还有使用时的可能注意事项
# strong
strong
是 ARC
引入的方便内存管理的一种 attribute
,跟 MRC
属性中的retain
效果基本是一样的。区别在于编译器遇到 strong
修饰的变量的时候会自动为其在合适的地方插入一条 release
语句。相同的地方就是为了强引用属性。强引用的意思就是当前对象持有自己的属性对象,如果当前对象不释放的话这个属性也不会被释放,而当对象释放的时候,ARC
也会自动为我们处理属性的释放,不需要开发者关心。strong
是我们最常用的一个 attribute
。
几个说明:
strong attribute
和变量前的修饰符__strong
在ARC
里的作用是一样的。只不过一个修饰属性一个修饰变量而已。参考 Property declarations (opens new window)strong
不能用来修饰非对象。否则编译器会报错Property with retain(or strong) attribute must be object type
一般如果某个属性前面不需要显式地写出
strong
编译器会自动认为这是strong
类型的属性。strong
修饰符会自动处理以下两种 case,而引用计数不会出现异常。{// 自己生成并持有对象 id __strong obj = [[NSObject alloc] init]; } { // 非自己生成并持有对象. id __strong obj = [NSMutableArray array]; }
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
能解决引起内存泄露的循环引用问题。
几个说明:
weak
和strong
一样只能用来修饰对象。weak attribute
和变量前的修饰符__weak
在ARC
里的作用是一样的。
# 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
使用多少有点区别的。MRC
的 assign
是可以修饰对象还有普通基本类型,因为你根本也没得选。但是 ARC
下虽然 assgin
也都可以修饰基本类型和对象,但是通常修饰对象的话不会用 assign
,因为缺少了 weak
修饰变量被释放时候置为 nil
的特性,很有可能出现内存问题。所以我们现在用 assign
修饰基本类型就好了,需要弱引用的时候用 weak
用来修饰对象。规则也比较明确。
# unsafe_unretained
unsafe_unretained
其实就是阉割版本的 weak
实现,它缺少正是对象释放时候置为 nil
的特性。但是 unsafe_unretained
还能修饰基本数据类型,weak
不行。
看起来,我们使用 weak 就好了为啥要用 unsafe_unretained
这个属性呢。答案是因为 __weak
只支持 iOS 5.0
和 OS X Mountain Lion
作为部署版本,如果是想要兼容更低的支持 ARC 的版本,比如你想部署回 iOS 4.0
和 OS X Snow Leopark
就不得不使用 unsafe_unretained
。
还有一种说法是 weak
底层实现比较繁琐消耗性能,我认为与 weak
带来的好处相比,这点消耗可以忽略。
# atomic & nonatomic
atomic
和 nonatomic
区别在于向属性对应成员变量赋值的时候是否为原子写入,即能不能够保证安全写入,从这一点上 atomic
确实是安全的。但是 atomic
并非是线程安全的,因为 atomic
控制的粒度太细了。
举个例子,A 线程向属性写入一个值,A 线程后续再次读这个值之前,可能 B 线程也向同样的属性里写入另外一个值,这样 A 线程读取的时候并非读到的是自己刚写入的值而是一个预期之外的值。
从这一点上看 atomic
似乎用处不是很大,同时 atomic
底层是用锁实现的,频繁写入会影响性能。个人认为最好的实践是,使用 nonatomic
然后自己去处理线程相关的东西。
几个说明:
atomic
是默认属性。比如@property NSString *name;
这种属性默认就是atomic
修饰的。atomic
的底层实现里,赋值和读取值都有锁的保护,而且使用的都是一个锁。atomic
不允许开发者自己复写其getter
方法,强制复写会得到一个警告。#Writable atomic property 'name' cannot pair a synthesized setter with a user defined getter
# readonly & readwrite
readonly
和 readwrite
其实就是编译器级别帮你做了只读和读写的处理。
readonly
是告诉编译器不用生成 setter
方法,同时如果你对这个 readonly
属性赋值的时候编译器会报错
#Assignment to readonly property
readwrite
是默认的属性,它会告诉编译器自动生成 setter
和 getter
方法。开发者可以随意复写这两个方法来满足自己的时机情况。
# 补充关于自动合成属性的声明。
Clang
提供了对已声明属性自动合成的支持。这个功能提供了没有被 @dynamic
修饰的属性的 getter
和 setter
方法,而不用用户手动添加。
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.
参考
- Transitioning to ARC Release Notes (opens new window)
- Objective-C Automatic Reference Counting (ARC) (opens new window)
- property 属性修饰符总结 (opens new window)
- Objective-C: Property Attribute Reference Guide (opens new window)
- Encapsulating Data (opens new window)
- Variable property attributes or Modifiers in iOS (opens new window)