(一)Category
特點:
category只能給某個已有的類擴充方法斋泄,不能擴充成員變量杯瞻。
category中也可以添加屬性,只不過@property只會生成setter和getter的聲明是己,不會生成setter和getter的實現(xiàn)以及成員變量又兵。
如果category中的方法和類中原有方法同名,運行時會優(yōu)先調(diào)用category中的方法卒废。也就是沛厨,category中的方法會覆蓋掉類中原有的方法。所以開發(fā)中盡量保證不要讓分類中的方法和原有類中的方法名相同摔认。避免出現(xiàn)這種情況的解決方案是給分類的方法名統(tǒng)一添加前綴逆皮。比如category_。
如果多個category中存在同名的方法参袱,運行時到底調(diào)用哪個方法由編譯器決定电谣,最后一個參與編譯的方法會被調(diào)用。這個要看在xcode配置中抹蚀,他們的.m 是在前還是在后剿牺,放在上面的先編譯,那么后面編譯的category同名方法就會被調(diào)用环壤。
結構:
Category不允許為已有的類添加新的成員變量晒来,實際上允許添加屬性的,同樣可以使用@property郑现,但是不會生成_變量(帶下劃線的成員變量)湃崩,也不會生成添加屬性的getter和setter方法,所以接箫,盡管添加了屬性攒读,也無法使用點語法調(diào)用getter和setter方法。
我們看下category的結構:
typedef?struct?category_t?{
const?char?*name;??//類的名字
classref_t?cls;??//類
struct?method_list_t?*instanceMethods;??//category中所有給類添加的實例方法的列表
struct?method_list_t?*classMethods;??//category中所有添加的類方法的列表
struct?protocol_list_t?*protocols;??//category實現(xiàn)的所有協(xié)議的列表
struct?property_list_t?*instanceProperties;??//category中添加的所有屬性
}?category_t;
我們看到它是有instanceProperties這樣一個數(shù)組的辛友,那么為什么說無法添加屬性薄扁,其實是添加后不會自動生成get,set方法而已废累,所以在使用 點語法的時候邓梅,回報找不到方法的錯誤提示。
因此我們需要使用runtime動態(tài)的給category添加對應的get,set方法九默。
添加屬性的案例,比如給一個分類添加 blog這樣一個屬性:
@property (nonatomic, copy) NSString *blog;
- (NSString *)blog
{
????// 根據(jù)關聯(lián)的key震放,獲取關聯(lián)的值。
????return objc_getAssociatedObject(self, key);
}
/**
blog的setter方法
*/
- (void)setBlog:(NSString *)blog
{
????// 第一個參數(shù):給哪個對象添加關聯(lián)
????// 第二個參數(shù):關聯(lián)的key驼修,通過這個key獲取
????// 第三個參數(shù):關聯(lián)的value
????// 第四個參數(shù):關聯(lián)的策略
????objc_setAssociatedObject(self, key, blog, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
另外需要注意的有兩點:
1)殿遂、category的方法沒有“完全替換掉”原來類已經(jīng)有的方法,也就是說如果category和原來類都有methodA乙各,那么category附加完成之后墨礁,類的方法列表里會有兩個methodA。
2)耳峦、category的方法被放到了新方法列表的前面恩静,而原來類的方法被放到了新方法列表的后面,這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法,這是因為運行時在查找方法的時候是順著方法列表的順序查找的驶乾,它只要一找到對應名字的方法邑飒,就會罷休,殊不知后面可能還有一樣名字的方法级乐。
(二)Extension
extension被開發(fā)者稱之為擴展疙咸、延展、匿名分類风科。extension看起來很像一個匿名的category撒轮,但是extension和category幾乎完全是兩個東西。和category不同的是extension不但可以聲明方法贼穆,還可以聲明屬性题山、成員變量。extension一般用于聲明私有方法故痊,私有屬性顶瞳,私有成員變量。
比如很的.m 文件里面會在implementation 上面加個
@interface viewModel() //這就是extension崖蜜,可以添加屬性浊仆, 方法一般用于私有方法,私有屬性豫领,成員變量
@implementation ViewModel
(三)category和extension的區(qū)別
就category和extension的區(qū)別來看抡柿,我們可以推導出一個明顯的事實,extension可以添加實例變量等恐,而category是無法添加實例變量的(因為在運行期洲劣,對象的內(nèi)存布局已經(jīng)確定,如果添加實例變量就會破壞類的內(nèi)部布局课蔬,這對編譯型語言來說是災難性的)囱稽。
extension在編譯期決議,它就是類的一部分二跋,但是category則完全不一樣战惊,它是在運行期決議的。extension在編譯期和頭文件里的@interface以及實現(xiàn)文件里的@implement一起形成一個完整的類扎即,它吞获、extension伴隨類的產(chǎn)生而產(chǎn)生,亦隨之一起消亡谚鄙。
extension一般用來隱藏類的私有信息各拷,你必須有一個類的源碼才能為一個類添加extension,所以你無法為系統(tǒng)的類比如NSString添加extension闷营,除非創(chuàng)建子類再添加extension烤黍。而category不需要有類的源碼,我們可以給系統(tǒng)提供的類添加category。
extension可以添加實例變量速蕊,而category不可以栅炒。
extension和category都可以添加屬性裙顽,但是category的屬性不能生成成員變量和getter闹瞧、setter方法的實現(xiàn)这敬。
補充:
那么category 中關聯(lián)的屬性是存放在哪里呢筝闹,又如何銷毀:
從runtime的源碼中可以看到媳叨,這里其實所有的關聯(lián)對象都由AssociationsManager管理,AssociationsManager里面是由一個靜態(tài)AssociationsHashMap來存儲所有的關聯(lián)對象的关顷。這相當于把所有對象的關聯(lián)對象都存在一個全局map里面糊秆。而map的的key是這個對象的指針地址(任意兩個不同對象的指針地址一定是不同的),而這個map的value又是另外一個AssociationsHashMap议双,里面保存了關聯(lián)對象的kv對痘番。
銷毀:runtime的銷毀對象函數(shù)objc_destructInstance里面會判斷這個對象有沒有關聯(lián)對象,如果有平痰,會調(diào)用_object_remove_assocations做關聯(lián)對象的清理工作汞舱。
AssociationsManager定義如下:
class AssociationsManager {
? ? static OSSpinLock _lock;
? ? static AssociationsHashMap *_map;? ? ? ? ? ? ? // associative references:? object pointer -> PtrPtrHashMap.public:
? ? AssociationsManager()? { OSSpinLockLock(&_lock); }
? ? ~AssociationsManager()? { OSSpinLockUnlock(&_lock); }
? ? AssociationsHashMap &associations() {
? ? ? ? if (_map == NULL)
? ? ? ? ? ? _map = new AssociationsHashMap();
? ? ? ? return *_map;}}