Category 加載詳解

定義

category 的主要作用是為已經(jīng)存在的類添加方法黍檩。

官方文檔

官網(wǎng)提供了其他的優(yōu)勢:

  • 可以把類的實現(xiàn)分開在幾個不同的文件里面颂碧。
    • 可以減少分開文件的體積
    • 可以把不同的功能組織到不同的category里
    • 可以由多個開發(fā)者共同完成一個類
    • 可以按需加載想要的類別等等。
  • 聲明專有方法

原理

思考沫换?

一個類的實例方法臭蚁、類方法默認(rèn)都是存在這個類的類對象和元類對象中,那么分類的方法存在哪呢苗沧?
(也在類方法刊棕,合并,運行時合并 利用runtime)

首先我們創(chuàng)建一個類待逞,然后創(chuàng)建一個類的 category 文件甥角,并使用編譯命令來生成對應(yīng)的 c++ 文件。

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+name.m

可以看見我們的分類被編譯成了下面這種結(jié)構(gòu)

// 空的分類
static struct _category_t _OBJC_$_CATEGORY_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    0,
    0,
    0,
    0,
};

// 設(shè)置了方法 和 屬性等 編譯后的文件
static struct _category_t _OBJC_$_CATEGORY_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_name,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_name,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_name,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_name,
};

結(jié)構(gòu)體的類型為 _category_t

struct _category_t {
    const char *name;   // 類的名稱
    struct _class_t *cls;  //
    const struct _method_list_t *instance_methods;   // 實例方法
    const struct _method_list_t *class_methods;  // 類方法
    const struct _protocol_list_t *protocols; // 協(xié)議
    const struct _prop_list_t *properties; // 屬性
};

實例方法的結(jié)構(gòu)體

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"myName", "@16@0:8", (void *)_I_Person_name_myName}}
};

類方法的結(jié)構(gòu)體

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"myHeight", "i16@0:8", (void *)_C_Person_name_myHeight}}
};

屬性的結(jié)構(gòu)體

static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"len","Ti,N"}}   // i  指的是 int 類型
};

協(xié)議的結(jié)構(gòu)體

static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[2];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    2,
    &_OBJC_PROTOCOL_NSCopying,
    &_OBJC_PROTOCOL_NSCoding
};

runtime 源碼解讀

首先找到 runtime 的入口, 在 objc-os.mm 中的 _objc_init 函數(shù)

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

然后在 map_images 中調(diào)用 map_images_nolock,然后調(diào)用了_read_images识樱,
然后里面有下面代碼


    // Discover categories. Only do this after the initial category
    // attachment has been done. For categories present at startup,
    // discovery is deferred until the first load_images call after
    // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

跟進(jìn)去之后嗤无,可以找到 attachCategories 方法,這部分代碼就是合并分類的代碼。

