引言
property 是 OC 的一项特性,用于封装对象中的数据。
OC 通常把所需要的数据保存为各种实例变量,实例变量通过存取方法来访问,即 setter 和 getter
@property 的优势
尽量多的使用属性(property)而不是实例变量(attribute)因为属性(property)相比实例变量有很多的好处:
-
自动合成 getter 和 setter 方法。当声明一个属性(property)的时候编译器默认情况下会自动生成相关的 getter 和 setter 方法
-
更好的声明一组方法。因为访问方法的命名约定,可以很清晰的看出 getter 和 setter 的用处。
-
属性(property)关键词能够传递出相关行为的额外信息。属性提供了一些可能会使用的特性来进行声明,包括 assign(vs copy),weak,strong,atomic(vs nonatomic),readwrite,readonly 等
属性生成
@property 的本质是 ivar(实例变量) + getter + setter。
默认情况下 @property 是用 @synthesize (合成)修饰的(默认实现: @synthesize property = _property),自动生成 ivar + setter + getter。
ivar 是实例变量 编译器自动生成 _属性名 的实例变量
例如:
objc
@property (nonatomic, readwrite, copy) NSString *name;
@synthesize name = _name;
@synthesize 会自动生成 setter 和 getter 方法,但是也可以省略 @synthesize 关键字,让编译器自动合成 getter 和 setter 方法。
单独重写 getter 和 setter 方法没有问题,但是如果同时重写 getter 和 setter 方法,系统部会自动生成 _property 变量,编译器会报错,所以如果需要同时重写 getter 和 setter 方法,要添加 @synthesize
objc
// 重写 getter 和 setter 方法
- (NSString *)name {
//必须使用_name来访问属性值,使用self.name来访问值时编译器会自动转为调用该函数,会造成无限递归
return _name;
}
- (void)setName:(NSString *)name {
//必须使用_name来赋值,使用self.name来设置值时编译器会自动转为调用该函数,会导致无限递归
//使用_name则是直接访问底层的存储属性,不会调用该方法来赋值
//这里使用copy是为了防止NSMutableString多态
if (_name != name) {
_name = [name copy];
}
}
property 常用指示符
存取器方法 getter setter
指定获取属性对象的名字为 getterName,如果你没有使用 getter 指定 getterName ,系统默认直接使用 propertyName 访问即可。通常来说,只有所指属性需要我们指定 isPropertyName 对应的 Bool 值时,才使用指定 getterName ,一般直接用 PropertyName 即可。
setter=setterName: 则是用来指定设置属性所使用的的 setter 方法,即设置属性值时使用 setterName: 方法,此处 setterName 是一个方法名,因此要以":"结尾,具体示例如下:
objc
// 指定getter方法名为isBlue
@property (nonatomic, readonly, getter=isBlue) BOOL blue;
// 指定setter方法名为 theNickname
@property (nonatomic, copy, setter=theNickname:) NSString *nickname;
原子性 nonatomic atomic
atomic(默认属性):原子性访问,编译器会通过默认机制(锁定机制)确保 getter 和 setter 完整性(系统给自动生成的 getter 和 setter 方法进行加锁) 。但是 atomic 并不是绝对线程安全,A 先成进行写操作后(写完成),B 线程又进行写,A 线程再读取就不一定是之前写入得值(可能是 B 的)。如果是 MRC,C 线程进行了 release 操作,会 crash,破坏了线程安全,所以要添加锁等操作来保证线程安全。所以 atomic 只是了保证了存取方法的线程安全,并不能保证整个对象是线程安全的(如 NAArray 的 objectAtIndex:就不是线程安全的,需要加锁等保证线程安全)。
nonatomic:非原子性访问,不保证 setter 和 getter 的完整性,本质来说就是去掉了 atomic 对 getter 和 setter 方法添加的锁(系统不会给自动生成的 getter 和 setter 方法进行加锁),也就是 getter 和 setter 方法不是线程安全的,比如,当 A 线程进行写操作,B 线程突然把未修改好的属性值提取出来,时候线程读到的值不一定是对,iOS 中同步锁开销过大会带来性能问题,一般情况下不要求属性是 atomic,因为其本身不能保证线程安全。
objc
@property (nonatomic, readwrite, copy) NSString *name;
@property (atomic, readwrite, copy) NSString *atomicName;
读写权限 readwrite readonly
readwrite(编译器默认属性):可读可选权限,若该属性由@synthesize 修饰,自动生成对应的 getter 和 setter 方法。
readonly:只读权限,若该属性由@synthesize 修饰,只生成 getter 方法,不生成 setter。
objc
@property (nonatomic, readwrite, copy) NSString *name;
@property (nonatomic, readonly, getter=isBlue) BOOL blue;
内存管理:assign strong weak copy retain unsafe_unretained
assign 是指针赋值,没有引用计数操作,对象销毁之后不会自动置为 nil
assign 对属性只是简单的赋值操作,不更改赋值新值的引用计数,即不进行 retain 操作,也不改变旧值的引用计数,常用于标量类型,NSInteget、NSUInteget、CGFloat、NSTimeInterval、Bool 等。
assign 也可以修饰对象如 NSString 等类型对象,因为不改变引用计数,所以当新值的引用计数为 0 对象被销毁时,当前属性并不知道,编译器不会将该属性置为 nil,指针仍然指向之前被销毁的内存,这个时候访问该属性会产生野指针,并 crash,所以 assign 修饰的类型一定是标量类型。例子
objc
@property (nonatomic, assign) NSUInteger age;
@property (nonatomic, assign) NSString *assignName;
copy copy 一个新对象,引用计数+1。
当调用修饰对象的 setter 方法时会建立一个新对象,引用计数+1,即对象会在内存里拷贝一个副本,两个指针指向不同的地址。
copy 一般用来修饰有可变类型子类的对象:NSArray,NSDictionary,NSString, 为确保这些不可变对象因为可变子类对象影响,需要 copy 一份备份,如果不使用 copy 修饰,使用 strong 或 assign 等修饰则会因为多态导致属性值被修改。例子
objc
@property (nonatomic, copy) NSArray *array;
block 也用 copy 修饰。
objc
@property (nonatomic, copy) ABlock *block;
对于可变类型如 NSMutableString、NSMutableArray、NSDictionary 则不能用 copy 修饰,因为这些类都实现了 NSCopying 协议,使用 copy 方法返回的都是不可变对象。
即如果使用 copy 修饰符在对可变对象赋值时会获取一个不可变对象,接下来如果对这个对象进行可变对象的操作会产生异常 Crash,因为没有 mutableCopy 修饰符,对于可变对象使用 strong 修饰符来修饰。例子
objc
@property (nonatomic, strong) NSMutableArray *mutableArray;
strong 强引用,引用计数+1
strong 表示属性对所赋值的对象强引用,是一种拥有关系,增加新值的引用计数,释放旧值减少引用计数, 修饰对象的引用计数会+1,当对象的引用计数为 0 时,对象就会在内存中释放,通常修饰对象类型、可变集合、可变字符串
objc
@property (nonatomic, strong) NSMutableString *strongName;
retain 强引用,引用计数+1
MRC 下的强引用修饰词,跟 strong 同理,ARC 中被 strong 代替
weak 弱引用,没有引用计数操作,对象销毁之后自动置为 nil
weak 表示属性对所赋值的对象弱引用,是一种非拥有关系,所赋的值在引用计数为 0 被销毁后,weak 修饰的属性会被自动置为 nil 能够有效防止野指针错误。
weak 一般用来修饰代理 delegate,避免循环引用。weak 与 assign 不同,只能修饰对象类型
objc
@property (nonatomnic, assign) id <AProtocol> delegate;
unsafe_unretained 弱引用,没有引用计数操作,修饰的对象销毁之后指针不会自动置为 nil,如果此时在调用该指针会产生野指针:EXC_BAD_ACCESS
错误
不安全非拥有,类似 assign 非常少用 但只能修饰对象类型,不能修饰标量类型
分类 Category 中添加属性
在分类中,添加@propety 属性只会生成 setter 和 getter 方法的声明,并不会有具体的实现,以为 Category 在运行时对象的内存布局就已经确定了,如果此时添加实例变量就会破坏对象的内存布局,所以 Category 无法添加实例变量。
所以如何在 Category 实现实例变量功能呢?可以通过 Runtime 添加关联对象实现成员变量:
objc
@property (nonatomic, assign) NSTimeInterval yz_acceptEventTime; /// 发生时间
- (NSTimeInterval)yz_acceptEventTime {
return [objc_getAssociatedObject(self, button_acceptEventTime) doubleValue];
}
- (void)setYz_acceptEventTime:(NSTimeInterval)yz_acceptEventTime {
objc_setAssociatedObject(self, button_acceptEventTime, @(yz_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}