属性和实例变量间的关系
-
“属性”(property)是 Objective-C 的一项特性,用于封装对象中的数据。 Objective-C 对象通常会把所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”来访问。其中,“获取方法”(getter)用于读取变量值,而“设置方法“(setter)用于写入变量值。开发者可以利用“属性”这一特性令编译器自动编写与属性相关的存取方法,此特性引入了一种新的“点语法”,使开发者可以更为容易地依照类对象来访问存放于其中的数据。
-
一个问题:如果代码中实例变量使用了编译期计算出的偏移量,那么修改类定义之后必须重新编译,否则就会出错。例如,某个代码库中的代码使用了一份旧的类定义,如果和其链接的代码使用了新的类定义,那么运行时就会出现不兼容现象。
Objective-C 的做法是:把实例变量当作一种存储偏移量所用的“特殊变量”,交由“类对象”(class object)保管。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也变了,这样的话,无论何时访问实例变量,总能使用正确的偏移量。 甚至可以在运行期向类中新增实例变量,这就是“稳固的”(notfragile)“应用程序二进制接口”(Application Binary Interface, ABI)。ABI定义了许多内容,其中一项就是生成代码时应遵循的规范。有了这种“稳固的”(nonfragile)的ABI,我们就可以在类扩展(Extensions / class-continuation)或实现文件中定义实例变量了。
所以说,不一定要在接口中把全部实例变量都声明好,可以将某些变量从接口的 public 区段(C++ 和 JAVA 中的写法)里移走,以便保护与类实现有关的内部信息。
关于
Non Fragile ivars这个特性的介绍可以看这篇文章:Objective-C类成员变量深度剖析 -
编译器会把“点语法”转换为对存取方法对调用,使用“点语法”对效果和直接调用存取方法之间没有丝毫差别。在赋值语句等号之前调用“点语法”相当于调用 setter 方法,而在其他地方调用“点语法”则相当于调用 getter 方法。
-
属性还有更多优势,如果使用了属性的话,那么编译器就会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,找个过程由编译器在编译期执行,所以编译器里看不到这些“合成方法”的源代码。
除了生成方法代码之外,编译器还要自动向类中添加适当的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。 也可以在类的实现代码里通过
@synthesize语法来指定实例变量的名字。 -
若不想令编译器自动合成存取方法,则可以自己实现。如果你只实现了其中一个存取方法,那么另外一个还是会由编译器来合成。
还有一种方法能阻止编译器自动合成存取方法,就是使用
@dynamic关键字,它会告诉编译器:不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。而且,在编译访问属性的代码时,即使编译器发现没有定义存取方法,也不会报错,它相信这些方法能在运行期找到。但如果当程序运行到相应代码段时,没有找到相应的存取方法,会导致程序崩溃。
属性 VS 实例变量的优缺点对比
1.在访问效率上的比较 ------实例变量完胜
概念示意图
2.在内存管理语义上的比较 ------属性完胜
概念示意图
3.KVO触发机制上的比较 ------- 属性略胜
直接访问实例变量,无法触发KVO机制,这一点需要根据具体业务来和对象具体的行为来决定。
概念示意图
4.在调试错误上的比较 ------ 属性胜
通过属性来访问可以来帮助排查与之相关的错误,因为我们有机会在set/get方法中增加断点,而实例变量无法做到。
根据具体场景来决定声明实例变量还是属性
- 在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应通过属性来写。
- 在初始化方法及
dealloc方法中,总是应该直接通过实例变量来读写数据。 - 使用懒加载初始化时,需要通过属性来读取数据。
参考博客:iOS巩基之 不再纠结实例变量&属性