筆者最近梳理iOS知識脈絡(luò),計劃寫一個名為“重識iOS”的系列恬总,內(nèi)容來自平時的學習筆記,參考了一些文章和書籍肚邢,融入自己的理解以記錄壹堰。歡迎交流指正拭卿。本文為第一篇:Category。
介紹
熟悉設(shè)計模式的開發(fā)人員應(yīng)該都知道裝飾模式(Decorator)贱纠,它是在不修改原代碼的基礎(chǔ)上進行拓展峻厚。iOS開發(fā)中category就是對裝飾模式的典型實踐。
Category的簡介
Category是Objective-C 2.0之后添加的語言特性谆焊,category的主要作用是為已經(jīng)存在的類添加方法目木。
Category的結(jié)構(gòu)
我們知道Objective-C中類和對象都是C結(jié)構(gòu),category的結(jié)構(gòu)如下:
struct _category_t {
const char *name; // 1
struct _class_t *cls; // 2
const struct _method_list_t *instance_methods; // 3
const struct _method_list_t *class_methods; // 4
const struct _protocol_list_t *protocols; // 5
const struct _prop_list_t *properties; // 6
};
-
name
主類的名字 -
cls
要擴展的類對象懊渡,編譯期沒有值刽射,運行期根據(jù)name
對應(yīng)到類對象 -
instance_methods
實例方法列表 -
class_methods
類方法列表 -
protocols
實現(xiàn)的協(xié)議列表,不常用但確實支持 -
properties
屬性列表剃执,可以定義屬性誓禁,不能合成實例變量,可通過關(guān)聯(lián)對象進行綁定肾档,與傳統(tǒng)實例變量是兩樣東西摹恰。
使用場景
- 為已經(jīng)存在的類添加方法(特別是為看不見源碼的系統(tǒng)類)
- 把類的實現(xiàn)分開在幾個不同的文件中,好處:
- 減少單個文件體積
- 功能分離
- 多人共同開發(fā)一個類
- 實現(xiàn)按需加載
特點(局限性)
- 不能添加實例變量(可通過runtime關(guān)聯(lián)對象)
注意:category可以添加屬性怒见,只是不能自動合成實例變量俗慈。
- 方法覆蓋:與主類同名的方法優(yōu)先級高于主類方法
注意:category并不是完全替換掉主類的同名方法,只是類的方法列表里會出現(xiàn)兩個名字一樣的方法遣耍,并且category的方法會排在本類方法的前面闺阱,運行時查找方法按照順序,一旦找到就停止舵变,也就出現(xiàn)了所謂的方法覆蓋酣溃。
Category與Extension
Category在 運行期決議 。category無法添加實例變量纪隙,因為在運行期對象的內(nèi)存布局已經(jīng)確定赊豌,添加實例變量會破壞類的內(nèi)部結(jié)構(gòu)。
Extension在 編譯器決議 绵咱。extension可以單獨創(chuàng)建生成 .h 文件碘饼,常寫在主類 .m 中作為類的一部分,一般用來隱藏類的私有信息悲伶,必須有一個類的源碼才能為其添加一個extension艾恼。
關(guān)聯(lián)對象
我們已經(jīng)知道category不能自動合成實例變量,但是可以通過runtime關(guān)聯(lián)對象的方式來實現(xiàn) setter
拢切、getter
蒂萎。
//.h文件
#import "MyClass.h"
@interface MyClass (Addition)
@property (nonatomic, copy) NSString *name;
@end
//.m文件
#import "MyClass+Addition.h"
static void *kNameKey = &kNameKey;
@implementation MyClass (Addition)
/**
* 設(shè)置關(guān)聯(lián)對象
*
* @param 需要被關(guān)聯(lián)的對象
* @param key 關(guān)聯(lián)對象的key 一般這樣設(shè)置static char key;
* @param value 被關(guān)聯(lián)對象的屬性,如果設(shè)置nil淮椰,就取消關(guān)聯(lián)
* @param policy 關(guān)聯(lián)策略,相當于屬性的內(nèi)存管理語義
*/
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, kNameKey, name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name {
return objc_getAssociatedObject(self, kNameKey);
}
@end
//objc_setAssociatedObject第4個參數(shù):策略的枚舉
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, //關(guān)聯(lián)對象的屬性是弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //關(guān)聯(lián)對象的屬性是強引用且關(guān)聯(lián)對象不使用原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //關(guān)聯(lián)對象的屬性是copy且關(guān)聯(lián)對象不使用原子性
OBJC_ASSOCIATION_RETAIN = 01401, //關(guān)聯(lián)對象的屬性是copy且關(guān)聯(lián)對象使用原子性
OBJC_ASSOCIATION_COPY = 01403 //關(guān)聯(lián)對象的屬性是copy且關(guān)聯(lián)對象使用原子性
};
延伸
- 1.在類的
+load
方法中可以調(diào)用category里聲明的方法嗎?
可以主穗,因為附加category到類的工作會先于 +load
方法的執(zhí)行泻拦。
- 2.類和category的
+load
方法調(diào)用順序是什么樣的?
+load
的執(zhí)行順序是:先類忽媒,后category争拐。而各個category的 +load
執(zhí)行順序是由編譯順序決定的。
- 3.關(guān)聯(lián)對象存在哪晦雨?如何存儲架曹?對象銷毀時候如何處理關(guān)聯(lián)對象?
所有的關(guān)聯(lián)對象都由 AssociationsManager
管理闹瞧, AssociationsManager
里面是由一個靜態(tài) AssociationsHashMap
來存儲所有的關(guān)聯(lián)對象的绑雄。
相當于把所有對象的關(guān)聯(lián)對象都存在一個全局 map
面。而 map
的 key
是這個對象的指針地址奥邮, value
一個 AssociationsHashMap
万牺,里面保存了關(guān)聯(lián)對象的鍵值對。
runtime的銷毀對象函數(shù) objc_destructInstance
里面會判斷這個對象有沒有關(guān)聯(lián)對象洽腺,如果有脚粟,會調(diào)用 _object_remove_assocations
做關(guān)聯(lián)對象的清理工作。
參考文章: