Category 底层分析

栏目: Objective-C · 发布时间: 6年前

内容简介:一、Category 浅层分析参照上图,思考关于分类的问题。分类有没有可能同上图的 class 或 meta-class 处于并列关系,每创建一个分类分别对应着一个 分类class 和 分类meta-class 对象?
  • 一、Category 浅层分析

  • 二、Category 底层结构

  • 三、Category 源码分析(分类方法优先调用)

  • 四、小结

一、Category 浅层分析

Category 底层分析

参照上图,思考关于分类的问题。分类有没有可能同上图的 class 或 meta-class 处于并列关系,每创建一个分类分别对应着一个 分类class 和 分类meta-class 对象?

这里先说出结论,在第二小节再做验证。实际情况并非如此,为了充分利用资源,一个类永远只存在一个类对象。Category 中的对象方法会合并到类(class)的对象方法列表中,类方法合并到元类(meta-class)的类方法列表中。方法合并的时机在运行时进行,而非编译期间。

二、Category 底层结构

@implementation Person
- (void)run{
    NSLog(@"run");
}
@end
@implementation Person (Test)
- (void)test{
    NSLog(@"test");
}
+ (void)test{
    
}
@end
@implementation Person (Eat)
- (void)eat{
    NSLog(@"eat");
}
+ (void)eat{
  
}
@end

为验证上述问题,可以编写如上代码,即创建 Person 以及 Person + Test 和 Person + Eat 分类。然后借助xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件 命令将 Person + Test类转为 C/C++ 代码。

生成的 .cpp 文件中包含  _category_t 结构体,该结构体即为分类底层数据结构,主要包含类名、对象方法、类方法、协议以及属性。

struct _category_t {
    const char *name;//类名,这里是Person
    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;//属性列表
};

生成的.cpp 文件中还存在下下面一段代码,该段代码与 _category_t结构体对应,依次给_category_t 结构体内部成员赋值。其中第二、五、六三个参数均为 0。

static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,
    0,
    0,
};

从上述验证可以看出,编译期间过后,每个分类唯一对应一个 _category_t 结构,与原有类是分开的。

三、Category 源码分析(分类方法优先调用)

为进一步理解 Category ,可以查看在该网站 查看 objc4-723 runtime 底层源码 。可以顺着如下顺序阅读源码,其中 objc-os.mm 文件为运行时的入口文件。

objc-os.mm ---> _objc_init ---> map_images ---> map_images_nolock --->objc-runtime-new.mm --->_read_images --->remethodizeClass --->attachCategories --->attachLists

在_read_images方法中可以发现这样一段代码,代码上方注释为 Discover categories , 另外还有个二维数组category_t ** catlist, catlist 数组中元素为结构体,存储着一堆 category_t 结构。remethodizeClass 方法被调用两次,从命名来看意思为:重新方法化,两次传入参数分别为 cls 和 cls->ISA, 即类对象的元类对象。

 // Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[I];
            Class cls = remapClass(cat->cls);
            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }
            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

remethodizeClass 方法内部调用了 attachCategories 方法,attachCategories 方法前两个参数分别为 class 和 分类数组cats。

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;
    runtimeLock.assertWriting();
    isMeta = cls->isMetaClass();
    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

attachCategories 方法内部创建了三个二维数组method_list_t **mlists,property_list_t **proplists,protocol_list_t **protolists, 分别用于保存方法、属性和协议。mlists[mcount++] = mlist; 表示取出每个分类中的方法列表放入到二维数组中;auto rw = cls->data(); 表示取出类对象中的数据;rw-

>methods.attachLists(mlists, mcount);表示将所有分类的对象方法附加到类对象方法列表中。
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_t, method_t],[method_t, method_t]]
    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
    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.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);
}

attachLists 方法中调用memmove方法将类中的原有方法方法放到数组末尾,调用memcpy方法将二维数组 addedLists 中的每个 Category 的方法列表放置到类中原有方法的前面。

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;
        
        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

四、小结

经过上述源码分析,最终方法在内存中的布局结构如下。该布局很好说明了分类方法调用顺序要优于原有类方法。因为调用方法时,会顺序遍历二维数组查找方法,当查找到目标方法后就无需再向后继续遍历查找方法。

作者:ZhengYaWei

链接:https://www.jianshu.com/p/bdf200c28752


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

千夫所指

千夫所指

乔恩·罗森 / 王岑卉 / 九州出版社 / 2016-10-1 / CNY 42.80

编辑推荐: 《乌合之众》是为了跪舔权贵?《普通心理学》实验存在重大漏洞?《引爆点》的理论都是瞎掰的?社交网络时代《1984》预言的“老大哥”是否已经变成事实? 《纽约时报》年度十佳书 《GQ》杂志年度十佳书 《卫报》年度十佳书 《泰晤士报》年度十佳书 《经济学人》年度重推! 黑天鹅年度重点图书! 《乌合之众》是为了迎合权贵?《普通心理学》实验存在重大......一起来看看 《千夫所指》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具