ios Category

2018-09-04  本文已影响9人  赵哥窟
Category简介

category是Objective-C 2.0之后添加的语言特性,category的主要作用是为已经存在的类添加方法。除此之外,apple还推荐了category的另外两个使用场景1

category的其他几个使用场景:

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;
}
上一篇下一篇

猜你喜欢

热点阅读