iOS Category源码分析

什么是Category?

CategoryObjective-C 2.0以后添加的语言特性,它的主要做用是为已经存在的类添加方法,通常称为分类。 CategoryiOS开发中使用很是的频繁,特别是在为系统类进行拓展的时候,咱们能够不用继承系统类,直接给系统类添加方法,最大程度的体现了Objective-C的动态语言特性。数组

Category结构体

新建了一个工程,建立了一个实体类,和一个category分类 bash

cmd+B编译以后打开终端,cd到工程的目录,执行 clang -rewrite-objc Test+categroy.m命令
同个目录下会生成 Test+categroy.cpp文件
打开以后搜索 _category_t,看到编译出来的category结构体
查看源码中的objc_runtime_new.h文件就能够看到Category的内部结构以下

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; // 属性
    // 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);
};
复制代码

能够看出,在源码中Category的底层结构category_t和上文中咱们获得的_category_t结构体基本一致,在编译以后,每一个Category确实会生成一个category_t类型的结构体。并且category_t类型的结构体中是没有ivars这项,这就是分类是不能添加成员变量的缘由。数据结构

Category加载流程

dyld加载

  • dyld是苹果的动态加载器,用来加载imageimage指的是Mach-O格式的二进制文件,不是图片)
  • 当程序启动时,系统内核首先会加载dyld,而dyld会将咱们APP所依赖的各类库加载到内存中,其中就包括libobjc库(OCruntime),这些工做,是在APPmain函数执行以前完成的
  • _objc_initObject-C runtime 的入口函数,在这里主要是读取Mach-O文件OC对应的Segment section,并根据其中的数据代码信息,完成为OC的内存布局,以及初始化runtime相关的数据结构。

_objc_init函数

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
复制代码
  • 调用一些init函数以后,重点在_dyld_objc_notify_register这一句代码,这里注册了三个函数
  • &map_imagesimage加载进内存
  • load_imagesdyld初始化加载image方法
  • unmap_images移除内存 下面探究map_images这个函数
void map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
复制代码
  • map_images_nolock函数是用来执行全部类注册和修复操做,并调用+load方法
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ...
    //前面的代码省略,主要代码是_read_images这个函数
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
}
复制代码
  • _read_images函数中有多个段落,咱们研究的重点在于分类
// Discover categories. 
    for (EACH_HEADER) {
        // 获取项目中全部的Category,获得一个二维数组
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();
        // 遍历二维数组,获得每个category_t类型的结构体变量
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            // 经过cat->cls拿到当前Category所属的类
            Class cls = remapClass(cat->cls);
            
            if (!cls) {
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }
            // 判断class是否存在
            bool classExists = NO;
            // 若是Category中存在实例方法,协议或者是实例属性
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                // 将cls的未合并的全部Category存放到以cls为key的一个映射表中去
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    // 从新组织实例方法,将Category中的方法、属性、协议等等附加到cls的实例方法列表、实例属性列表和协议列表中去
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }
            // 若是Category中存在类方法,协议或者是类属性
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                // 从新组织元类方法,将Category中的类方法、类属性、协议等等附加到cls的元类的类方法列表、类属性列表和协议列表中去
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
复制代码
  • 进入remethodizeClass函数,重点函数在attachCategories
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函数
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
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        // 取出其中一个分类
        auto& entry = cats->list[i];
        // 取出分类中的方法存放到二维数组 mlists 中,isMeta决定是类方法仍是实例方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        // 取出分类中的属性存放到二维数组 proplists 中
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
        // 取出分类中的协议存放到二维数组 protolists 中
        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这个函数主要是关联原类方法的
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]));
        }
复制代码
  • 能够看出,分类的方法附加的类对象中的时候,只是把分类的方法放到原来的方法前面,这样runtime查找方法的时候就会先调用分类的方法

参考文章

手把手带你探索Category底层原理
CATEGORY实现的原理一:底层结构及源码分析函数

相关文章
相关标签/搜索