【iOS】关于属性的理解与验证
iOS里面有属性,而且很常用。我们经常在.h,.m文件里写:
@interface ViewController : UIViewController @property (nonatomic, strong) NSNumber *firstNumber; @property (nonatomic, strong) NSNumber *secondNumber; @property (nonatomic, strong) NSNumber *thirdNumber; @property (nonatomic, strong) NSNumber *fourthNumber; @end
这就是属性,个人理解的属性就是普通的变量加上了一些通用的规则,有点类似于扩展一下,在编译的时候编译器帮我们生成一些东西(猜测,并未验证)。
一、属性的含义
属性有三个作用:
1,定义了变量,相当于在程序里做以下声明:
@interface ViewController () { NSNumber *_firstNumber; NSNumber *_secondNumber; NSNumber *_thirdNumber; NSNumber *_fourNumber; } @end
2,为生成的变量加上修饰符
这里的修饰符大概就是给变量加上__strong,__weak之类的修饰符,具体区别和联系比较复杂,暂时不在这里说了。
这里有一个nonatomic要注意下,声明的时候养成习惯,都加上。它的作用是标明属性是非原子性的,对应的是atomic。iOS默认不写nonatomic会被自动认为是atomic的,而atomic属性本身特别消耗资源,所以如果你不想让程序太卡的话,请加上nonatomic吧。
3,生成getter setter函数
自动生成的函数就是get/set + 首字母大写的属性名: - (NSNumber *)getFirstNum();
getter setter函数是可以定义的,如: @property (nonatomic, getter=isOn) BOOL on; 这样对应的getter函数就是- (BOOL)isOn(); 而非 - (BOOL)getOn();
二、属性的get/set函数相关问题
如上所说,生成的get/set函数是get/set + 首字母大写的属性名 来命名的,那么,当属性名首字母为大写的时候会出现什么情况?当函数名get后的字母是小写的时候又会发生什么事情呢?(注:在SSH里面,设置属性可能出现问题,那里默认首字母如果大写的话,get/set函数默认变成第二个字母大写...然后就各种奇葩规则了)
1,仅首字母大小写不同的属性,生成的get/set函数是一样滴!如果强势使用的话,你会收到编译器的error,如下:
@property (nonatomic, strong) NSNumber *firstNumber; @property (nonatomic, strong) NSNumber *FirstNumber; - (void)setFirstNumber:(NSNumber *)firstNumber { _firstNumber = firstNumber; }
看起来不错,只要保证命名规范,我们重写get/set函数的时候就不会出问题了。
2,get/set函数必须属性名首字母大写才有效,无论属性名定义的时候首字母是大写还是小写。即:
- (NSNumber *)getFirstNum(); √,可以正常重载
- (NSNumber *)getfirstNum(); ×,重载失败,不会被调用
三、属性与_property变量,self.property 与 _property的使用
既然声明属性之后,编译器会帮我们生成_property变量,那么如果我们已经定义了名为_property的变量怎么办?在使用的时候,我调用self.property和调用_property有什么区别?
1,定义属性之后,是否再定义同名变量没有区别。
如果定义同名变量,编译器会报错,这点毋庸置疑:
定义属性之后,虽然默认会为我们生成带下划线的同名变量,但是如果我们自己定义过了,编译器不会画蛇添足的,或者理解为我们把系统生成的变量"重载"了(当然,变量没有重载这个东西)。因此我们不用担心同时定义变量和属性导致error的问题,但是我们应当注意变量可能被同名属性覆盖的问题。
2,建议读取的时候采用直接访问(_property)的方法,存储的时候采用点语法(self.property)。
这个建议是纯粹的个人的建议,目前仍然有激烈的讨论,如果理解了各种建议的原理之后,使用者根据自己的分析来使用会比计较合理,而不应该被建议束缚。
首先区别下点语法和直接访问的区别:
使用点语法(self.property)会根据情况调用getter/setter方法,使用_property则是直接从内存中把变量拿出来用,所以是直接访问。具体区别如下:
1)点语法要经过OC的“方法派发”(method dispatch),而直接访问直接访问内存获取变量,所以直接访问要快些。
2)点语法调用getter/setter函数,直接访问则绕过了这点。即使在我们没有重写getter/setter函数的情况下,如果设置属性为copy,使用点语法会把对象复制一份,然后在调用,而使用直接访问则很可能直接将原值更改(保留新值,释放旧值)。
3) 使用直接访问不会触发“键值观测”(Key-Value Observing, KVO)通知。至于会不会出问题,那只有你知道了~
4) 使用点语法的时候,你可以在getter/setter函数里面打断点,看看有没有问题,便于错误排查。
可以看出来,直接访问比较快,但是没有点语法安全。因此使用折中的方案比较合适,即:写入实例变量时,通过点语法来做;读取实例变量时,通过直接访问。折中方案是比较安全的,因为我们在存的时候可能会应用到一些规则,保证覆盖、拷贝之类的问题不会出差错;而读取的时候只要拿到对象就好了,也不会去对其操作(如果是读取之后直接对其更改的操作会很危险)。
综上,建议:读取的时候直接访问,存储的时候用点语法。存取逻辑复杂的地方,根据需要自行选择。
3,外部访问尽量用点语法,内部调用尽量直接访问
首先,这点和上点并不冲突,只是从不同维度提供使用建议。
外部访问的时候,不希望外部分析内部的实现来判断是否可以直接访问,因此统一使用点语法比较合理(当然,如果你只设置属性的话,并不会暴露_property变量的)。内部调用的时候,默认用户会了解本身逻辑,因此完全可以把握正确的直接访问,而这样效率更高,因此建议内部直接访问。