【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变量的)。内部调用的时候,默认用户会了解本身逻辑,因此完全可以把握正确的直接访问,而这样效率更高,因此建议内部直接访问。

 

posted @ 2016-04-11 12:22  heiline  阅读(1237)  评论(0编辑  收藏  举报