// 附加上分類的核心操作
// cls:類對象或者元類對象怜庸,cats_list:分類列表
static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    // 先分配固定內(nèi)存空間來存放方法列表当犯、屬性列表和協(xié)議列表
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    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();

    for (uint32_t i = 0; i < cats_count; i++) {
        // 取出某個分類
        auto& entry = cats_list[i];

        // entry.cat就是category_t *cat
        // 根據(jù)isMeta屬性取出每一個分類的類方法列表或者對象方法列表
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        
        // 如果有方法則添加mlist數(shù)組到mlists這個大的方法數(shù)組中
        // mlists是一個二維數(shù)組:[[method_t, method_t, ....], [method_t, method_t, ....]]
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            // 將分類列表里先取出來的分類方法列表放到大數(shù)組mlists的最后面(ATTACH_BUFSIZ - ++mcount),所以最后編譯的分類方法列表會放在整個方法列表大數(shù)組的最前面            
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        // 同上面一樣取出的是分類中的屬性列表proplist加到大數(shù)組proplists中
        // proplists是一個二維數(shù)組:[[property_t, property_t, ....], [property_t, property_t, ....]]
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        // 同上面一樣取出的是分類中的協(xié)議列表protolist加到大數(shù)組protolists中
        // protolists是一個二維數(shù)組:[[protocol_ref_t, protocol_ref_t, ....], [protocol_ref_t, protocol_ref_t, ....]]
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    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){
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    // 將分類的所有屬性附加到類對象的屬性列表中
    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    // 將分類的所有協(xié)議附加到類對象的協(xié)議列表中
    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

里面有關(guān)鍵性代碼 attachLists, 在此處進(jìn)行了分類方法的加載順序嚎卫。

void attachLists(List* const * addedLists, uint32_t addedCount) {
   if (addedCount == 0) return;

   if (hasArray()) {
       // 這里是把類的地址重新分配新的地址加 newcount 也是分類的創(chuàng)建個數(shù),而老的分類信息重新復(fù)制一下內(nèi)存宏榕,通過 i-- 倒著取值拓诸,所以最先編譯的會在最后面侵佃,
       // 獲取原本的個數(shù)
       uint32_t oldCount = array()->count;
       // 最新的個數(shù) = 原本的個數(shù) + 新添加的個數(shù)
       uint32_t newCount = oldCount + addedCount;
       
       // 重新分配內(nèi)存
       array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
       // 將新數(shù)組的個數(shù)改為最新的個數(shù)
       newArray->count = newCount;
       // 將舊數(shù)組的個數(shù)改為最新的個數(shù)
       array()->count = newCount;
       
       // 遞減遍歷,將舊數(shù)組里的元素從后往前的依次放到新數(shù)組里
       for (int i = oldCount - 1; i >= 0; i--)
           newArray->lists[i + addedCount] = array()->lists[i];
       
       // 將新增加的元素從前往后的依次放到新數(shù)組里
       for (unsigned i = 0; i < addedCount; i++)
           newArray->lists[i] = addedLists[i];
       
       // 釋放舊數(shù)組數(shù)據(jù)
       free(array());
       
       // 賦值新數(shù)組數(shù)據(jù)
       setArray(newArray);
       validate();
   }
   else if (!list  &&  addedCount == 1) {
       // 0 lists -> 1 list
       list = addedLists[0];
       validate();
   } 
   else { .... }
}

上面內(nèi)容就是分類方法的一個合并流程奠支。

參考文檔:

http://www.babyitellyou.com/details?id=6045c2fe4da5fa50e14ff4f3
http://www.reibang.com/p/ed8d3be7e8f4

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末馋辈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子倍谜,更是在濱河造成了極大的恐慌迈螟,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尔崔,死亡現(xiàn)場離奇詭異答毫,居然都是意外死亡,警方通過查閱死者的電腦和手機您旁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門烙常,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鹤盒,你說我怎么就攤上這事蚕脏。” “怎么了侦锯?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵驼鞭,是天一觀的道長。 經(jīng)常有香客問我尺碰,道長挣棕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任亲桥,我火速辦了婚禮洛心,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘题篷。我一直安慰自己词身,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布番枚。 她就那樣靜靜地躺著法严,像睡著了一般。 火紅的嫁衣襯著肌膚如雪葫笼。 梳的紋絲不亂的頭發(fā)上深啤,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天,我揣著相機與錄音路星,去河邊找鬼溯街。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的苫幢。 我是一名探鬼主播访诱,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼韩肝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起九榔,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤哀峻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哲泊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剩蟀,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年切威,在試婚紗的時候發(fā)現(xiàn)自己被綠了育特。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡先朦,死狀恐怖缰冤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喳魏,我是刑警寧澤棉浸,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站刺彩,受9級特大地震影響迷郑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜创倔,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一嗡害、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧畦攘,春花似錦霸妹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至朗徊,卻和暖如春首妖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爷恳。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工有缆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓棚壁,卻偏偏與公主長得像杯矩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子袖外,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容