iOS底層原理 - Category實(shí)現(xiàn)原理(一)

通過(guò)探索Category底層原理回答以下問(wèn)題

1)?Category是否可以添加方法、屬性、成員變量?Category是否可以遵守Protocol?

2)?Category的本質(zhì)是什么,在底層是怎么存儲(chǔ)的?

3)?Category的實(shí)現(xiàn)原理是什么,Catagory中的方法是如何調(diào)用到的?

4)?Category中是否有Load方法,load方法是什么時(shí)候調(diào)用的?

5)?load、initialize的區(qū)別

Category可以直接添加 屬性、成員變量嗎?

創(chuàng)建一個(gè)ZHPerson類



添加分類


發(fā)現(xiàn)分類中可以添加屬性,方法,協(xié)議,但是不能添加成員變量。

分析為什么不能添加成員變量?

Category的底層數(shù)據(jù)結(jié)構(gòu)

? ? 首先創(chuàng)建兩個(gè)分類 協(xié)助測(cè)試






將分類文件編譯為.cpp文件,切換到文件所在文件夾下執(zhí)行:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ZHPerson+Sport.m

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ZHPerson+Eat.m


檢索_I_ZHPerson_Sport_sport 和?_C_ZHPerson_Sport_sport


分析可知:上述代碼創(chuàng)建了_method_list_t類型的結(jié)構(gòu)體變量

_OBJC_$_CATEGORY_INSTANCE_METHODS_ZHPerson_$_Sport?

?_OBJC_$_CATEGORY_CLASS_METHODS_ZHPerson_$_Sport

分別用于存儲(chǔ)實(shí)例方法列表和類方法列表



這里簡(jiǎn)單說(shuō)下_objc_method中的method_type,詳細(xì)介紹后續(xù)在探究runtime消息機(jī)制時(shí)再繼續(xù)扒。method_type其實(shí)可以看做用字符縮寫來(lái)表達(dá)的函數(shù)類型字符串,比如?v@:i?就是返回類型為void,第一個(gè)參數(shù)為id類型,第二個(gè)參數(shù)為指針類型,第三個(gè)參數(shù)為int類型的函數(shù),如- (void)addStepCount:(int)count(我們知道iOS中方法調(diào)用會(huì)默認(rèn)傳入隱式參數(shù) 方法調(diào)用者:self 和 方法名:_cmd。這也是為什么我們可以在方法內(nèi)部訪問(wèn)self、cmd的原因)

繼續(xù)檢索_OBJC_$_CATEGORY_ZHPerson_$_Sport


回答第二個(gè)問(wèn)題:分類底層如何存儲(chǔ)的

分析可知:上述代碼創(chuàng)建了一個(gè)_category_t類型的結(jié)構(gòu)體變量 _OBJC_$_CATEGORY_ZHPerson_$_Sport;并且傳入了類方法列表、實(shí)例方法列表、協(xié)議類別、屬性列表,具備了Category的所有信息,沒(méi)錯(cuò)Category在底層就是_category_t類型,下邊我們檢索結(jié)構(gòu)體類型_category_t,看下_category_t的定義;


到此我們已經(jīng)清楚的看到了Category在底層的存儲(chǔ)結(jié)構(gòu),并且可以看到底層并沒(méi)有存儲(chǔ)成員變量,這也就是為什么直接添加成員變量會(huì)報(bào)錯(cuò)的原因。

Category中的屬性

并且我們知道在類中添加一個(gè)屬性,系統(tǒng)為我們做了三件事

@property (nonatomic, copy) NSString *name;

? ? 1) 創(chuàng)建了一個(gè)成員變量_name ?

? ? 2)?生成了setter、getter方法的聲明

? ? 3)?生成了setter、getter方法的實(shí)現(xiàn)

對(duì)比?ZHPerson 編譯后的.cpp中的屬性和分類中的區(qū)別

ZHPerson.cpp:


ZHPerson (Sport).cpp 文件


發(fā)現(xiàn)原類中生成了屬性的settter 和getter 方法,但是分類中沒(méi)有屬性的settter 和getter?實(shí)現(xiàn)。

在Category添加屬性系統(tǒng)僅僅只生成了setter、getter方法的聲明

如何使Category中的屬性與類中的屬性具備同樣的效果(關(guān)聯(lián)對(duì)象)

? ??

可以通過(guò)runtime中的關(guān)聯(lián)對(duì)象方法來(lái)實(shí)現(xiàn)


關(guān)聯(lián)策略,和@property后的關(guān)鍵字對(duì)應(yīng).


系統(tǒng)是如何管理關(guān)聯(lián)對(duì)象的

? ??去官網(wǎng)下載runtime源碼,搜索objc_setAssociatedObject方法,這里不做過(guò)多分析,簡(jiǎn)單說(shuō)下結(jié)論,后續(xù)會(huì)單開一篇扒關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)原理。

runtime用四個(gè)類來(lái)管理關(guān)聯(lián)對(duì)象,AssociationsManager、AssociationsHashMap、AssociationsMap、ObjectAssociation,

1)關(guān)聯(lián)對(duì)象并不是存儲(chǔ)在被關(guān)聯(lián)對(duì)象本身內(nèi)存中

2)?關(guān)聯(lián)對(duì)象存儲(chǔ)在全局的統(tǒng)一的一個(gè)AssociationsManager中

3)設(shè)置關(guān)聯(lián)對(duì)象為nil,就相當(dāng)于是移除關(guān)聯(lián)對(duì)象

Category中的方法調(diào)用順序 - 表象

再創(chuàng)建一個(gè)ZHPerson的子類ZHStudent ,再編寫一個(gè)ZHStudent的分類,分類里書寫life方法




