iOS底层原理之七:category的实现

题记

作为iOS开发者,对category肯定不会陌生,category一般又叫分类,当我们需要为一个类增加额外的方法属性等时,分类便是我们的首选。根据前文可知,我们对象方法是存放在类中,类方法是存放在元类对象中,那么如果我们在category增加了方法属性等,它们又是怎么存放的?苹果内部是怎么实现的呢?


准备工作

老规矩我们准备一个继承自NSObject的JJPerson类,然后为它增加一个分类JJPerson+run,增加一个age属性,一个run对象方法以及一个eat类方法。


为了方便我们去了解它的底层实现,我们可以先把它的.m文件转成c++,并拽入Xcode方便查看,为了避免不必要的报错,我们不让这个.cpp文件参与编译,前文有详细操作步骤

准备工作到此完毕,下面我们可以进入分析阶段

编译文件分析


  • 底层结构
    我们搜索“_category_t”可以看到,一个分类在编译后转成C++文件,就是以上图这种结构体的方式存在。我们每增加一个分类,编译后就会多一个这样的结构体,然后在运行时阶段,把所有分类的结构体全部动态合并到我们JJPerson类里面去。
  • 结构体分析
    我们看到结构体包含下面几种常见数据类型
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;

(1)instance_methods是我们为分类增加的实例方法列表
(2)class_methods是我们为分类增加的类方法列表
(3)protocols是分类遵守的协议列表
(4)properties则是我们增加的属性列表
(5)对于结构体前面的name和class,我们可以猜测这个是类名和分类的名称相关的内容

  • 我们可以看到最下面红色框内就是编译后的结构体数据,也就是我们所说“ _category_t”的结构,箭头两个参数对应着实例方法和类方法,而且这两个方法编译后也是以结构体的形式存在在上面。最后两个参数为0,是因为我们的分类没有遵守协议也没有添加属性。
  • 我们每增加一个分类,编译后就会增加一个这样的结构体,格式一样,但是命名和参数以各自为准。
  • 在运行时阶段,才会真正的把结构体里面的数据合并到我们的类里面去,也就是前面文章提到的,类方法存放在元类对象里,属性,协议,实例方法等存放在类对象里

源码分析


  • 我们尝试从运行时源码去分析,看看苹果是怎么对分类进行处理


(1)首先我们打开下载好的objc源码,专题文章第一篇有介绍,这里不再赘述
(2)选择 objc-os.mm 文件,这里是运行时方法的入口
(3)搜索 _objc_init 方法,点击 &map_images 参数


(4)再点击 map_images_nolock 方法进入

(5)注释提到,hCount 是用来查找所有oc元数据模块的,那我们可以专门留意一下它

(6)通过搜索 hCount ,我们可以看到一个叫 _read_images(加载模块) 的方法里面有个参数是传入 totalClasses(所有类),我们可以点击进入作进一步查看

(7)然后我们往下拉可以看到一个关键注释,Discover categories,很明显接下来部分就是对找到的分类进行处理。尤其红色框框内我们还能看到熟悉的 category_t 类型,甚至还能看到方法名是叫做 _getObjc2CategoryList(获取分类列表),那么我们接下来就重点看这里的处理

(8)首先我们可以看到这里有两个是对类重新方法化的处理,分别把class和isa传了进去,这就符合了我们前面提到的,把类方法添加到元类对象的方法列表,把实例方法等添加到类对象的方法列表。所以我们需要看看这个方法的内部实现

(9)我们又看到一个名为附加协议的方法 attachCategories,再次点进去,我们就能看到真正的实现

(10)为了方便阅读,我在这里加了一些中文注释。这里会有一个参数 isMeta 记录传进来的是否是元类对象。然后调用malloc函数开辟存储空间创建一个方法列表的二维数组(属性列表和协议列表同理),然后用 while 循环,根据是否为元类对象,取出对应的类方法或者实例方法,然后把方法数组添加到前面创建的二维数组中(属性和协议处理方法同理)。

(11)最后获取类数据,把分类中所有的方法,属性,协议全部添加到类中去。我们可以再进一步看看 attachLists 这个方法里面做了什么,因为这里将是内存分配最重要一环

(12)我们可以看到,在这个方法里重新计算了列表新的长度,并且调用 realloc 重新分配了新的内存空间。接下来调用 memmove 方法,把原来的方法列表向后移动,前面留出了新列表的长度,再调用 memcpy 方法,把新方法列表插入到整个列表最前面。

(13)这一步内存移动的体现是,如果我们在分类中添加和原类中同名的方法时,我们调用该方法时会优先调用分类的方法,究其原因就是我们分类的方法在方法列表中重新插入在最前面,所以会优先调用,这也是我们常说的分类方法会覆盖原方法的原因。


总结


  • 每创建一个分类,编译时就会生成一个 _category_t 的结构体,里面包含着分类的所有信息(方法,属性,协议)
  • 在运行时阶段,分类的所有信息(方法,属性,协议)会被分别读取,并且逐一添加到类里面去
  • 由于内存操作的原因,分类的方法会排在方法列表最前面,所以分类方法会优先于原类方法的调用(所谓的分类方法覆盖,本质是排序超前)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345