iOS - Category底層原理

在Objective-C中,Category(分類)是一種為已有類動(dòng)態(tài)添加方法、屬性、協(xié)議的機(jī)制,無(wú)需修改原類的源碼,也無(wú)需創(chuàng)建子類。其底層實(shí)現(xiàn)依賴于編譯期的結(jié)構(gòu)體定義和運(yùn)行時(shí)(Runtime)的動(dòng)態(tài)合并邏輯,核心原理可從編譯期結(jié)構(gòu)運(yùn)行時(shí)加載兩個(gè)階段解析。

一、編譯期:Category的底層結(jié)構(gòu)

編譯時(shí),每個(gè)Category會(huì)被編譯器轉(zhuǎn)換為一個(gè)名為category_t的結(jié)構(gòu)體(定義在Objective-C Runtime源碼的objc-runtime-new.h中),存儲(chǔ)分類的核心信息。簡(jiǎn)化后的結(jié)構(gòu)如下:

struct category_t {
    const char *name;               // 所屬類的類名(如"UIViewController")
    classref_t cls;                 // 所屬類的指針(編譯時(shí)為NULL,運(yùn)行時(shí)綁定)
    struct method_list_t *instanceMethods;  // 實(shí)例方法列表(分類中定義的-方法)
    struct method_list_t *classMethods;     // 類方法列表(分類中定義的+方法)
    struct protocol_list_t *protocols;      // 分類實(shí)現(xiàn)的協(xié)議列表
    struct property_list_t *instanceProperties;  // 分類中聲明的屬性列表
    // ... 其他字段(如版本信息)
};
  • 關(guān)鍵字段說(shuō)明
    • name:標(biāo)識(shí)該分類屬于哪個(gè)類(如為UIViewController添加分類,name就是"UIViewController")。
    • instanceMethods/classMethods:分別存儲(chǔ)分類中定義的實(shí)例方法和類方法(方法列表是method_t結(jié)構(gòu)體的數(shù)組,每個(gè)method_t包含方法名、實(shí)現(xiàn)、類型編碼等)。
    • instanceProperties:存儲(chǔ)分類中聲明的屬性(僅包含屬性名、類型等元信息,不包含實(shí)例變量,這也是分類不能直接添加實(shí)例變量的原因)。
    • protocols:存儲(chǔ)分類實(shí)現(xiàn)的協(xié)議,用于運(yùn)行時(shí)將協(xié)議添加到原類中。

二、運(yùn)行時(shí):Category與原類的合并

Category的核心功能(為原類添加方法/屬性/協(xié)議)并非在編譯期完成,而是在程序啟動(dòng)時(shí)(runtime初始化階段) 通過(guò)Runtime的邏輯動(dòng)態(tài)合并到原類中。具體流程如下:

1. 加載所有Category

程序啟動(dòng)時(shí),Runtime會(huì)加載可執(zhí)行文件(Mach-O)和所有依賴的動(dòng)態(tài)庫(kù),通過(guò)_read_images函數(shù)(負(fù)責(zé)加載類和分類的核心函數(shù))掃描其中的__DATA段,找到所有category_t結(jié)構(gòu)體(編譯時(shí)會(huì)被存放在這里)。

2. 綁定Category到原類

對(duì)于每個(gè)category_t,Runtime會(huì)根據(jù)其name字段找到對(duì)應(yīng)的原類(cls字段此時(shí)被賦值為原類的指針)。例如,name"UIViewController"的分類,會(huì)綁定到UIViewController類。

3. 合并方法/屬性/協(xié)議到原類

Runtime通過(guò)attachCategories函數(shù),將Category中的方法、屬性、協(xié)議合并到原類的對(duì)應(yīng)列表中,核心邏輯如下:

(1)方法合并(最核心)

  • 實(shí)例方法:合并到原類的class_rw_t結(jié)構(gòu)體的methods列表中(class_rw_t是類的“可讀寫數(shù)據(jù)”,包含方法、屬性、協(xié)議等動(dòng)態(tài)信息)。

  • 類方法:合并到原類的元類(meta-class)class_rw_tmethods列表中(因?yàn)轭惙椒ū举|(zhì)是元類的實(shí)例方法)。

  • 合并規(guī)則
    Category的方法會(huì)被插入到原類方法列表的前面(而非替換)。這意味著:

    • 若Category與原類有同名方法,調(diào)用時(shí)會(huì)優(yōu)先執(zhí)行Category的方法(因?yàn)椴檎曳椒〞r(shí)從列表頭部開始,找到第一個(gè)匹配的就返回);
    • 原類的方法并未被刪除,只是被“隱藏”(可通過(guò)Runtime的class_getInstanceMethod強(qiáng)制獲取原類方法)。

    示例:原類A有方法-foo,分類A+Test也有-foo,合并后A的方法列表為[A+Test的-foo, A的-foo],調(diào)用[A new] foo時(shí)執(zhí)行分類的實(shí)現(xiàn)。

