iOS 底層學習16

前言

iOS 底層第16天的學習哥力。接著 15天學習的內容。分析 category 是如何加載到 class 里的泣侮。

category 探索


  • 我們回到 realizeClassWithoutSwift
static Class realizeClassWithoutSwift(Class cls, Class previously) {
    // ...
    // Attach categories
    methodizeClass(cls, previously);
    // ...
}
  • 進入 methodizeClass
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();
 
   // ... 
    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1); 
    }
  // ....
  • if (rwe) 有值 rwe 就會 methods.attachLists 局雄,進去反推 rwe = rw->ext(),那 ext() 在哪里實現(xiàn)的?
  • 進入 ext()
   class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
   }
  • 搜索class_rw_ext_t
  class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
   }

  • 找到 extAllocIfNeeded 一疯,全局搜索 extAllocIfNeeded
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    // ...
    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();
    // ...

}
  • 發(fā)現(xiàn)在 attachCategories 里有對 extAllocIfNeeded 進行調用
  • 全局搜索 attachCategories 撼玄, 發(fā)現(xiàn)有2個地方調用了 attachCategories
  • 第一處: attachToClass
 void attachToClass(Class cls, Class previously, int flags)
    {
       // ...
        auto &map = get();
        auto it = map.find(previously);

        if (it != map.end()) {
            category_list &list = it->second;
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }
  • 第二處: load_categories_nolock
static void load_categories_nolock(header_info *hi) {
    size_t count;
     auto processCatlist = [&](category_t * const *catlist) {
         for (unsigned i = 0; i < count; i++) {
            category_t *cat = catlist[I];
            Class cls = remapClass(cat->cls);
            locstamped_category_t lc{cat, hi};
                   // ...
                   if (cat->classMethods  ||  cat->protocols
                    ||  (hasClassProperties && cat->_classProperties))
                    {
                    if (cls->ISA()->isRealized()) {
                      // 調用了
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                        objc::unattachedCategories.addForClass(lc, cls->ISA());
                    }
                }
            }
     }
    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}
  • 先從 attachToClass 進行推導,全局搜索 attachToClass
static void methodizeClass(Class cls, Class previously)
{
 bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();
    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
  // ... 省略部分代碼
   objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

}

  • 最后發(fā)現(xiàn)又回到了 methodizeClass
  • 正向流程應該是: realizeClassWithoutSwift -> methodizeClass-> attachToClass -> attachCategories
  • 然后這個 attachCategories 內部到底做了哪些事情呢墩邀?我們繼續(xù)進行分析

attachCategories


  • 進入 attachCategories
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    // ...
    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();
   // ...
   if (mcount > 0) {
        // 方法的排序
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        //  方法的添加
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }
// . ..

}
  • 進入 methods.attachLists 進行分析
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;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; I--)
                newArray->lists[i + addedCount] = array()->lists[I];
            for (unsigned i = 0; i < addedCount; I++)
                newArray->lists[i] = addedLists[I];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr<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;
            for (unsigned i = 0; i < addedCount; I++)
                array()->lists[i] = addedLists[I];
            validate();
        }
    }
  • 我們先從最短的開始分析
  // 0 lists -> 1 list
  list = addedLists[0];
  validate();
  • 當新添加只有1個并且lists 無值的時候掌猛,list = addedList
  • 接著從 else 開始分析
    // 1 list -> many lists
   Ptr<List> oldList = list; 
   uint32_t oldCount = oldList ? 1 : 0; //  oldList 有數(shù)據(jù) => oldCount = 1
   uint32_t newCount = oldCount + addedCount; // 假設 addedCount =2 ,newCount = 1+2 = 3
   setArray((array_t *)malloc(array_t::byteSize(newCount))); // 創(chuàng)建一個新的array
   array()->count = newCount; //  新的 array 空間大小 = 3
   if (oldList) array()->lists[addedCount] = oldList; // 把 oldlist 放到 lists[2] 位置里
   for (unsigned i = 0; i < addedCount; I++)
        array()->lists[i] = addedLists[I]; // 循環(huán)從第0位置開始放入到lists眉睹,放入個數(shù)為addedCount = 2
   validate();
  • 總結一下就是把 oldlists 這個整體 放到 newlists 最后面荔茬,把 added 數(shù)據(jù) 放到 newlsit 的最前面
  • 接著繼續(xù)添加就會進入 if (hasArray()),開始分析if (hasArray())
