OC底层原理19-类和分类搭配加载

引入

OC底层原理18-分类的加载 中,我们探究了分类的加载时机,得出分类和类在是否实现load方法,即是否是懒加载类/分类,类中的方法加载时机不同,这篇我们再来研究一下类和分类中load方法搭配使用的时候,方法的加载时机,属性、协议、变量做一个总结,不做板书,有兴趣可以自己研究。

类和分类搭配加载

搭配情况 分类
情况1 非懒加载类(实现load) 非懒加载分类(实现load)
情况2 非懒加载类(实现load) 懒加载分类(不实现load)
情况3 懒加载类(不实现load) 非懒加载分类(实现load)
情况4 懒加载类(不实现load) 懒加载分类(不实现load)
一、非懒加载类和非懒加载分类

主类实现load方法,分类实现load方法

.main中不对类做任何初始化,依然用分类加载中的工程,运行打印输出流程

image.png

  • 从打印流程可以看出,类和分类中的方法在Main函数之前就完成了加载,即编译期就完成了加载。

methodizeClass方法中,我们找到了从ro中取出方法,存到

image.png

在这里打断点,往下走一步,打印list,验证方法是否从ro取出来了
image.png

  • 发现第一次进入methodizeClass方法,只取了主类GomoPerson里面的方法,然后调用prepareMethodLists,对方法根据sel的地址进行排序,然后存入中,在prepareMethodLists中已实现,可自行查看。

接着又调用了attachCategories添加分类,会先取出中的方法调用attachLists进行处理,然后又会对触发attachCategories分类GomuPerons+GomuB中的方法attachLists进行处理

接着GomuPerons+GomuA再次触发attachCategories进行attachLists处理

attachLists的源码如下

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;
        
        if (hasArray()) {
//:-- 第三次添加GomuPerson+GomuA的方法/属性/协议,
//:-- 此时list是一个二维数组,
//:-- 经过此处,GomuPerson+GomuA的方法列表被加到了list[0],
//:-- GomuPerson+GomuB的方法列表被移到了list[1],
//:-- GomuPerson的方法列表被移到了list[2]
            // 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) {
//:-- 第一次添加GomuPerson的方法/属性/协议,
//:-- 此时list是一个一维数组
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
//: -- 第二次添加GomuPerson+GomuB的方法/属性/协议,
//:-- 此时list是一个二维数组,
//:-- 经过此处,GomuPerson+GomuB的方法被加到了list[0],
//:-- GomuPerson的方法被移到了list[1]
            // 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]));
        }
    }
  • 分类方法也会被prepareMethodLists排序
  • 经过attachLists处理,最后加载的分类,处于list列表第一个位置,这就是为什么分类和类相同方法,会调用分类中的方法多个分类则会调用最后编译的分类中的方法
  • 多个分类相同方法的调用取决于该分类被编译的先后,如下图
    image.png
  • 这里文件顺序的越上面表示越先被编译。
  • 该图这样的顺序则会优先调用GomuPerson+GomuA中的同名方法
  • 如果把GomuPerson+GomuB拖到下面,则会优先调用GomuPerson+GomuB中的同名方法

总结 :

  1. 非懒加载类和非懒加载分类情况下,方法的加载会提前到编译期完成,在load_image的时候加载完成。
  2. 如果有类的分类,类中的list会变成一个二维或多维数组。越后编译的分类越插入到list最前面,这也是分类同名方法哪个优先调用的原因。
二、非懒加载类和懒加载分类

主类实现load方法,分类不实现load方法

打印流程


image.png
  • 发现只是进行了类的加载,没有动态添加分类

methodizeClass中下如下断点

image.png

打印list
image.png

  • 发现从data()读取出来的数据中,就已经加载好了类和分类中的所有方法

过一下断点,等prepareMethodLists执行之后再打印list

image.png

  • 发现prepareMethodLists方法对list进行了排序,把同名方法按顺序排在前面,其他方法依旧按照sel地址进行排序

总结:
非懒加载类和懒加载分类的情况下,方法的加载也在编译器完成,不同的是在data()中就已经完成,methodizeClass中只是进行重新排序。

三、懒加载类和非懒加载分类

主类不实现load方法,分类实现load方法

打印流程


image.png
  • 发现调用了methodizeClass,这个只有在主类中有load的情况才会调用,但是现在主类中没有实现load依然就调用了,说明非懒加载分类会迫使懒加载类非懒加载类的形式来提前加载数据

methodizeClass中下如下断点

image.png

打印list
image.png

  • 发现这种情况下,在methodizeClass中打印data(),只有主类的方法

等经过attachCategories方法后,在attachLists中打下个断点,查看方法何时存到类中

image.png

  • 第一次还是和第一种情况非懒加载类和非懒加载分类步骤类似,把主类的方法先加入list,自行打印list查看
  • 不同的是,这种情况下,GomuPerson+GomuAGomuPerson+GomuB中的方法被一起加到了list中,如下图打印
    image.png

attachLists最后一个条件中,即1维变2维的方法中,只是进行了memcpy内存拷贝,把GomuPerson移到了list的最后面,然后把addedLists加到了list前面,addedLists[0]中存的是GomuPerson+GomuB,所以调用gomu_instanceMethod1方法,会执行分类GomuBgomu_instanceMethod1的方法

总结:
懒加载类和非懒加载分类情况下,编译期会迫使主类加载,这种情况下也是在编译期就完成了类和分类的加载,但是在load_image之前

四、懒加载类和懒加载分类

主类不实现load方法,分类不实现load方法

打印流程


image.png
  • 懒加载类和懒加载分类的情况下,类和分类都不会提前在编译期加载。

main函数中调用一次方法,即objc_msgSend一次,查看打印

image.png

  • 类和分类的加载被推迟到了main函数之后,之后调用顺序和第二种情况非懒加载类和懒加载分类一模一样,也是在data()中就完成了类和分类的方法加载

总结:
懒加载类和懒加载分类的情况下,类和分类的加载推迟到了消息第一次调用,在data()中就完成了`方法的加载

二、推荐使用

由上面分析可得出,我们的类中应该减少load方法的使用,不仅可以节约资源,还能减少编译时间,切记切记,慎用load方法

三、属性、协议、变量的加载时机

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

推荐阅读更多精彩内容