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