柚子快報(bào)激活碼778899分享:iOS——分類、擴(kuò)展和關(guān)聯(lián)對(duì)象
柚子快報(bào)激活碼778899分享:iOS——分類、擴(kuò)展和關(guān)聯(lián)對(duì)象
前情回顧
回顧一下什么是分類、什么是擴(kuò)展: 分類(Category)和類擴(kuò)展(Extension)是兩種常見(jiàn)的代碼組織方式,用于擴(kuò)展類的功能。
分類(Category)
分類是一種將類的實(shí)現(xiàn)分散到多個(gè)源文件的方式。通過(guò)使用分類,你可以將一個(gè)類的實(shí)現(xiàn)分散到多個(gè)源文件中。分類可以為現(xiàn)有的類添加新的方法,這使得你可以向現(xiàn)有的類添加自己的方法。 分類不能添加實(shí)例變量,只能添加方法。 分類的特性是可以在運(yùn)行時(shí)階段動(dòng)態(tài)的為已有的類添加新行為。 分類就是對(duì)裝飾模式的一種具體實(shí)現(xiàn)。它的主要作用是在不改變?cè)蓄惖那疤嵯拢瑒?dòng)態(tài)地給這個(gè)類添加一些方法。 分類也可以把framework私有方法公開(kāi)化
現(xiàn)在我們通過(guò)源碼看一下分類:
struct _category_t {
const char *name; // 分類的名稱
struct _class_t *cls; // 分類所屬的類
const struct _method_list_t *instance_methods; // 分類中定義的實(shí)例方法列表
const struct _method_list_t *class_methods; // 分類中定義的類方法列表
const struct _protocol_list_t *protocols; // 分類實(shí)現(xiàn)的協(xié)議列表
const struct _prop_list_t *properties; // 分類中定義的屬性列表
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
從結(jié)構(gòu)體可以知道,分類可以添加屬性,但是只會(huì)生成這些屬性的getter和setter方法的聲明,并不會(huì)自動(dòng)實(shí)現(xiàn)這些方法。也就是說(shuō),如果你在分類中添加了一個(gè)屬性,你還需要自己去實(shí)現(xiàn)這個(gè)屬性的getter和setter方法。分類不像類,它沒(méi)有成員變量列表,所以你不能在分類中添加成員變量。分類中的方法可以在運(yùn)行時(shí)動(dòng)態(tài)改變,但結(jié)構(gòu)體不能。如果你想要在運(yùn)行時(shí)給原來(lái)的結(jié)構(gòu)體添加一個(gè)屬性,你只能通過(guò)關(guān)聯(lián)對(duì)象的方式。關(guān)聯(lián)對(duì)象的本質(zhì)是在類的定義之外為類增加額外的存儲(chǔ)空間,實(shí)現(xiàn)了一種映射關(guān)系。
擴(kuò)展(Extension)
類擴(kuò)展與分類類似,有時(shí)候也被稱為匿名分類,但是類擴(kuò)展可以添加實(shí)例變量和屬性。與分類不同,類擴(kuò)展的方法和屬性必須在類的主實(shí)現(xiàn)文件中實(shí)現(xiàn)。擴(kuò)展中聲明的所有方法和屬性必須在主類中實(shí)現(xiàn),否則會(huì)導(dǎo)致編譯錯(cuò)誤。此外,類擴(kuò)展中添加的實(shí)例變量默認(rèn)為@private類型,其使用范圍只能在自身類中。 擴(kuò)展是在編譯階段與該類同時(shí)編譯的,是類的一部分。擴(kuò)展中聲明的方法只能在該類的@implementation中實(shí)現(xiàn)。所以這也就意味著我們無(wú)法對(duì)系統(tǒng)的類使用擴(kuò)展。
分類和擴(kuò)展的區(qū)別
分類原則上只能增加方法,但是也可以通過(guò)關(guān)聯(lián)屬性增加屬性拓展可以增加方法和屬性,都是私有的。擴(kuò)展只能在自身類中使用,而不是子類或者其他地方。類擴(kuò)展是在編譯階段添加到類中,而分類是在運(yùn)行時(shí)添加到類中
關(guān)聯(lián)對(duì)象
在OC中,關(guān)聯(lián)對(duì)象是一種動(dòng)態(tài)給對(duì)象添加屬性的機(jī)制。 正如我們知道的,OC的類可以有實(shí)例變量和屬性,這些都是在編譯時(shí)期就已經(jīng)確定的。然而,有時(shí)候我們可能希望在運(yùn)行時(shí)給對(duì)象動(dòng)態(tài)添加一些屬性,就可以用關(guān)聯(lián)對(duì)象。 關(guān)聯(lián)對(duì)象還可以為分類添加屬性: Category(分類)的底層結(jié)構(gòu)中,沒(méi)有成員變量(ivar),因此不能給分類添加成員變量;在分類里面聲明的屬性,只會(huì)生成 get/set 方法的聲明,沒(méi)有方法的實(shí)現(xiàn),所以我們不能直接給分類添加成員變量,但是可以間接實(shí)現(xiàn),那就是使用關(guān)聯(lián)對(duì)象
關(guān)聯(lián)對(duì)象實(shí)際上是通過(guò)Objective-C的運(yùn)行時(shí)系統(tǒng)實(shí)現(xiàn)的。使用以下三個(gè)函數(shù)來(lái)添加、獲取或者刪除關(guān)聯(lián)對(duì)象:
給對(duì)象添加關(guān)聯(lián)對(duì)象:
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
其中objc_AssociationPolicy policy 是指關(guān)聯(lián)策略。 獲取對(duì)象的關(guān)聯(lián)對(duì)象:
id objc_getAssociatedObject(id object, const void * key)
刪除對(duì)象的所有關(guān)聯(lián)對(duì)象:
void objc_removeAssociatedObjects(id object)
使用方法:
創(chuàng)建ZSXPerson類,以及它的分類ZSXPerson+TextZSXPerson+Text.h中聲明想要添加的屬性ZSXPerson+Text.m中使用關(guān)聯(lián)對(duì)象API實(shí)現(xiàn) get/set 方法
修飾符
關(guān)聯(lián)對(duì)象并不會(huì)改變對(duì)象的類或類的定義,它們只是在運(yùn)行時(shí)環(huán)境中與特定的對(duì)象關(guān)聯(lián)在一起。關(guān)聯(lián)對(duì)象的生命周期與對(duì)象本身的生命周期相同,當(dāng)對(duì)象被銷毀時(shí),其關(guān)聯(lián)的對(duì)象也會(huì)被銷毀。
為分類添加關(guān)聯(lián)對(duì)象的本質(zhì)
關(guān)聯(lián)對(duì)象的本質(zhì)
每一個(gè)對(duì)象的關(guān)聯(lián)對(duì)象實(shí)際上都存儲(chǔ)在AssociationHashMap內(nèi)。所有對(duì)象的關(guān)聯(lián)內(nèi)容都在同一個(gè)全局容器中。 關(guān)聯(lián)對(duì)象由AssociationManager管理并在AssociationHashMap存儲(chǔ)。 所以說(shuō), 假如給分類添加了一個(gè)關(guān)聯(lián)對(duì)象, 那么該關(guān)聯(lián)內(nèi)容既不需要分類管理, 也不是由原類管理。
class AssociationsManager {
static spinlock_t _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { _lock.lock(); }
~AssociationsManager() { _lock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
AssociationsHashMap內(nèi)部維護(hù)了一個(gè)ObjectAssociationMap哈希表:
class AssociationsHashMap : public unordered_map
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
ObjectAssociationMap內(nèi)部維護(hù)了一個(gè)ObjcAssociation哈希表:
class ObjectAssociationMap : public std::map
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
key的常見(jiàn)用法
使用的get方法的@selecor作為key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)objc_getAssociatedObject(obj, @selector(getter))
使用指針的地址作為key
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)objc_getAssociatedObject(obj, MyKey)
使用static字符作為key
static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)objc_getAssociatedObject(obj, &MyKey)
使用屬性名作為key
objc_setAssociatedObject(obj, @“property”, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);objc_getAssociatedObject(obj, @“property”);
錯(cuò)誤用法舉例
static void *MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)objc_getAssociatedObject(obj, MyKey) 如上寫(xiě)法,沒(méi)有給*MyKey,他相當(dāng)于是NULL,這時(shí)候,如果再有另一個(gè)屬性使用Keystatic void *OtherKey;,MyKey和OtherKey都是 NULL,這就相當(dāng)于把多個(gè)屬性都與NULLKey關(guān)聯(lián),明顯就出問(wèn)題了
關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)原理
學(xué)習(xí)OC對(duì)象本質(zhì)時(shí),我們知道對(duì)象實(shí)際被轉(zhuǎn)化成struct ClassName_IMPL結(jié)構(gòu)體,對(duì)象的成員變量的值保存在該結(jié)構(gòu)體中。 使用關(guān)聯(lián)對(duì)象給分類添加的成員變量,他并不是保存在該結(jié)構(gòu)體下,因?yàn)镃ategory的底層結(jié)構(gòu)中并沒(méi)有ivar。 他是存儲(chǔ)在全局的統(tǒng)一的一個(gè)AssociationsManager中。
核心對(duì)象
AssociationsManagerAssociationsHashMapObjectAssociationMapObjcAssociation
下面來(lái)看它們之間的關(guān)系:
AssociationsManager
AssociationsManager 是管理所有關(guān)聯(lián)對(duì)象的中心管理器。它負(fù)責(zé)存儲(chǔ)和檢索關(guān)聯(lián)對(duì)象的整體結(jié)構(gòu)。它是一個(gè)單例,確保在整個(gè)運(yùn)行時(shí)環(huán)境中只有一個(gè)實(shí)例。
職責(zé):管理和維護(hù)所有對(duì)象的關(guān)聯(lián)數(shù)據(jù)。
AssociationsHashMap
AssociationsHashMap 是一個(gè)哈希表,用于將對(duì)象映射到它們的關(guān)聯(lián)數(shù)據(jù)。它在內(nèi)部使用了一個(gè)哈希表,這個(gè)字段的 key 是被添加關(guān)聯(lián)對(duì)象的對(duì)象的地址,value 是個(gè)ObjectAssociationMap
職責(zé):高效地查找和存儲(chǔ)對(duì)象與其關(guān)聯(lián)數(shù)據(jù)之間的映射。
ObjectAssociationMap
ObjectAssociationMap 是一個(gè)結(jié)構(gòu)體或類,用于存儲(chǔ)單個(gè)對(duì)象的所有關(guān)聯(lián)數(shù)據(jù)。key 是我們調(diào)用objc_setAssociatedObject時(shí)傳入的 key,value 是ObjectAssociation
職責(zé):管理一個(gè)對(duì)象的所有關(guān)聯(lián)鍵及其對(duì)應(yīng)的關(guān)聯(lián)值。
ObjcAssociation
ObjcAssociation 是具體的關(guān)聯(lián)對(duì)象,包含實(shí)際的數(shù)據(jù)以及數(shù)據(jù)的存儲(chǔ)策略,_policy是我們?cè)O(shè)置的修飾符(比如:OBJC_ASSOCIATION_ASSIGN),_value是我們傳入的值value 等。
職責(zé):存儲(chǔ)實(shí)際的關(guān)聯(lián)數(shù)據(jù)以及定義數(shù)據(jù)的內(nèi)存管理語(yǔ)義。
工作流程
設(shè)置關(guān)聯(lián)對(duì)象:
調(diào)用 objc_setAssociatedObject。AssociationsManager 查找或創(chuàng)建與目標(biāo)對(duì)象相關(guān)的 ObjectAssociationMap。在 ObjectAssociationMap 中查找或創(chuàng)建對(duì)應(yīng)的 ObjcAssociation。將關(guān)聯(lián)值和存儲(chǔ)策略設(shè)置到 ObjcAssociation 中。 獲取關(guān)聯(lián)對(duì)象:
調(diào)用 objc_getAssociatedObject。AssociationsManager 查找與目標(biāo)對(duì)象相關(guān)的 ObjectAssociationMap。在 ObjectAssociationMap 中查找對(duì)應(yīng)的 ObjcAssociation。返回 ObjcAssociation 中存儲(chǔ)的關(guān)聯(lián)值。 移除關(guān)聯(lián)對(duì)象:
調(diào)用 objc_removeAssociatedObjects 或 objc_setAssociatedObject 設(shè)置為 nil。AssociationsManager 查找與目標(biāo)對(duì)象相關(guān)的 ObjectAssociationMap。從 ObjectAssociationMap 中移除對(duì)應(yīng)的 ObjcAssociation。如果 ObjectAssociationMap 為空,可能會(huì)移除整個(gè)映射以釋放資源。
objc_AssociationPolicy的值并沒(méi)有weak,這點(diǎn)我們?cè)谑褂玫臅r(shí)候需要注意
柚子快報(bào)激活碼778899分享:iOS——分類、擴(kuò)展和關(guān)聯(lián)對(duì)象
精彩文章
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。