Category 原理源码分析 load、initialize方法

本节将解释下一下问题:

1.Category的实现原理?

2.Category跟Extension的区别?

3.Category有load方法么?父类子类Category之间调用顺序是什么?

4.load、initialize方法的区别,调用顺序是什么?以及出现继承他们的调用顺序是什么?

5.Categoryn能添加成员变量么?如果可以,如何添加?

1、Category实现原理


创建一个Category

首先可以将OC转换成C语言编译文件查看一下,当前目录下(注意方法要实现)

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc NSObject+Test.m -o NSObject+Test.cpp


Category转换成C语言编译

原理: 从编译文件我们可以看出,Category编译之后底层会转换成一个_category_t的结构体,内部包含着Category的对象方法,类方法,属性,协议信息。在运行时期,会经过runtime合并到类信息中(类方法放置元类对象中)。 PS: 类方法存储方式,可以看我之前的文章Class本质

下面我们就来搞下:runtime是如何将Category中的信息,合并到对应类信息中呢?

runtime源码地址

下载runtime的源码:objc-os.mm这个文件是入口文件,内部void_objc_init(void)方法是初始化方法


runtime初始化

内部调用map_images函数 (读取镜像/模块),进入函数,在进入map_images_nolock函数实现,再进入_read_images函数,(从函数命也能够看出来 到了读取模块的地方了)


开始读取Category

在进入重新布局class方法,内部调用了attachCategories方法,追加类别

开始追加Category


解析Category方法内部实现

然后进入attachLists函数调用。


Category追加到当前类

2.Category跟Extension的区别?

class extension 是在编译前就储存在类信息中了,而Category是在运行时才会被载入类信息中的

Category是在运行时才会被合并到类信息中去的。

3.Category有load方法么?父类子类Category之间调用顺序是什么?

load方法特点:是在runtime装载类/Category的时候调用,不管你是否用的该类、Category都会调用load方法,runtime是采用的是直接找到对应类的load函数地址调用,而并非是用objc_msgsend()来调用。

调用顺序是先调用类的load方法,再调用Category的load方法,如果类中存在继承关系,先调用super class的load方法在调用子类的load方法。

源码来看:(objc-os.mm-->load_images-->call_load_methods())


load方法加载顺序
真正调用class的load方法

从图中可以看出,先进行循环class的load方法执行,在进行Category方法的执行。

那么继承关系又是怎么调用的呢?同样我们也可以通过源码来分析。

源码路径:(objc-os.mm-->load_images-->prepare_load_methods()-->schedule_class_load)


预加载class列表

可以看到apple用递归调用,然后每次传入自身父类,生成的数组是@[@(super class),@(son class)]这种类型,而且我们从上上图 我们也可以看出,循环数组是从i=0开始的,也就意味着父类的调用顺序会比子类要早。

总结:load方法的调用顺序是:  

1.先加载class中的load方法。

       > 存在继承关系的类,会先进行加载父类,在加载子类,比如person.h、 son.h(son继承person)会先加载person的load方法

       >不存在继承关系的类,会根据编译顺序来加载load方法。比如dog.h 、cat.h两个类的编译顺序谁在先先调用。(编译顺序工程>Tagets>build phases >complie sources  可以看到编译顺序)

2.再进行加载Category中的load方法

         >会根据编译顺序来加载load方法。比如NSObject +dog.h 、NSObject +cat.h两个分类的编译顺序谁在先先调用。(编译顺序工程>Tagets>build phases >complie sources  可以看到编译顺序)

4.load、initialize方法的区别,调用顺序是什么?以及出现继承他们的调用顺序是什么?

initialze 方法是在对象第一次接受消息的时候调用,采用的是objc_msgSend()消息转发机制,

而且每个类只调用一次,父类优先调用,父类存在列表,并且实现的该方法,根据消息转发机制,类别调用顺序优于类对象,所以会先调用父类类别。

比如现在有两个类,person 、student  继承关系,还有两个类别,person+eat 、student+eat、四个文件的都实现了initialize方法,当发生[student alloc]的时候,也就是对象第一次接受纤细的时候,会先从父类中寻找,superclass的方法列表中,分类优于对象,所以执行的是person+eat 中的initialize方法,进而在调用student中的initialize方法,相同道理,会调用student+eat中的initialze方法。

load、initialze区别在于实现方式不同,load是直接找到load函数地址来调用,而initialze是采用消息转发机制来进行调用。因为initialze是通过objc_msgSend进行调用的,所以会有以下特点:如果子类没有实现,会调用父类的initialze,所以父类的initialze可能被调用多次,如果分类实现了initialze,就会覆盖类本身的initialze调用。

总结:load、initialze的区别总结?

1》调用方式:load是根据函数地址直接到调用。initialze是通过objc_msgSend()调用。

2》调用时刻:load是runtime加载类,Category的时候调用,只会调用一次,而initialze是在类第一次接受到消息的时候调用,每个类只会initialze一次,(父类的initialze可能会被多次调用)

3》load调用顺序:

a>  load 先调用类的load,先编译的类优先调用load方法,调用子类的load之前,会调用父类的load

b> 再调用分类的load

4》initialze调用顺序:

a> 先初始化父类

b> 再初始化子类,可能最终调用的父类的initialze方法。

5.Categoryn能添加成员变量么?如果可以,如何添加?

不能直接添加成员变量,因为Category中添加属性不会自动生成 _成员变量 、setter、getter方法的实现,只会生成setter、getter方法的声明。但是可以用运行时间接关联对象,完成添加属性。

运用runtime  关联对象的api,可以达到属性关联的目的。这里并不是说类别就可以添加属性了,而是通过runtime达到跟添加属性一样的目的而已。


关联属性

objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)

三个参数,第一个是当前对象,第二个是一个常亮指针。这里面用@selector(age) 返回的是一个唯一的age的函数指针地址,可以作为常亮。

第三个 

第三个参数

根据你参数定义的关键字选择对应的枚举值即可

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

推荐阅读更多精彩内容