ObjC中Category的原理簡析

Objective-C類別也叫分類,是一種不須要繼承便可給類添加方法的語法技術。數組

Category的使用

一般咱們是用Category爲一個類添加一些方法。咱們能夠直接用相似對象對方法調用的樣子直接對Category中的方法進行調用。好比下面的例子,爲Person(Person類定義在.h和.m文件中了,圖片沒有給出)類定義了一個名爲Test的Category。markdown

main.m

調用Category中的test方法與實例直接調用實例方法同樣,也是經過消息發送機制(objc_sendMethod)進行調用。app

咱們都知道相似這種方法調用是經過實例的isa指針查找類對象,在類對象中查找到相應的實例方法進行調用的,固然還包括superclass查找父類,這裏就不作贅述了。由於經過isa指針查找方法已經在往深處看-ObjC對象中有過介紹。iphone

那麼Category定義的方法存放在哪裏呢?其實它們跟類的實例對象同樣,也是存放在類對象中。一樣,Category中定義的類方法,也是存放在元類對象中的。只不過將Category中的信息合併到類或元類對象中的操做是在運行時完成的而非編譯的時。函數

Category的底層結構

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc mian.m -o main.cpp 將上述main.m轉換成main.cpp文件,能夠看到Person的Category (Test) 被轉換成了下面這樣一個_category_t類型的結構體。oop

Person+Test

查看_category_t結構體的結構:其中存放有:名稱、類信息、實例方法和類方法列表、協議列表和屬性列表。spa

_category_t

實際上在runtime源碼的最新版本objc4-723中對category_t的描述中添加了獲取方法列表的方法和元類屬性的方法,而且將properties分紅了instanceProperties和_classProperties。3d

runtime-723中的category_t-w1298

Category初始化傳值

咱們能夠看到在_OBJC_$_CATEGORY_Person_$_test中,傳入的值:指針

instance_methods

class_methods

protocols

properties

在runtime源碼中窺探Category

上面說過Category中的方法、屬性、協議是在運行時合併到類中的,因此咱們在運行時的入口objc_os.mm文件中找到_objc_init方法。咱們能夠看到在方法上面有註釋表示這是入口函數,會經過dyid去加載一些模塊。code

順着這個思路咱們查看map_image是經過map_images_nolock(count, paths, mhdrs)得到的,在這個方法中存在一個加載模塊的方法。

_read_images

加載模塊的函數中有一個重排方法的函數,在這個函數中有一個attachCategories(cls, cats, true /*flush caches*/);函數。這個函數是實現附加分類的做用。參數分別傳遞了cls對象和Category列表。

attachCategories()中將Category中的屬性、方法和協議取出放到數組中,而後用cls取出類中的class_rw_t結構體,取出的methods、properties、protocols分別調用attachLists方法,而且將方法數組、屬性數組、協議數組和數組的中元素的個數傳遞到attachLists中。

在attachLists函數中將以前存放方法、屬性、協議的數組擴容至oldCount+addedCount大小。將原方法、屬性、協議的數組從大數組的第一個位置移動到最後的位置,而後將attachCategories傳入的列表拷貝到大數組的前面,完成Category中的方法、屬性、協議向類對象或元類對象的合併。

attachList函數片斷

將Category中的內容合併到類或元類在runtime中的操做順序:

首先類或元類方法列表是一個二維數組:

方法列表

runtime合併Category信息到類或元類對象中在runtime源碼中的函數用軌跡:

合併Category到類或元類中

attachLists圖示:

attachLists

attachLists最終結果

使用Category中方法的一個問題

咱們能夠看到在沒有將Category的方法列表加到類或元類對象中以前,數組中只有這一個列表,而合併以後,它們就被放到了類或元類對象的方法列表的最後面,因此咱們調用在Category中重寫的類中的實例方法或者類方法都會優先執行Category中的方法,由於isa在類對象或者元類對象中尋找方法的時候會首先在Category中找到,既然找到了就不會繼續往下找了。

可是假若有多個擴展都重寫了類的某個方法,首先必然是執行某個Category的方法,這個Category在方法列表的最前面,那麼多個Category的方法列表在類或元類的方法列表中的順序是怎麼決定的呢?

咱們看到attachLists中擴展中的方法、屬性和協議列表是在attachCategories中生成的,生成的過程:

生成方法、屬性和協議列表

經過i--來進行一個while循環,其中i是int i = cats->count;即cats的個數。而mlist、proplists和protolists是進行++操做的,操做的結果形成了先取出cats列表最後一個放到mlist,propcounts的最前面。cats是按照編譯順序排列的,因此後編譯的cat中取出的方法、屬性和協議列表,分別放在mlist、proplists和protolists的最前面。

因此isa指針也會按照上述順序查找方法,即後編譯的cats中的方法會先調用。

**注:**編譯順序能夠在Xcode中查看和改變

文件編譯順序
相關文章
相關標籤/搜索