Category的源码分析

一、分类的用途

项目中的应用

1.分解体积大的类文件,按功能区分
2.声明私有方法
3.framework库中的方法公开

二、分类可以实现的内容

1.扩展类方法
2.扩展对象方法
3.添加协议
4.添加属性(添加getter、setter,不能添加成员变量)

思考:分类与类的关系,分类中为什么不能添加成员变量?

三、分类的底层结构

添加的分类会经过两个阶段
1.编译阶段:分类是编译器特性,在编译的时候,会将分类变成category_t结构体,所有的方法,属性,协议等都存放在这里
2.运行时阶段:当程序运行的时候,通过runtime,将category_t中的内容合并到类中

//编译阶段,将分类编译 成category_t类型的数据
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);
};

四、合并过程

合并过程发成在运行时阶段,app运行的时候首先执行的方法并不是main函数,在此之前dyld(动态链接器,操作系统级别的组件)会调用_objc_init()方法,其中进行runtime的初始化

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();
// images 是镜像、模块
// 注册关于加载images的通知回调,参数都是方法
// map_images  OC image被加载映射到内存 (包含category合并过程)
// load_images OC 加载镜像、模块的时候调用+load()方法在此时被调用
// unmap_image OC image被移除内存时
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

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);
}
继续调用
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ......
  // 内部会开始读取内容,类信息、分类信息等
// totalClasses 项目中所有的类
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    ......
}
_read_images内部搜索类、属性、协议、分类信息
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
......
// 获取所有的类
for (EACH_HEADER) {
        classref_t *classlist = _getObjc2ClassList(hi, &count);  
      .....
}
 //获取协议
......
// 获取分类
    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
            Class cls = remapClass(cat->cls);
            ......
// remethodizeClass方法中重新组织类
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());//重新组织类
                }
        ......
}

remethodizeClass 这个方法中会将类和分类信息合并再一起

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta; // 是否是元类

    runtimeLock.assertLocked();

    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)" : "");
        }
        //合并
        // cls 类对象或者是元类对象
        // cats 分类列表
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

//attachCategories 方法
 // cls 类对象或者是元类对象
// cats 分类列表 [category_t,category_t,category_t,...]
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // 分配内存空间
// 方法列表
    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));

    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
 // 循环 i-- 最后参与编译的分类放到第一位依次类推
    while (i--) {
        // 获取某一个分类
        auto& entry = cats->list[i];
        // 取出方法列表---一维数组,如果是元类去对象方法,如果不是,取实例方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            
// method_list_t **mlists 是总的方法列表二维数组[[method_t,method_t],[method_t,method_t]]
            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(); // 获取对象的class_rw_t 开始附加 方法、属性、协议
    // class_rw_t 是对象中可读可写的
    // 调用的data()方法中是将bits &一个掩码,获取rw结构体的地址

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    // mlist 是所有分类中的方法 一维数组
// rw->methods 取出rw中的方法列表,然后附加所有分类的方法列表mlists
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
// rw-> properties.取出所有的属性
// 然后附加分类中的属性
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
// rw->protocols取出所有的协议,然后附加分类中的协议
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

attachLists 方法是将类中的属性列表、协议列表、方法列表和分类中的属性、协议、方法进行合并为 一个列表存放到rw中
以方法列表为例

// addedList 是外面传入的mlist  addedCount 是传入的mlist的元素个数
void attachLists(List* const * addedLists, uint32_t addedCount) {
        
        //mlist 是所有分类中的方法 一维数组
        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]));
            // 分类中的方法地址,移动到前面   
           // addedLists 的顺序是后编译的再最前面
            // 编译顺序决定了,分类中的方法列表中的顺序,最后编译的会放到最前面
            // 分类中的方法永远都覆盖 原有类的方法,优先调用分类中的方法
// 将addedLists整个插入到新的列表中,所以后编译<<<先编译的顺序不变
            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]));
        }
    }

分类的合并是发生在运行时初始化的时候,进行的。编译的顺序决定了,分类在合并中的位置,即优先调用后面编译的分类中的内容

五、类扩展Extension

1.类扩展,存放在.m文件中,他在程序编译的时候,就已经合并到类中了
2.类扩展声明的都是私有的属性和方法
3.类扩展增加成员变量,声明属性会自动实现getter、setter方法

六、总结

1.分类中有属性、方法、协议,没有成员变量
2.分类的数据合并发生在运行时初始化的时候
3.后编译的分类数据存放在最前面,优先调用
4.类扩展在编译阶段 就已经合并到类中了,并且属性和方法都是私有的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容