本節(jié)將解釋下一下問題:
1.Category的實(shí)現(xiàn)原理?
2.Category跟Extension的區(qū)別?
3.Category有l(wèi)oad方法么?父類子類Category之間調(diào)用順序是什么?
4.load、initialize方法的區(qū)別,調(diào)用順序是什么?以及出現(xiàn)繼承他們的調(diào)用順序是什么?
5.Categoryn能添加成員變量么?如果可以,如何添加?
1、Category實(shí)現(xiàn)原理
首先可以將OC轉(zhuǎn)換成C語言編譯文件查看一下,當(dāng)前目錄下(注意方法要實(shí)現(xiàn))
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc NSObject+Test.m -o?NSObject+Test.cpp
原理:?從編譯文件我們可以看出,Category編譯之后底層會轉(zhuǎn)換成一個(gè)_category_t的結(jié)構(gòu)體,內(nèi)部包含著Category的對象方法,類方法,屬性,協(xié)議信息。在運(yùn)行時(shí)期,會經(jīng)過runtime合并到類信息中(類方法放置元類對象中)。 PS: 類方法存儲方式,可以看我之前的文章Class本質(zhì)。
下面我們就來搞下:runtime是如何將Category中的信息,合并到對應(yīng)類信息中呢?
下載runtime的源碼:objc-os.mm這個(gè)文件是入口文件,內(nèi)部void_objc_init(void)方法是初始化方法
內(nèi)部調(diào)用map_images函數(shù) (讀取鏡像/模塊),進(jìn)入函數(shù),在進(jìn)入map_images_nolock函數(shù)實(shí)現(xiàn),再進(jìn)入_read_images函數(shù),(從函數(shù)命也能夠看出來 到了讀取模塊的地方了)
在進(jìn)入重新布局class方法,內(nèi)部調(diào)用了attachCategories方法,追加類別
然后進(jìn)入attachLists函數(shù)調(diào)用。
2.Category跟Extension的區(qū)別?
class extension 是在編譯前就儲存在類信息中了,而Category是在運(yùn)行時(shí)才會被載入類信息中的
Category是在運(yùn)行時(shí)才會被合并到類信息中去的。
3.Category有l(wèi)oad方法么?父類子類Category之間調(diào)用順序是什么?
load方法特點(diǎn):是在runtime裝載類/Category的時(shí)候調(diào)用,不管你是否用的該類、Category都會調(diào)用load方法,runtime是采用的是直接找到對應(yīng)類的load函數(shù)地址調(diào)用,而并非是用objc_msgsend()來調(diào)用。
調(diào)用順序是先調(diào)用類的load方法,再調(diào)用Category的load方法,如果類中存在繼承關(guān)系,先調(diào)用super class的load方法在調(diào)用子類的load方法。
源碼來看:(objc-os.mm-->load_images-->call_load_methods())
從圖中可以看出,先進(jìn)行循環(huán)class的load方法執(zhí)行,在進(jìn)行Category方法的執(zhí)行。
那么繼承關(guān)系又是怎么調(diào)用的呢?同樣我們也可以通過源碼來分析。
源碼路徑:(objc-os.mm-->load_images-->prepare_load_methods()-->schedule_class_load)
可以看到apple用遞歸調(diào)用,然后每次傳入自身父類,生成的數(shù)組是@[@(super class),@(son class)]這種類型,而且我們從上上圖 我們也可以看出,循環(huán)數(shù)組是從i=0開始的,也就意味著父類的調(diào)用順序會比子類要早。
總結(jié):load方法的調(diào)用順序是:??
1.先加載class中的load方法。
? ? ? ?> 存在繼承關(guān)系的類,會先進(jìn)行加載父類,在加載子類,比如person.h、 son.h(son繼承person)會先加載person的load方法
? ? ? ?>不存在繼承關(guān)系的類,會根據(jù)編譯順序來加載load方法。比如dog.h 、cat.h兩個(gè)類的編譯順序誰在先先調(diào)用。(編譯順序工程>Tagets>build phases >complie sources? 可以看到編譯順序)
2.再進(jìn)行加載Category中的load方法
? ? ? ? ?>會根據(jù)編譯順序來加載load方法。比如NSObject +dog.h 、NSObject +cat.h兩個(gè)分類的編譯順序誰在先先調(diào)用。(編譯順序工程>Tagets>build phases >complie sources? 可以看到編譯順序)
4.load、initialize方法的區(qū)別,調(diào)用順序是什么?以及出現(xiàn)繼承他們的調(diào)用順序是什么?
initialze 方法是在對象第一次接受消息的時(shí)候調(diào)用,采用的是objc_msgSend()消息轉(zhuǎn)發(fā)機(jī)制,
而且每個(gè)類只調(diào)用一次,父類優(yōu)先調(diào)用,父類存在列表,并且實(shí)現(xiàn)的該方法,根據(jù)消息轉(zhuǎn)發(fā)機(jī)制,類別調(diào)用順序優(yōu)于類對象,所以會先調(diào)用父類類別。
比如現(xiàn)在有兩個(gè)類,person 、student? 繼承關(guān)系,還有兩個(gè)類別,person+eat 、student+eat、四個(gè)文件的都實(shí)現(xiàn)了initialize方法,當(dāng)發(fā)生[student alloc]的時(shí)候,也就是對象第一次接受纖細(xì)的時(shí)候,會先從父類中尋找,superclass的方法列表中,分類優(yōu)于對象,所以執(zhí)行的是person+eat 中的initialize方法,進(jìn)而在調(diào)用student中的initialize方法,相同道理,會調(diào)用student+eat中的initialze方法。
load、initialze區(qū)別在于實(shí)現(xiàn)方式不同,load是直接找到load函數(shù)地址來調(diào)用,而initialze是采用消息轉(zhuǎn)發(fā)機(jī)制來進(jìn)行調(diào)用。因?yàn)閕nitialze是通過objc_msgSend進(jìn)行調(diào)用的,所以會有以下特點(diǎn):如果子類沒有實(shí)現(xiàn),會調(diào)用父類的initialze,所以父類的initialze可能被調(diào)用多次,如果分類實(shí)現(xiàn)了initialze,就會覆蓋類本身的initialze調(diào)用。
總結(jié):load、initialze的區(qū)別總結(jié)?
1》調(diào)用方式:load是根據(jù)函數(shù)地址直接到調(diào)用。initialze是通過objc_msgSend()調(diào)用。
2》調(diào)用時(shí)刻:load是runtime加載類,Category的時(shí)候調(diào)用,只會調(diào)用一次,而initialze是在類第一次接受到消息的時(shí)候調(diào)用,每個(gè)類只會initialze一次,(父類的initialze可能會被多次調(diào)用)
3》load調(diào)用順序:
a>? load 先調(diào)用類的load,先編譯的類優(yōu)先調(diào)用load方法,調(diào)用子類的load之前,會調(diào)用父類的load
b>?再調(diào)用分類的load
4》initialze調(diào)用順序:
a>?先初始化父類
b>?再初始化子類,可能最終調(diào)用的父類的initialze方法。
5.Categoryn能添加成員變量么?如果可以,如何添加?
不能直接添加成員變量,因?yàn)镃ategory中添加屬性不會自動(dòng)生成 _成員變量 、setter、getter方法的實(shí)現(xiàn),只會生成setter、getter方法的聲明。但是可以用運(yùn)行時(shí)間接關(guān)聯(lián)對象,完成添加屬性。
運(yùn)用runtime? 關(guān)聯(lián)對象的api,可以達(dá)到屬性關(guān)聯(lián)的目的。這里并不是說類別就可以添加屬性了,而是通過runtime達(dá)到跟添加屬性一樣的目的而已。
objc_setAssociatedObject(<#id? _Nonnull object#>, <#const void * _Nonnull key#>, <#id? _Nullable value#>, <#objc_AssociationPolicy policy#>)
三個(gè)參數(shù),第一個(gè)是當(dāng)前對象,第二個(gè)是一個(gè)常亮指針。這里面用@selector(age) 返回的是一個(gè)唯一的age的函數(shù)指針地址,可以作為常亮。
第三個(gè)?
根據(jù)你參數(shù)定義的關(guān)鍵字選擇對應(yīng)的枚舉值即可