本文章將記錄有關(guān) Category(分類)和類擴(kuò)展(extension)、關(guān)聯(lián)對象的特性箩朴,如有錯誤歡迎指出~
Category(分類)
分類的應(yīng)用在App的開發(fā)中是非常廣泛的,它可以動態(tài)地為已有類添加新行為炸庞。
我們平常都是使用分類來對系統(tǒng)的類封裝一些小功能,如NSString判空處理等查牌,可以看下 ibireme大神開源的這個庫YYCategories,都是針對系統(tǒng)的類使用分類拓展的小功能纸颜,很實(shí)用。再來看看業(yè)界聞名的空白頁框架DZNEmptyDataSet绎橘,它就是通過對 UIScrollView
使用分類功能,非常完美称鳞、無侵入的解決了無數(shù)據(jù)時涮较,避免白屏的尷尬胡岔,改善用戶體驗(yàn)。真的是居家旅行必備之利器靶瘸,強(qiáng)大~
隨著業(yè)務(wù)的增長,.m文件會越來越膨脹怨咪,膨脹到你開始撓頭屋剑。我們也可以利用分類的特性诗眨,將類的實(shí)現(xiàn)分開在幾個不同的文件里面,好處顯而易見:
- 減少單個文件的體積
- 把不同的功能分配到不同的分類里,便于管理
- 可以按需加載想要的分類
通常巍膘,我會添加一些 AppDelegate
的分類,比如AppDelegate+Services
,專門用來初始化各種第三方等等峡懈。
Category 的結(jié)構(gòu)
在Objective-C
中璃饱,所有的類和對象在runtime層都是用struct表示的肪康,category也是如此,它的結(jié)構(gòu)體為category_t
typedef struct category_t {
const char *name; // 類名
classref_t cls; // 類
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; // 添加的所有屬性
} category_t;
從結(jié)構(gòu)體可以看出磷支,分類能
- 給類添加實(shí)例方法 (instanceMethod)
- 給類添加類方法 (classMethod)
- 實(shí)現(xiàn)協(xié)議 (protocol)
- 添加屬性 (instancePropertie)
但是不能添加實(shí)例變量,即無法自動生成實(shí)例變量的setter和getter方法雾狈。當(dāng)然,我們可以通過關(guān)聯(lián)對象來實(shí)現(xiàn)分類對實(shí)例變量的添加箍邮,請看后面章節(jié)~
Category的方法會“覆蓋”掉原來類的同名方法茉帅?
這個問題的分析锭弊,可以看下美圖技術(shù)團(tuán)隊(duì)的這篇文章擂错,很詳細(xì)。這里只記錄一下總結(jié):
Category的方法沒有“完全替換掉”原來類已經(jīng)有的方法钮呀,也就是說如果Category和原來類都有methodA剑鞍,那么Category附加完成之后爽醋,類的方法列表里會有兩個methodA
Category的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的后面蚂四,這也就是我們平常所說的Category的方法會“覆蓋”掉原來類的同名方法,這是因?yàn)檫\(yùn)行時在查找方法的時候是順著方法列表的順序查找的遂赠,它只要一找到對應(yīng)名字的方法久妆,很開心的返回了跷睦,不會在理會后面的同名方法。
同名方法的調(diào)用抑诸,是根據(jù)編譯順序決定的烂琴,對于“覆蓋”掉的方法,會先找到最后一個編譯的category里的對應(yīng)方法奸绷。可查看項(xiàng)目的 Build Phases -> Compile Sources健盒,位置越往后绒瘦,越晚編譯扣癣。
類擴(kuò)展(extension)
extension的別名有很多,擴(kuò)展父虑、延展该酗、匿名分類士嚎。它就是類的一部分,在編譯期和頭文件里的@interface
以及實(shí)現(xiàn)文件里的@implement
一起形成一個完整的類莱衩,它伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡笨蚁。在viewController.m
文件中它長這個樣子:
@interface ViewController ()
@end
是不是特別的熟悉~
沒錯睹晒,它看起來很像一個匿名的category括细。我們一般用來聲明私有方法伪很,私有屬性和私有成員變量奋单。
extension的應(yīng)用很簡單, 我們基本天天都在用览濒。需要注意的點(diǎn)是它和category的區(qū)別:
- extension 在編譯期決議, category在運(yùn)行期決議匾七。
關(guān)聯(lián)對象(Associated Object)
如上所述絮短,category是無法為分類添加實(shí)例變量的昨忆,但是在實(shí)際開發(fā)中往往需要在分類中添加和對象關(guān)聯(lián)的值。這時我們可以利用runtime的特性,通過關(guān)聯(lián)對象來實(shí)現(xiàn)為分類添加實(shí)例變量的功能席里。
關(guān)聯(lián)對象是指某個對象通過一個唯一的key連接到一個類的實(shí)例上。
看下runtime提供給我們的3個API方法:
// 關(guān)聯(lián)對象奖磁,傳入 nil 則可以移除已有的關(guān)聯(lián)對象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// 獲取關(guān)聯(lián)的對象
id objc_getAssociatedObject(id object, const void *key)
/** 移除關(guān)聯(lián)的對象。這個方法會移除一個對象的所有關(guān)聯(lián)對象咖为,將該對象恢復(fù)成“原始”狀態(tài)秕狰。
這樣做就很有可能把別人添加的關(guān)聯(lián)對象也一并移除躁染,所以我們通常用不上這個方法。
一般的做法是通過給 objc_setAssociatedObject 方法傳入 nil 來移除某個已有的關(guān)聯(lián)對象吞彤。*/
void objc_removeAssociatedObjects(id object)
參數(shù)說明:
- id object:被關(guān)聯(lián)的對象
- const void *key:關(guān)聯(lián)的key我衬,要求唯一饰恕。一般用
selector
,使用getter
方法的名稱作為key
值埋嵌。 - id value:關(guān)聯(lián)的對象
- objc_AssociationPolicy policy:內(nèi)存管理的策略
objc_AssociationPolicy的枚舉值和相關(guān)說明:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 指定一個弱引用相關(guān)聯(lián)的對象
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關(guān)對象的強(qiáng)引用,非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相關(guān)的對象被復(fù)制莉恼,非原子性
OBJC_ASSOCIATION_RETAIN = 01401, // 指定相關(guān)對象的強(qiáng)引用,原子性
OBJC_ASSOCIATION_COPY = 01403 // 指定相關(guān)的對象被復(fù)制俐银,原子性
};
在絕大多數(shù)情況下,我們都會使用 OBJC_ASSOCIATION_RETAIN_NONATOMIC
的關(guān)聯(lián)策略端仰,這可以保證我們持有關(guān)聯(lián)對象。
看一下簡單的應(yīng)用:
為ViewController
的分類 ViewController+AssociatedObjects
添加一個字符串類型的成員變量荔烧。
@interface ViewController (AssociatedObjects)
@property (strong, nonatomic) NSString *associatedObject;
@end
@implementation ViewController (AssociatedObjects)
- (NSString *)associatedObject {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setassociatedObject:(NSString *)associatedObject {
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
如果想了解一下關(guān)聯(lián)對象底層的實(shí)現(xiàn)原理,可以看下雷純峰大神的 Objective-C Associated Objects 的實(shí)現(xiàn)原理鹤竭,這里只記錄一些總結(jié):
Q : 關(guān)聯(lián)對象被存儲在什么地方,是不是存放在被關(guān)聯(lián)對象本身的內(nèi)存中臀稚?
A : 關(guān)聯(lián)對象與被關(guān)聯(lián)對象本身的存儲并沒有直接的關(guān)系,它是存儲在單獨(dú)的哈希表中的。所有的關(guān)聯(lián)對象都由AssociationsManager
管理窜管,它是由一個靜態(tài)AssociationsHashMap
來存儲所有的關(guān)聯(lián)對象的。這相當(dāng)于把所有對象的關(guān)聯(lián)對象都存在一個全局map里面幕帆。而map的的key是這個對象的指針地址(任意兩個不同對象的指針地址一定是不同的)获搏,而這個map的value又是另外一個AssociationsHashMap失乾,里面保存了關(guān)聯(lián)對象的key對。
Q : 關(guān)聯(lián)對象的生命周期是怎樣的碱茁,什么時候被釋放裸卫,什么時候被移除早芭?
A : 關(guān)聯(lián)對象的釋放時機(jī)與移除時機(jī)并不總是一致。