這個系列的文章僅僅對 Category 的實如今源碼層面作必定的分析,你們仍是要多上手嘗試,你會發現不少有意思的問題,例如:
若是你的工程和你使用的第三方庫,產生了方法重名,在你開啓了 LTO 優化的先後,你會發現分別調用到了第三方庫和你的主工程方法中,這又是爲何呢?容許我在開頭就挖個坑,這是個頗有意思的問題,等我有空會寫一篇文章。c++
這裏有幾點要說明的:數組
Category相信你們都有使用過,利用分類能夠對一些系統類進行擴展,分模塊等等。咱們先來構造個分類看一下:markdown
// Person類
- (void)run {
NSLog(@"run");
}
複製代碼
// Person+Test 分類
- (void)test {
NSLog(@"test");
}
+ (void)test2 {
NSLog(@"test2");
}
複製代碼
這樣咱們就能夠在引入頭文件後調用對應的方法。函數
咱們都知道,調用方法的時候是經過objc_msgSend(self, SEL)來實現的,那麼會產生問題:工具
person的類對象中有沒有存儲分類中的實例方法?Person+Test會不會有本身的分類對象,將分類中的實例方法存放在分類對象中呢?
先說結論:不論有沒有分類,每一個類都只有一個類對象,分類中的方法也是存儲在Person類的類對象中的。優化
首先咱們 cd 到 Person+Eat 的目錄下,而後執行 clang rewrite-objc Person+Test.m,這樣Person+Test.m就被轉化爲了c++的代碼 Person+Test.cppui
這個文件比較長,咱們直接看 _category_t 這個結構體,這個結構體是每個分類的結構spa
而後再找到這個結構體初始化的地方:調試
經過這裏咱們就能看出,Person+Test的初始化時,類名叫作 Person,有實例方法和類方法,其餘都是空,那麼讓咱們來看看實例方法和類方法初始化的地方:code
不難看出也正好對應這 Person+Test 分類中的兩個方法,- (void)test 和 + (void)test2
實際上這些方法、屬性、協議,都是在運行時經過runtime進行合併的,要了解這個合併的過程,就須要閱讀runtime的源碼。下面是本次閱讀runtime源碼的一些過程:
這個方法中,將全部的category中的方法、屬性、協議,整合到二維數組method_list_t mlists中,而後調用 attachLists 方法進行attach,咱們來看一下這個方法裏的代碼
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
// 從新分配內存,由於要進行合併,因此原來分配的內存不夠了
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
// 將array()原有的list拷貝到新list的尾部
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 將新的方法list拷貝到list的頭部
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
複製代碼
這裏咱們看到,最終合併的方法列表的存儲結構是順序表,而查找時也是順序查找的。
分類方法會覆蓋原類方法的緣由是由於原類方法被放在了list最後(memmove操做)。
後加載的分類方法會覆蓋前面的分類方法是由於,在 map_images_nolock 函數中,採用的是while(i--)的方式,所以後加載的分類會排在前面。
這篇文章中沒有把全部的源碼拿出來,你們應當對着源碼親自上手看一看。 文中若有錯誤,歡迎指出。