(一)Category
特點(diǎn):
category只能給某個(gè)已有的類(lèi)擴(kuò)充方法,不能擴(kuò)充成員變量。
category中也可以添加屬性,只不過(guò)@property只會(huì)生成setter和getter的聲明,不會(huì)生成setter和getter的實(shí)現(xiàn)以及成員變量。
如果category中的方法和類(lèi)中原有方法同名,運(yùn)行時(shí)會(huì)優(yōu)先調(diào)用category中的方法。也就是,category中的方法會(huì)覆蓋掉類(lèi)中原有的方法。所以開(kāi)發(fā)中盡量保證不要讓分類(lèi)中的方法和原有類(lèi)中的方法名相同。避免出現(xiàn)這種情況的解決方案是給分類(lèi)的方法名統(tǒng)一添加前綴。比如category_。
如果多個(gè)category中存在同名的方法,運(yùn)行時(shí)到底調(diào)用哪個(gè)方法由編譯器決定,最后一個(gè)參與編譯的方法會(huì)被調(diào)用。這個(gè)要看在xcode配置中,他們的.m 是在前還是在后,放在上面的先編譯,那么后面編譯的category同名方法就會(huì)被調(diào)用。
結(jié)構(gòu):
Category不允許為已有的類(lèi)添加新的成員變量,實(shí)際上允許添加屬性的,同樣可以使用@property,但是不會(huì)生成_變量(帶下劃線的成員變量),也不會(huì)生成添加屬性的getter和setter方法,所以,盡管添加了屬性,也無(wú)法使用點(diǎn)語(yǔ)法調(diào)用getter和setter方法。
我們看下category的結(jié)構(gòu):
typedef?struct?category_t?{
const?char?*name;??//類(lèi)的名字
classref_t?cls;??//類(lèi)
struct?method_list_t?*instanceMethods;??//category中所有給類(lèi)添加的實(shí)例方法的列表
struct?method_list_t?*classMethods;??//category中所有添加的類(lèi)方法的列表
struct?protocol_list_t?*protocols;??//category實(shí)現(xiàn)的所有協(xié)議的列表
struct?property_list_t?*instanceProperties;??//category中添加的所有屬性
}?category_t;
我們看到它是有instanceProperties這樣一個(gè)數(shù)組的,那么為什么說(shuō)無(wú)法添加屬性,其實(shí)是添加后不會(huì)自動(dòng)生成get,set方法而已,所以在使用 點(diǎn)語(yǔ)法的時(shí)候,回報(bào)找不到方法的錯(cuò)誤提示。
因此我們需要使用runtime動(dòng)態(tài)的給category添加對(duì)應(yīng)的get,set方法。
添加屬性的案例,比如給一個(gè)分類(lèi)添加 blog這樣一個(gè)屬性:
@property (nonatomic, copy) NSString *blog;
- (NSString *)blog
{
????// 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值。
????return objc_getAssociatedObject(self, key);
}
/**
blog的setter方法
*/
- (void)setBlog:(NSString *)blog
{
????// 第一個(gè)參數(shù):給哪個(gè)對(duì)象添加關(guān)聯(lián)
????// 第二個(gè)參數(shù):關(guān)聯(lián)的key,通過(guò)這個(gè)key獲取
????// 第三個(gè)參數(shù):關(guān)聯(lián)的value
????// 第四個(gè)參數(shù):關(guān)聯(lián)的策略
????objc_setAssociatedObject(self, key, blog, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
另外需要注意的有兩點(diǎn):
1)、category的方法沒(méi)有“完全替換掉”原來(lái)類(lèi)已經(jīng)有的方法,也就是說(shuō)如果category和原來(lái)類(lèi)都有methodA,那么category附加完成之后,類(lèi)的方法列表里會(huì)有兩個(gè)methodA。
2)、category的方法被放到了新方法列表的前面,而原來(lái)類(lèi)的方法被放到了新方法列表的后面,這也就是我們平常所說(shuō)的category的方法會(huì)“覆蓋”掉原來(lái)類(lèi)的同名方法,這是因?yàn)檫\(yùn)行時(shí)在查找方法的時(shí)候是順著方法列表的順序查找的,它只要一找到對(duì)應(yīng)名字的方法,就會(huì)罷休,殊不知后面可能還有一樣名字的方法。
(二)Extension
extension被開(kāi)發(fā)者稱(chēng)之為擴(kuò)展、延展、匿名分類(lèi)。extension看起來(lái)很像一個(gè)匿名的category,但是extension和category幾乎完全是兩個(gè)東西。和category不同的是extension不但可以聲明方法,還可以聲明屬性、成員變量。extension一般用于聲明私有方法,私有屬性,私有成員變量。
比如很的.m 文件里面會(huì)在implementation 上面加個(gè)
@interface viewModel() //這就是extension,可以添加屬性, 方法一般用于私有方法,私有屬性,成員變量
@implementation ViewModel
(三)category和extension的區(qū)別
就category和extension的區(qū)別來(lái)看,我們可以推導(dǎo)出一個(gè)明顯的事實(shí),extension可以添加實(shí)例變量,而category是無(wú)法添加實(shí)例變量的(因?yàn)樵谶\(yùn)行期,對(duì)象的內(nèi)存布局已經(jīng)確定,如果添加實(shí)例變量就會(huì)破壞類(lèi)的內(nèi)部布局,這對(duì)編譯型語(yǔ)言來(lái)說(shuō)是災(zāi)難性的)。
extension在編譯期決議,它就是類(lèi)的一部分,但是category則完全不一樣,它是在運(yùn)行期決議的。extension在編譯期和頭文件里的@interface以及實(shí)現(xiàn)文件里的@implement一起形成一個(gè)完整的類(lèi),它、extension伴隨類(lèi)的產(chǎn)生而產(chǎn)生,亦隨之一起消亡。
extension一般用來(lái)隱藏類(lèi)的私有信息,你必須有一個(gè)類(lèi)的源碼才能為一個(gè)類(lèi)添加extension,所以你無(wú)法為系統(tǒng)的類(lèi)比如NSString添加extension,除非創(chuàng)建子類(lèi)再添加extension。而category不需要有類(lèi)的源碼,我們可以給系統(tǒng)提供的類(lèi)添加category。
extension可以添加實(shí)例變量,而category不可以。
extension和category都可以添加屬性,但是category的屬性不能生成成員變量和getter、setter方法的實(shí)現(xiàn)。
補(bǔ)充:
那么category 中關(guān)聯(lián)的屬性是存放在哪里呢,又如何銷(xiāo)毀:
從runtime的源碼中可以看到,這里其實(shí)所有的關(guān)聯(lián)對(duì)象都由AssociationsManager管理,AssociationsManager里面是由一個(gè)靜態(tài)AssociationsHashMap來(lái)存儲(chǔ)所有的關(guān)聯(lián)對(duì)象的。這相當(dāng)于把所有對(duì)象的關(guān)聯(lián)對(duì)象都存在一個(gè)全局map里面。而map的的key是這個(gè)對(duì)象的指針地址(任意兩個(gè)不同對(duì)象的指針地址一定是不同的),而這個(gè)map的value又是另外一個(gè)AssociationsHashMap,里面保存了關(guān)聯(lián)對(duì)象的kv對(duì)。
銷(xiāo)毀:runtime的銷(xiāo)毀對(duì)象函數(shù)objc_destructInstance里面會(huì)判斷這個(gè)對(duì)象有沒(méi)有關(guān)聯(lián)對(duì)象,如果有,會(huì)調(diào)用_object_remove_assocations做關(guān)聯(lián)對(duì)象的清理工作。
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;}}