// many lists -> many lists
    uint32_t oldCount = array()->count;
    uint32_t newCount = oldCount + addedCount;
    array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount)); // 開辟一個新的內存空間
    newArray->count = newCount;
    array()->count = newCount;

    for (int i = oldCount - 1; i >= 0; i--) // 倒序 ,假設oldCount = 3 辣往,addedCount = 1, i = 2
        newArray->lists[i + addedCount] = array()->lists[i]; //第一次循環(huán)兔院,newlists[2 + 1 = 3] =殖卑, lists[2]  站削,就是把list最后元素 加到  newlist 的最后面
    for (unsigned i = 0; i < addedCount; I++)
        newArray->lists[i] = addedLists[I]; //  把  added  數(shù)據(jù)加到 newlist 的前面
    free(array());  // 情況數(shù)據(jù)重新開始
    setArray(newArray);
    validate();
  • 總結一下就是把 lists 數(shù)據(jù) 倒序依次放到 newlists 最后面,把added 數(shù)據(jù) 放到 newlists 的最前面
  • ps:圖畫的有點丑請見諒

動態(tài)調試分析 attachCategories
  • 分析前準備新建一個 XKStudent (XKA) 類目孵稽,主類分類同時調用 load 方法
@implementation XKStudent

- (void) doSomething {}
- (void) doSomething2 {}

+ (void) load {
    NSLog(@"加載了 %s",__func__);
}

@end
// 分類
@implementation XKStudent (XKA)

+ (void) load {
    NSLog(@"加載了 %s",__func__);
}
- (void) doSomethingByCate {}
- (void) doSomething2ByCate {}

@end
  • 打印流程如下
  • 這時我們發(fā)現(xiàn)當主類分類同時調用 load 方法時许起,會調用 load_categories_nolock
  • 開始分析,在哪里調用了 load_categories_nolock
  • 查看堆棧信息菩鲜, 得知在 load_images -> loadAllCategories -> load_categories_nolock
    最終來到了 attachCategories

  • 這時得出一個結論就是: 分類 同時調用 load方法
    _read_image 開始 調用 realizeClassWithoutSwift -> methodizeClass-> attachToClass 最終沒有進入 attachCategories

  • 而是在 load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

  • 探索到這里也就能解釋了之前為何 attachCategories 會有2個地方(1.attachToClass,2: load_categories_nolock)調用的原因了园细。

  • 進入 load_categories_nolock

  • 打印輸出 cat 驗證 cat 是我們這次要分析 Student(XKA)
  • 繼續(xù)往下分析
  • 根據(jù)判斷條件進入 attachCategories,來到 ??
  • 分析代碼 mlists[ATTACH_BUFSIZ - ++mcount] = mlist ,輸出打印結果
  • mlist 放到了 mlists 的 第 63 個位置
  • 繼續(xù)往下分析接校,來到??
  • mlists + ATTACH_BUFSIZ - mcount 這個是猛频?,我們打印輸出一下
  • 這下我們就能得知原來· attachLists 里存放的 指針的指針
  • 接下我們動態(tài)調試再次進入了 attachLists
  • 這時我們已得知 addedLists 是一個 二級指針蛛勉,·addedCount = 1
  • 繼續(xù)往下分析來到了 else ??
  • 這里的 list 應該是 主類 - XKStudent 鹿寻,打印驗證下
  • 而這里的 array()->lists[addedCount] 存的是什么呢?
  • 打印 array()->lists[addedCount]
  • 打印 addedList
  • 由此得知 array()->lists[i] 里其實就是存了一個數(shù)組指針诽凌,而指針指向的地址就是 methods_list_t, 由此就能得知 array()->lists[0] 指針指向就是一個category:methods_list ??

已知在 attachLists 方法里還有一個if (hasArray()) {}毡熏,那何時會動態(tài)分析進入呢?
猜想如果定義多個 category 是不是就會進入呢侣诵?

  • 分析前準備新建3個分類
