ios Category
Category简介
category是Objective-C 2.0之后添加的语言特性,category的主要作用是为已经存在的类添加方法。除此之外,apple还推荐了category的另外两个使用场景1
- 可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处,
a)可以减少单个文件的体积
b)可以把不同的功能组织到不同的category里
c)可以由多个开发者共同完成一个类
d)可以按需加载想要的category 等等。 - 声明私有方法
category的其他几个使用场景:
- 模拟多继承
- 把framework的私有方法公开
Category真面目
typedef struct category_t {
const char *name;//类的名字 主类名字
classref_t cls;//类
struct method_list_t *instanceMethods;//实例方法的列表
struct method_list_t *classMethods;//类方法的列表
struct protocol_list_t *protocols;//所有协议的列表
struct property_list_t *instanceProperties;//添加的所有属性
} category_t;
从category的定义也可以看出category可以添加实例方法,类方法,甚至可以实现协议,添加属性。无法添加实例变量。
过程:
1.在编译时期,会将分类中实现的方法生成一个结构体 method_list_t 、将声明的属性生成一个结构体 property_list_t ,然后通过这些结构体生成一个结构体 category_t 。
2.在运行时期,Runtime 会拿到编译时期我们保存下来的结构体 category_t
3.然后将结构体 category_t 中的实例方法列表、协议列表、属性列表添加到主类中
4.将结构体 category_t 中的类方法列表、协议列表添加到主类的 metaClass 中
需要注意的有两点:
1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休,殊不知后面可能还有一样名字的方法。
Category 为什么不能添加实例变量
通过结构体 category_t ,我们就可以知道,在 Category 中我们可以增加实例方法、类方法、协议、属性。这里没有 objc_ivar_list 结构体,代表我们不可以在分类中添加实例变量。
因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这个就是 Category 中不能添加实例变量的根本原因。
为什么使用Runtime又可以添加属性?
使用Runtime技术中的关联对象可以为类别添加属性。
先看AssociationsManager代码如下:
class AssociationsManager {
static OSSpinLock _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { OSSpinLockLock(&_lock); }
~AssociationsManager() { OSSpinLockUnlock(&_lock); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
其原因是:关联对象都由AssociationsManager管理,AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址,而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的key/value对。
Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
Category中有load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方法
void call_load_methods(void) {
// 是否已经录入
static bool loading = NO;
// 是否有关联的 Category
bool more_categories;
loadMethodLock.assertLocked();
// 由于 loading 是全局静态布尔量,如果已经录入方法则直接退出
if (loading) return;
loading = YES;
// 声明一个 autoreleasePool 对象
// 使用 push 操作其目的是为了创建一个新的 autoreleasePool 对象
void *pool = objc_autoreleasePoolPush();
do {
// 重复调用 load 方法,直到没有
while (loadable_classes_used > 0) {
call_class_loads(); // 首先调用类的load方法
}
// 调用 Category 中的 load 方法
more_categories = call_category_loads();
// 继续调用,直到所有 Class 全部完成
} while (loadable_classes_used > 0 || more_categories);
// 将创建的 autoreleasePool 对象释放
objc_autoreleasePoolPop(pool);
// 更改全局标记,表示已经录入
loading = NO;
}