面试宝典

Category为何不能添加成员变量?

2017-03-26  本文已影响745人  开发者老岳

关于Category的原理,参考这篇文章:http://tech.meituan.com/DiveIntoCategory.html

Category的使用场景

Category到底是不能添加成员变量,还是属性?还是都不能添加?

属性和成员变量的区别,就不介绍了。
首先,Category在代码里添加成员变量是根本编译不过去的,而添加属性是可以编译通过的,如图:


FFA05734-5479-4D2F-AC71-78C86C681EC7.png

但是,在Category添加的属性后,只会声明setter和getter方法,.m文件并未实现setter和getter方法,会有黄色预警,如图:


4F9D5BD7-FB97-49BE-9B35-7FC274A4D062.png

而且,如果调用的话,也不会被赋值,如图:


035F002C-E200-4F7D-BDF1-EFB56C117583.png

最常见的解决方案就是用runtime手动实现setter和getter方法,这里就不介绍了。
所以,刚才那个问题,Category不能添加成员变量和属性?就可以有答案了,不能添加成员变量,可以添加属性,但是属性要手动实现setter和getter方法。

Category为何不能添加成员变量,而只能添加方法?

这要从Category的原理说起,简单地说就是通过runtime动态地把Category中的方法等添加到类中(苹果在实现的过程中并未将属性添加到类中,所以属性仅仅是声明了setter和getter方法,而并未实现),具体请参考http://tech.meituan.com/DiveIntoCategory.html
Objective-C提供的runtime函数中,确实有一个lass_addIvar()
函数用于给类添加成员变量,但是文档中特别说明:

This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.

意思是说,这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就被runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用objc_registerClassPair
之后才可以被使用,同样没有机会再添加成员变量。
我们设想一下如果Objective-C允许动态增加成员变量,会发生什么事情。假设如下代码可以执行。

E6AD8A2E-77C0-4186-AFC0-26C8451F6571.png
MyObject *obj = [[MyObject alloc] init];
// 基类增加一个4字节的成员变量someVar
class_addIvar([NSObject class], "someVar", 4, ...);
// 基类增加方法someMethod,用到了someVar
class_addMethod([NSObject class], @selector(someMethod), ...);
// 调用someMethod,修改了someVar
[obj someMethod];
// 访问子类成员变量,会发生什么?
[obj->students length];

显然,这样做会带来严重问题,为基类动态增加成员变量会导致所有已创建出的子类实例都无法使用,比如上线后的app,如果用户手机系统升级iOS新版本后,必须重新编译提交才能在新版系统上运行。那为什么runtime允许动态添加方法和属性,而不会引发问题呢?

因为方法和属性并不“属于”类实例,而成员变量“属于”类实例。我们所说的“类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。所以假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在objc_class中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。

需要注意的有两点:

参考:http://quotation.github.io/objc/2015/05/21/objc-runtime-ivar-access.html

上一篇 下一篇

猜你喜欢

热点阅读