iOS 开发 Objective-C

iOS 底层 day06 Category-01

2020-08-27  本文已影响0人  望穿秋水小作坊

Category 当我们对一个类的功能进行按模块进行拆分的时候,就会用到Category

一、探究 Category 的底层结构

1. 通过生成 cpp 文件来观察 Category 的底层结构
// .h文件
#import "Person.h"
@interface Person (Walk)<NSCoding, NSCopying>
@property(nonatomic, assign) int walkSpeed;
- (void)walk;
@end

// .m文件
#import "Person+Walk.h"
@implementation Person (Walk)
- (void)walk {
    NSLog(@"Person walk");
}
@end
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;
};
static struct _category_t _OBJC_$_CATEGORY_Person_$_Walk __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Walk,
    0,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Walk,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Walk,
};
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Walk __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"walk", "v16@0:8", (void *)_I_Person_Walk_walk}}
};

二、Category 的源码分析

1. 先说几个结论,我们带着结论去阅读源码,才有目的性
2. 开始分析 objc4-723 源码
/***********************************************************************
* remethodizeClass
* Class重新方法化
* Attach outstanding categories to an existing class.
* 将未处理的 categories 附加到现存的 class 中
* Fixes up cls's method list, protocol list, and property list.
* 修正 cls 的方法列表、协议列表、属性列表
* Updates method caches for cls and its subclasses.
* 更新 cls 的和它子类的 方法缓存
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();

    // 元类判断
    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    // 找出所有未处理的 categories ,放入 cats 中
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        // 将 categories 进行归属
        attachCategories(cls, cats, true /*flush caches*/);        
        // 释放 cats
        free(cats);
    }
}
// Attach method lists and properties and protocols from categories to a class.
// 将 categories 中的方法列表、属性列表和协议列表链接到 class 上
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
// 假设cats中的所有categories都已加载,并且按照加载顺序排序,最早的categories 放在前。
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    // 给三个二维数组分配内存(方法列表数组、协议列表数组、属性列表数组)
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    // i-- ,就是倒叙遍历cats,最后加载的会放到最前面(这也是为什么后加载的分类,调用方法的时候优先级高的原因之一)
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    // 在这里对 rw 的methods进行链接了
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}
// addedLists 是categories方法列表数组,是二维数组
// addedCount 是二维数组的长度
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            // 原来的方法列表list长度
            uint32_t oldCount = array()->count;
            // 原来的方法列表list个数 + 新增list个数
            uint32_t newCount = oldCount + addedCount;
            // 对原来数组进行扩容
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            // 将原来方法list的位置,利用 memmove 向后移动 addedCount 个位置
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            // 讲新的分类方法list,利用 memcpy 拷贝到 上一步 挪开的来位置(这一步是为什么分类的方法优先级高于原类的原因)
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
}

三、至此,我们可以轻松解决如下问题

1. Category的使用场景是什么?
2. Category的实现原理是什么?
3. Category 和 Class Extension 的区别是什么?
上一篇 下一篇

猜你喜欢

热点阅读