《52个有效方法》笔记2——关于“属性”的一些细节
关于“属性”
属性的作用域:
很多语言在定义属性时可以指定其作用域(private/public等),OC中也有该关键字(@private/@public),不过几乎不会用到。因为这种写法导致的结果:对象布局在编译期就已经确定了。这有什么问题吗?
嗯,是有问题的。
对象的属性在内存中被访问是通过其和对象在内存中的偏移量来查找的。这种写法的问题是当你给对象新加入一个属性后,原来各属性的偏移量会弄错乱,所以需要重新编译。例如,某代码库中的一个类使用的是旧的类定义,而和它进行链接的类却新定义了属性,那么就会出现不兼容现象。
OC解决这种问题的方案是在运行期才进行判断,若发现类的定义变了,则按照最新的(此时已编译过)偏移量去查找属性。这是极好极安全的。
由属性自动合成存取方法(setter/getter):
@property (nonatomic, copy)NSString *userName;
@property会自动在我们的属性名前加下划线,所以真正的属性名是_userName。
@synthesize userName = _myUserName;
用于自定义属性名,此时属性名是_myUserName;(不过一般情况下貌似没有自定义属性名的这个必要)。
最重要的是编译器会帮我们生成属性存取方法。而你若想阻止编译器自动合成setter和getter方法,则可以使用@dynamic userName;
关键字。需要注意的是@synthesize和@ dynamic关键字都是写在.m文件的@implementation下的。
修饰属性的一些关键字
- 原子性:所谓原子性,就是说在操作该属性时会加锁,确保读写正确。atomic代表该属性是原子性的,而nonatomic则相反,代表非原子性的。一般情况下我们都使用nonatomic,因为原子性的代价是性能消耗厉害。
- 读写权限:readonly代表只可读,writeonly代表只可写。
- 内存管理:在这里我只说copy,因为以前对copy没有深入理解过。
关于为什么NSString类型的属性要用copy
注意:我们定义属性时在@property后加了这些修饰词后,编译器会帮我们自动合成满足这些条件的存取方法:
- (void)setUserName:(NSString *)userName
{
if(_userName!=userName)
{
_userName = [userName copy];
}
}
同理,我们自定义初始化方法时也要遵守我们定义属性时的语义。既然属性_userName是NSString类型的,是被copy修饰的,那我们定义初始化方法时就应该这样:
- (id)initWithUserName:(NSString *)userName
{
self = [super init];
if(self)
{
_userName = [userName copy];
}
return self;
}
使用_userName还是self.userName?
在对象内部是直接访问实例变量呢(_userName)?还是通过存取方法访问呢(setter/getter)?
直接访问_userName当然速度更快。
但是绕过了存取方法,我们定义属性时的修饰语义也就不会落实。比如,没有了给_userName属性赋值时的copy处理,则不太安全。
而且直接访问_userName不会触发键值观察(KVO),这个具体情况做取舍。
所以,综上所述,我们折中的方案是:在获取属性值时直接读取实例变量_userName;而在赋值时要通过setter方法。
NSLog(@"%@",_userName); // 读取
self.userName = @"wang66"; // 赋值