iOS 底层原理03: Category, 关联对象

465 阅读6分钟

一、Category 详解

编译后的Category
  1. _category_t 结构体
struct _category_t {
    const char *name;   // 主类名
    struct _class_t *cls; // 具体类
    const struct _method_list_t *instance_methods; // 实例方法列表
    const struct _method_list_t *class_methods; // 类方法列表
    const struct _protocol_list_t *protocols; // 协议列表
    const struct _prop_list_t *properties; // 属性列表
};
  1. category的构造方法
static struct _category_t _OBJC_$_CATEGORY_NSString_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
        "NSString",  // name
        0, // &OBJC_CLASS_$_NSString, // cls
        0, // instance_methods
        0, // class_methods
        (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_NSString_$_Test, // protocols
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NSString_$_Test, // properties
};
Category相关源码

1.category_t 结构体, 可以看到源码与编译后的差异不大

struct category_t {
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
    WrappedPtr<method_list_t, PtrauthStrip> classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;
    // 先拿当前分类的方法列表, 如果是类, 返回实例方法, 如果是元类, 返回类方法
    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    // 属性列表
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    // 协议列表
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};
  1. Category是如何加载的呢?

Category 是在runtime时动态加载到内存的, 核心代码如下:

  • attachCategories 函数主要进行所有分类的拼接
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{

    // ...其他代码
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    // 声明了64长度的方法数组, 属性数组, 协议数组 (都是二维数组)
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];
    
    // 每个数量都初始化为0
    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    
    /** class_rw_t -> extAllocIfNeeded */
    // rwe 其实就是 当前类的 class_rw_ext_t成员变量
    auto rwe = cls->data()->extAllocIfNeeded();

    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];

        // category_t -> methodsForMeta
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {  // 如果本类有方法列表, 将方法列表添加到 声明的 mlists 中
            if (mcount == ATTACH_BUFSIZ) { // 如果超过64个, 就修正mlist, 在进行拼接
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            // 这句表明了, 将原来的方法, 放在了mlists 靠后的位置, 
            // mlists是二维数组, 存放的直接是 mlist
            // 先遍历的list 被放在了mlists后边的位置, 也就是先编译的分类放在后边
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        // 同样处理proplist
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }
        // 同样处理protolist
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        // 准备 方法列表
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        // 将 拼接方法列表
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        // ...其他代码
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
  • attachLists 函数主要进行每个分类里方法拼接
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;
        
        if (hasArray()) {
            // many lists -> many lists
            // oldCount 是本类的方法数量
            uint32_t oldCount = array()->count;
            // newCount 是本来和分类数量的综合
            uint32_t newCount = oldCount + addedCount;
            // 开辟新的空间, 复制count为newCount
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;  
            array()->count = newCount;
       
            // 先倒序遍历newArray, 将本类的方法放入
            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];

            // 在正序遍历newArray, 将分类的方法放入
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            // 释放空间, 将newArray 赋值给自身
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // ... 其他代码
        } 
        else {
           // ... 其他代码
        }
    } 

经过拼接以后, 本类的方法数组的结构就会变成
[
[分类方法列表1],
[分类方法列表2],
.....
[本类方法列表]
]

所以, 才会有分类方法会 "覆盖" 本来方法的说话, 其实并不是覆盖, 只是在objc_msgSend()的时候, 从前往后先找到了分类的方法就直接返回了

二、可以给Category添加成员变量么?

这是一个经典的面试题. 通过源码, 我们可以很直观的看到, 分类的结构体 category_t 并没有 ivas 成员变量, 所以我们不可以直接给Category 添加成员变量

但是 category_t 中有 属性列表 properties, 那么也就是说可以给属性添加属性, 但是这跟本来的属性有什么区别呢?

本类中的属性, 编译器会自动帮我们生成setter和getter方法, 但是Category中的属性并不会, 所以当在Category我们声明了一个属性, 在给属性赋值时, 就会提示 unrecognized selector 给Category的属性赋值

所以, 这就需要我们自己手动给分类的属性添加setter和getter方法. 但是分类又不能添加成员变量, 那么我们的setter方法给谁赋值? getter 又返回谁?

三、关联对象技术

我们可以通过关联对象间接给一个Category添加一个类似成员变量的东西. 关联对象相关有三个主要的函数

/** 给一个对象设置关联对象
object: 需要添加关联的源对象
key: 关联值的唯一key
value: 关联的具体值
policy: 关联的策略 (类似声明properties的参数)
*/
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

/** 获取某个对象的某个关联对象
object: 需要获取关联的源对象
key: 关联值的唯一key
*/
id objc_getAssociatedObject(id object, const void *key)

/** 移除对象的关联对象
 object: 需要移除的源对象
*/
void objc_removeAssociatedObjects(id object) 

通过关联对象技术, 我们可以在setter方法中, 给 cName 赋值, 在getter方法中获取 cName 的值

使用关联对象技术

四、关联对象的实现原理

  1. 通过关联对象的源码, 可以看到关联对象的原理主要由4个部分组成
  • AssociationsManager: manager 中 管理一个 AssociationsHashMap
  • AssociationsHashMap: 这个map 是用 setXXX 传入的 Object 为基础, 进行一些其他操作以后作为 map 的Key, 用一个ObjectAssociationMap 作为 value
  • ObjectAssociationMap: 这个map 用 setXXX 传入的Key 作为key, 用 setXXX 传入的 value 和 policy 组成的 ObjcAssociation 作为 value

这样的设计思路, 在 AssociationsHashMap 中, 对于同一个 Object, 通过 Object作为Key 找到的 ObjectAssociationMap 中, 有他的所有关联对象 {Key: ObjcAssociation}, 而对于不同的Objcet, 通过Key 可以找到不同的 ObjectAssociationMap, 而所有的 AssociationsHashMap 由AssociationsManager 统一管理, 示意图如下: 关联对象实现逻辑

  1. 关联对象的相关源码
  • objc_setAssociatedObject() 函数
// objc_setAssociatedObject() 直接调用_object_set_associative_reference()函数
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

// 具体实现
void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // ...异常判断

    // 将object 进行一些处理, 生成Key
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // 根据 policy和value 生成 ObjcAssociation
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();

    bool isFirstAssociation = false;
    {   
        // 声明AssociationsManager
        AssociationsManager manager;
        // 从 manager 中拿到 AssociationsHashMap
        AssociationsHashMap &associations(manager.get());

        if (value) {  // 如果value不为nil, 是添加关联对象
            // associations中, 根据object处理后的key 获取 ObjectAssociationMap
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                isFirstAssociation = true;
            }
            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            // 在 ObjectAssociationMap 中 通过key 获取设置 association
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else { // 如果value为nil, 是删除关联对象
            // 获取ObjectAssociationMap
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                // 获取 association
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    // 擦除 association
                    refs.erase(it);
                    if (refs.size() == 0) {
                        // 如果也没有其他关联对象了, 擦除ObjectAssociationMap
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }

    /// ...   其他代码
}
  • objc_getAssociatedObject() 函数
// objc_getAssociatedObject() 直接调用_object_get_associative_reference()函数
id objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}

id _object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        // 获取 manager
        AssociationsManager manager;
        // 获取 AssociationsHashMap
        AssociationsHashMap &associations(manager.get());
        // associations中, 根据object处理后的key 获取 ObjectAssociationMap
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;
            // 通过key 获取 Association
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) { 
                // 如果存在 给association赋值
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }
    return association.autoreleaseReturnedValue();
}