(2)屬性合并

Category中聲明的屬性(如@property (nonatomic, copy) NSString *test;)會(huì)被合并到原類的class_rw_tproperties列表中,但不會(huì)自動(dòng)生成實(shí)例變量(ivar)和setter/getter實(shí)現(xiàn)。

  • 若需讓屬性生效,需手動(dòng)實(shí)現(xiàn)setter/getter(或通過(guò)@dynamic延遲實(shí)現(xiàn)),或通過(guò)關(guān)聯(lián)對(duì)象(Associated Objects) 模擬實(shí)例變量存儲(chǔ)(如objc_setAssociatedObject)。

(3)協(xié)議合并

Category實(shí)現(xiàn)的協(xié)議會(huì)被合并到原類的class_rw_tprotocols列表中,效果等同于原類直接實(shí)現(xiàn)了這些協(xié)議(可通過(guò)class_conformsToProtocol檢測(cè))。

4. 多個(gè)Category的優(yōu)先級(jí)

若多個(gè)Category為同一類添加了同名方法,最終調(diào)用哪個(gè)取決于編譯順序

  • Xcode中,編譯順序在Target > Build Phases > Compile Sources中定義,最后編譯的Category的方法會(huì)被插入到方法列表的最前面,因此會(huì)被優(yōu)先調(diào)用。

三、為什么Category不能添加實(shí)例變量?

實(shí)例變量(ivar)的內(nèi)存布局在編譯期就已確定(存儲(chǔ)在類的class_ro_t結(jié)構(gòu)體的ivars中,class_ro_t是類的“只讀數(shù)據(jù)”,編譯后不可修改)。
而Category是在運(yùn)行時(shí)才被加載并合并到原類中,此時(shí)原類的內(nèi)存布局已固定,無(wú)法動(dòng)態(tài)擴(kuò)展ivars的內(nèi)存空間,因此不能直接添加實(shí)例變量。

若需“模擬”添加變量,需通過(guò)Runtime的關(guān)聯(lián)對(duì)象(Associated Objects) 機(jī)制,將變量存儲(chǔ)在一個(gè)全局哈希表中,與原類實(shí)例綁定(本質(zhì)并非真正的實(shí)例變量)。

四、Category與Class Extension的區(qū)別

常有人混淆兩者,核心差異在于合并時(shí)機(jī)功能范圍

特性 Category(分類) Class Extension(類擴(kuò)展)
合并時(shí)機(jī) 運(yùn)行時(shí)(動(dòng)態(tài)合并) 編譯期(與原類一起編譯)
能否添加實(shí)例變量 不能(需用關(guān)聯(lián)對(duì)象模擬) 能(直接聲明@private等變量)
能否添加方法 能(實(shí)例方法/類方法) 能(通常用于擴(kuò)展私有方法)
能否聲明屬性 能(但需手動(dòng)實(shí)現(xiàn)setter/getter) 能(編譯器自動(dòng)生成setter/getter)
文件名規(guī)范 原類名+分類名(如A+Test.h 通常在原類.m中定義(無(wú)單獨(dú)文件)

總結(jié)

Category的底層原理可概括為:

  1. 編譯期:被打包為category_t結(jié)構(gòu)體,存儲(chǔ)方法、屬性、協(xié)議等信息;
  2. 運(yùn)行時(shí):Runtime通過(guò)attachCategories函數(shù)將其內(nèi)容合并到原類(實(shí)例方法到類,類方法到元類),且Category的方法優(yōu)先級(jí)高于原類同名方法;
  3. 限制:因類的內(nèi)存布局編譯期固定,無(wú)法添加實(shí)例變量,需通過(guò)關(guān)聯(lián)對(duì)象模擬。

這一機(jī)制使得Objective-C具備了靈活的動(dòng)態(tài)擴(kuò)展能力,也是iOS開發(fā)中“無(wú)侵入式擴(kuò)展”的核心實(shí)現(xiàn)方式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容