//  XKA
@interface XKStudent (XKA)

- (void) doSomethingByCateA;
- (void) doSomething2ByCateA;

@end
//  XKB
@interface XKStudent (XKB)

- (void) doSomethingByCateB;
- (void) doSomething2ByCateB;

@end
//  XKC
@interface XKStudent (XKC)

- (void) doSomethingByCateC;
- (void) doSomething2ByCateC;

@end
  • 動態(tài)分析進入 load_categories_nolock
  • 打印輸出 count ,catelist[i] 驗證是不是要分析的對象
  • count = 3 說明有3 個 cate痢法,分別是 catelist[0] = XKC ,catelist[1] = XKA,catelist[2] = XKB
  • 繼續(xù)分析進入 attachLists
  • 得知 oldCount = 2 說明 array()->lists 里已經(jīng)有2個數(shù)據(jù)了狱窘,我們輸出來驗證一下
  • 繼續(xù)往下走
  • 打印輸出 newArray->lists
  • 由輸出可知 oldlists ptr 倒序插入到了 newlists ptr,再把addedlists 插入到 newlists 第0個
  • 最后得出的結論就是:當 oldArray() 里已有一個主類至少有一個分類 會進入 if (hasArray()) 里 财搁,然后在0號位置繼續(xù)插入新的分類蘸炸。

??的分析和探索都是基于主類分類 都同時調用了 load 方法。那如果有一種情況不滿足會如何呢尖奔?繼續(xù)進行探索

load 探索

1.主類 load 分類 no load

  • 動態(tài)調試日志??
  • 根據(jù)打印的日志幻馁,發(fā)現(xiàn)并沒有進入 attachCategories

2.主類 no load 分類 load

  • 動態(tài)調試日志??
  • 根據(jù)打印的日志,發(fā)現(xiàn)沒有進入 attachCategories

3.主類 no load 分類 no load

  • 動態(tài)調試日志??
  • 在一次發(fā)生消息的時候時會進入到 realizeClassWithoutSwift

4.主類 load 多個分類不都有 load

  • 可知流程 realizeClassWithoutSwift -> methodizeClass-> attachToClass -> attachCategories

5.主類 no load 多個分類不都有 load

  • 可知流程 realizeClassWithoutSwift -> methodizeClass-> attachToClass -> attachCategories
  • 這下我們就能得知 主類 雖然 no load,但 分類 load 了還是會進入 attachCategories 越锈。有種被迫的感覺

那如果沒有進入 attachCategories 仗嗦,分類里的數(shù)據(jù)到底是何時加載到里的呢?

  • 進入 realizeClassWithoutSwift 分情況進行驗證

  • 1:主類 load 分類 no load

    • 斷點輸出 ro 內部方法
  • 2:主類 no load 分類 load

    • 斷點輸出 ro 內部方法
  • 3:主類 no load 分類 no load

    • 斷點輸出 ro 內部方法
  • 整理:1甘凭,2稀拐,3 在一開始就存在了 data() 里,都是在一開始已經(jīng)從disk 里讀取到了分類的數(shù)據(jù)丹弱。 data() 直接從 macho 里加載數(shù)據(jù)

總結

  • 我們今天從 realizeClassWithoutSwift 會切入點德撬,探索了分類到底是何時進行加載的
  • 當在 主類分類 同時調用 load 方法時,會進入一段很繁瑣的加載流程
    _read_image -> realizeClassWithoutSwift -> methodizeClass-> attachToClass
    load_images -> loadAllCategories -> load_categories_nolock -> attachCategories
    目的就是把 存放list 的指針數(shù)組 加入到 rwe
  • 當在 主類分類 不同時調用 load 方法時躲胳,就會從 macho 直接讀取放到 data() 里蜓洪,再從 data() 讀取 rwe 數(shù)據(jù)
  • 得出最主要的結論就是 在開發(fā)過程中盡量不要在主類和分類同時加載 load 方法

知識點補充

ro rw rwe

你是否會有同樣的?ro rw rwe 到底是什么坯苹?為什么 要有rwe呢隆檀?