結(jié)論:1、分類中方法會(huì)覆蓋原類中的方法

2.、Compile Sources中編譯順序在后面的文件優(yōu)先級(jí)會(huì)更高。

ZHPerson有兩個(gè)分類Sport 和Eat,并且兩個(gè)分類都實(shí)現(xiàn)了life方法,

但是全部調(diào)用的分類Sport的方法,和Build Phases 里的Compile Sources 里的文件編譯順序有關(guān)


1、分類中的方法優(yōu)先級(jí)高于原類中的方法

2、后編譯的分類優(yōu)先級(jí)高于先編譯的分類

3、我們常說(shuō)的分類方法覆蓋原類方法并不是真正的覆蓋,只是objc_msgSend在分類中找到方法實(shí)現(xiàn)后不再繼續(xù)查找。

Category方法調(diào)用順序 - 本質(zhì)

? ? 1)OC中的方法調(diào)用簡(jiǎn)單的說(shuō)就是通過(guò)實(shí)例對(duì)象(或類對(duì)象)的isa指針和類對(duì)象(或元類對(duì)象)的superClass指針去類(或元類)對(duì)象中查找方法。

? ? 2)Category中的方法、屬性等編譯后是存儲(chǔ)在category_t結(jié)構(gòu)體中的,也就是說(shuō)編譯后分類中的方法并沒(méi)有合并到類(或元類)中,我們是無(wú)法在類對(duì)象(或元類對(duì)象)中找到Category中的方法的。

? ? 3)但是最終調(diào)用的時(shí)候我們卻可以通過(guò)isa和superClass指針找到這些方法。所以我們有理由猜測(cè)runtime幫我們做了方法合并

? ? 4)_objc_init就是runtime的初始化函數(shù),是在app啟動(dòng)過(guò)程的"初始化除可執(zhí)行文件外的所有Mach-O文件初始化調(diào)用的;按文件編譯倒序?qū)⒏鞣诸愔械姆椒?、協(xié)議、屬性列表分別整合成一個(gè)二維數(shù)組后,添加到原類中的方法列表,屬性列表,協(xié)議列表。(分類中的方法屬性等系統(tǒng)是什么時(shí)候如何添加到原類中的?)



Category中的+load方法

在創(chuàng)建的兩個(gè)類文件以及3個(gè)分類文件添加+load方法,也添加上initialize()方法,方便后面測(cè)試。



不導(dǎo)入上面相關(guān)任何文件,不創(chuàng)建對(duì)象,直接運(yùn)行

可以發(fā)現(xiàn)未做任何調(diào)用和對(duì)象創(chuàng)建的情況下,也會(huì)執(zhí)行+ (void)load方法。

嘗試在xcode->targets->build phases->compile sources中調(diào)整文件編譯順序,發(fā)現(xiàn)

1、父類中的load優(yōu)先于子類中調(diào)用,且不受編譯順序影響。

2、原類中的load方法優(yōu)先于分類調(diào)用,且不受編譯順序影響。

3、兩個(gè)分類中的load方法執(zhí)行順序根據(jù)編譯順序,且與其繼承的父類無(wú)關(guān)系。


Category中的+ (void)initialize方法

再創(chuàng)建一個(gè)新類ZHDog,作對(duì)比


像測(cè)試load方法一樣,不導(dǎo)入創(chuàng)建的任何文件不創(chuàng)建對(duì)象,直接運(yùn)行但是并沒(méi)有調(diào)用initialize方法。

創(chuàng)建對(duì)象

情況1

情況2

情況3


以上3種情況的打印結(jié)果都是下面結(jié)果:


3種情況編譯順序都是



說(shuō)明同一個(gè)類中的initialize方法在多次創(chuàng)建對(duì)象時(shí)僅調(diào)用一次


現(xiàn)在只調(diào)整文件編譯順序:



打印結(jié)果沒(méi)變

initialize調(diào)用總結(jié):

1)父類調(diào)用優(yōu)先級(jí)高于子類,不受編譯順序影響;

2) 分類會(huì)覆蓋原類中的方法

注意,如果子類及子類分類沒(méi)有實(shí)現(xiàn)initialize方法,根據(jù)runtime消息發(fā)送機(jī)制,父類中的initialize會(huì)調(diào)用兩次

現(xiàn)在注釋掉ZHStudent及其分類的initialize方法,其他全不變





load是只要類所在的文件被引用就會(huì)被調(diào)用,而initialize在類或其子類的第一個(gè)方法調(diào)用之前被調(diào)用(runtime 中l(wèi)oad方法不能認(rèn)為第一個(gè)方法)。load在main函數(shù)之前調(diào)用,initialize在main函數(shù)之后調(diào)用。這兩個(gè)方法會(huì)被自動(dòng)調(diào)用。

· load和initialize方法都不用顯示的調(diào)用父類的方法而是自動(dòng)調(diào)用,即使子類沒(méi)有initialize方法也會(huì)調(diào)用父類的方法如果子類顯示調(diào)用[super initialize],則父類多次調(diào)用,load方法則不會(huì)調(diào)用父類。

·load方法通常用來(lái)進(jìn)行Method Swizzle,initialize方法一般用于初始化全局變量或靜態(tài)變量。

·load和initialize方法內(nèi)部使用了鎖,因此它們是線程安全的。實(shí)現(xiàn)時(shí)要盡可能保持簡(jiǎn)單,避免阻塞線程,不要再使用鎖。

·每個(gè)類只調(diào)用initialize一次。如果希望為類和類的類別執(zhí)行獨(dú)立初始化,則應(yīng)該實(shí)現(xiàn)load方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容