名稱 解釋 描述 來源
ro read only 只讀 運動時內存是不會發(fā)生變化恐仑,被稱為 clean memory:干凈內存 disk 讀取
rw read write 讀寫 運行時會被寫入新的數(shù)據(jù),因此它非常的 昂貴为鳄,被稱為 dirty memory 臟內存 rocopy
rwe rw 的擴展 rw 內存非常昂貴,rwe 優(yōu)化 rw rw

rwe 是怎么優(yōu)化rw呢裳仆?

Apple develop 有個運行時機制,它能通過擴展 ,底層 api 動態(tài)增加 rw 的大小孤钦,而 rw 又十分的 昂貴歧斟。而且我們還發(fā)現(xiàn)并不是每個 class 都需要 擴展 或是 動態(tài)api ,只有在需要時候才去分配 內存偏形,因此引入了 rwe静袖。

cls ->Data() 探索

realizeClassWithoutSwift 會有 auto ro = (const class_ro_t *)cls->data();
那為何cls->data() 就能轉成成 ro 呢?

  • 動態(tài)調試把程序運行看看 cls->data() 里到底是什么壳猜,斷點來到??
  • 進入 data()
  • p 輸出 bit->data, x/8gx 輸出 cls ??

  • 發(fā)現(xiàn) data() 就是地址指針勾徽,存放在 cls 里第5片內存里

那為何能轉成 class_ro_t 這個結構呢?

  • 這個最主要的原因就是 Applellvm編譯時 做了數(shù)據(jù)匹配相應的處理。只要 地址指針里的格式結構體的格式 是相互匹配的喘帚,就是進行 結構體指針 的賦值操作畅姊。
  • 再舉個 ??
int main(int argc, const char * argv[]) {
    // runtime 調用 api ,讀取 Method
    Method m = class_getInstanceMethod(XKStudent.class, @selector(teacherSay));
}
  • 輸出 ??
  • 找到 Method 源碼 吹由,自定義一個 類似 Method 結構體
struct objc_method {
    SEL _Nonnull method_name;
    char * _Nullable method_types;
    IMP _Nonnull method_imp;
};
int main(int argc, const char * argv[]) {
     struct objc_method *method =  class_getInstanceMethod(XKStudent.class, @selector(teacherSay));
}
  • 輸出 ??
  • 可知同一個指針地址 只要知道其內部結構,就能根據(jù)地址進行賦值

  • 這下又知道了

    • 1.指針地址不但能進行取值
    • 2:指針地址還能再進行同一個格式的結構體指針進行相應的賦值
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末若未,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子倾鲫,更是在濱河造成了極大的恐慌粗合,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乌昔,死亡現(xiàn)場離奇詭異隙疚,居然都是意外死亡,警方通過查閱死者的電腦和手機磕道,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門供屉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溺蕉,你說我怎么就攤上這事伶丐。” “怎么了疯特?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵哗魂,是天一觀的道長。 經(jīng)常有香客問我漓雅,道長录别,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任故硅,我火速辦了婚禮庶灿,結果婚禮上,老公的妹妹穿的比我還像新娘吃衅。我一直安慰自己,他們只是感情好腾誉,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布徘层。 她就那樣靜靜地躺著,像睡著了一般利职。 火紅的嫁衣襯著肌膚如雪趣效。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天猪贪,我揣著相機與錄音跷敬,去河邊找鬼。 笑死热押,一個胖子當著我的面吹牛西傀,可吹牛的內容都是我干的斤寇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拥褂,長吁一口氣:“原來是場噩夢啊……” “哼娘锁!你這毒婦竟也來了?” 一聲冷哼從身側響起饺鹃,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤莫秆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后悔详,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镊屎,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年茄螃,在試婚紗的時候發(fā)現(xiàn)自己被綠了杯道。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡责蝠,死狀恐怖党巾,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情霜医,我是刑警寧澤齿拂,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站肴敛,受9級特大地震影響署海,放射性物質發(fā)生泄漏。R本人自食惡果不足惜医男,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一砸狞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镀梭,春花似錦刀森、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至透罢,卻和暖如春榜晦,著一層夾襖步出監(jiān)牢的瞬間羽圃,已是汗流浹背乾胶。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人识窿。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓斩郎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腕扶。 傳聞我的和親對象是個殘疾皇子孽拷,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內容