前言
上一篇講了分類的本質(zhì)和底層原理,提到分類可以關(guān)聯(lián)對象添加屬性,也講到類擴展和分類的區(qū)別,這篇講一下 深入探索一下,類擴展和關(guān)聯(lián)對象的底層實現(xiàn)原理。
復(fù)習(xí):iOS 底層探索:類的加載下(分類)
準(zhǔn)備:調(diào)試代碼下載
一 锅移、 類擴展extension
1.1. 類擴展的創(chuàng)建方式
- 通過 command+N 新建 -> Objective-C File -> 選擇Extension
- 直接在類中書寫:永遠在聲明之后,在實現(xiàn)之前(需要在.m文件中書寫)
1.2. 類擴展的本質(zhì)
Clang 生成cpp文件:xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc AppDelegate.m
查看被編譯后的內(nèi)容如下:在編譯過程中饱搏,方法就直接添加到了 methodlist中非剃,作為類的一部分,即編譯時期直接添加到本類里面
本想通過源碼調(diào)試的推沸,結(jié)果電腦升級到最新系統(tǒng)版本之后备绽,源碼不過了,尷尬鬓催。肺素。。宇驾。以后補充吧
總結(jié):
類的擴展 在編譯器 會作為類的一部分倍靡,和類一起編譯進來
類的擴展只是聲明,依賴于當(dāng)前的主類课舍,沒有.m文件塌西,可以理解為一個·h文件
二 、關(guān)聯(lián)對象
之前分析分類的時候筝尾,講過分類添加屬性是無效的捡需,但是可以通過runtime的方式動態(tài)添加關(guān)聯(lián)對象。
舉個例子:
#import <objc/runtime.h>
// 本類
@interface HJPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation HJPerson
@end
// 分類
@interface HJPerson (EXT)
@property (nonatomic, copy) NSString * ext_name; // 屬性
@end
@implementation HJPerson(EXT)
- (void)setExt_name:(NSString *)ext_name {
/*給屬性`ext_name `忿等,動態(tài)添加set方法
1 .對象
2. 標(biāo)識符
3.value
4.屬性的策略,即nonatomic崔挖、atomic贸街、assign等
*/
objc_setAssociatedObject(self, "ext_name", ext_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)ext_name { // 給屬性`ext_name `,動態(tài)添加get方法
return objc_getAssociatedObject(self, "ext_name");
}
@end
其底層原理的實現(xiàn)狸相,主要分為兩部分:
動態(tài)設(shè)置
關(guān)聯(lián)屬性: objc_setAssociatedObject
(關(guān)聯(lián)對象薛匪,關(guān)聯(lián)屬性key,關(guān)聯(lián)屬性value脓鹃,策略)
動態(tài)讀取
關(guān)聯(lián)屬性:objc_getAssociatedObject
(關(guān)聯(lián)對象逸尖,關(guān)聯(lián)屬性key)
復(fù)習(xí):
很明顯我們這里看可以看到這個關(guān)聯(lián)屬性是通過這兩個方法寫入讀取的。那么回顧一下,我們正常寫的屬性是如何寫入讀取的呢娇跟?岩齿?
2.0 正常屬性的寫入
通過Clang 查看編譯代碼如下:
static NSString * _I_LGTeacher_ext_name(LGTeacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGTeacher$_ext_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
發(fā)現(xiàn)常規(guī)是調(diào)用objc_setProperty完成set方法,我們在源碼中檢查objc_setProperty的實現(xiàn):
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
進入reallySetProperty:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) __attribute__((always_inline));
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset); //根據(jù)讀源碼的經(jīng)驗苞俘,我們就可以想到+ offset 就是在做內(nèi)存偏移計算盹沈,通過類地址和偏移值讀到當(dāng)前屬性的地址,然后讀取到值
if (copy) {
newValue = [newValue copyWithZone:nil]; //copy 方法
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];//mutableCopy 方法
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);//如果等于新值 就返回新值
}
if (!atomic) {// 非原子操作直接賦值
oldValue = *slot;
*slot = newValue;
} else {// 原子操作加鎖之后再賦值
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue); //舊值release
}
總結(jié):正常屬性的讀瘸砸ァ:1. 通過地址讀取屬性 -> 2.新值retain -> 3.屬性賦值 -> 4.舊值release
2.1. objc_setAssociatedObject設(shè)值流程
// 進入源碼
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);
}
//繼續(xù)jump
static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
// 繼續(xù)jump
static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
進入_object_set_associative_reference
,查看policy的枚舉類型如下乞封,很明顯定義了屬性的類型assign
,copy
岗憋,retain
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
進入_object_set_associative_reference源碼實現(xiàn)
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
if (!object && !value) return;
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
//object封裝成一個數(shù)組結(jié)構(gòu)類型肃晚,類型為DisguisedPtr
DisguisedPtr<objc_object> disguised{(objc_object *)object};//相當(dāng)于包裝了一下 對象object,便于使用
// 包裝一下 policy - value
/*
class ObjcAssociation {
uintptr_t _policy;
id _value;
*/
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();// 根據(jù)策略類型進行處理 retain一個新值
//局部作用域空間
{
//初始化manager變量,相當(dāng)于自動調(diào)用AssociationsManager的析構(gòu)函數(shù)進行初始化
AssociationsManager manager;//并不是全場唯一仔戈,構(gòu)造函數(shù)中加鎖只是為了避免重復(fù)創(chuàng)建关串,在這里是可以初始化多個AssociationsManager變量的
AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全場唯一 內(nèi)存中獨一份。 因為 &associations 操作的是地址
if (value) {
//refs_result :從map表中讀取類的buckets (類對:key:為disguised 類標(biāo)志杂穷;value: 為類中關(guān)聯(lián)對象的信息)
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {//判斷第二個存不存在悍缠,即bool值是否為true ,為什么是second 得從associations.try_emplace 方法內(nèi)部查看
/* it's the first association we make 第一次建立關(guān)聯(lián)*/
object->setHasAssociatedObjects();//nonpointerIsa 耐量,標(biāo)記位true 即置isa指針的has_assoc屬性為true
}
/* establish or replace the association 建立或者替換關(guān)聯(lián)*/
auto &refs = refs_result.first->second; //得到一個空的桶子飞蚓,找到引用對象類型,即第一個元素的second值
auto result = refs.try_emplace(key, std::move(association));//查找當(dāng)前的key是否有association關(guān)聯(lián)對象
if (!result.second) {//如果結(jié)果不存在
association.swap(result.first->second);
}
} else {
//如果傳的是空值,則移除關(guān)聯(lián)廊蜒,相當(dāng)于移除
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();//調(diào)用 objc_release 釋放舊值
}
其整體流程如下:關(guān)鍵點的分析:
2.1.1 setHasAssociatedObjects()
設(shè)置關(guān)聯(lián)
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
newisa.has_assoc = true; // 這里給isa的has_assoc 做了一個標(biāo)記 趴拧,要考的
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
這里涉及一個面試題: 問關(guān)聯(lián)對象是否需要手動釋放?從這里就可以看出來山叮,在關(guān)聯(lián)對象的時候著榴,isa中記錄了是否有關(guān)聯(lián)對象。通過dealloc 釋放屁倔。
執(zhí)行流程: objc_object::rootDealloc() -> object_dispose()->objc_destructInstance()
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects(); //這里如果有關(guān)聯(lián)對象
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj); //就在這里釋放啦
obj->clearDeallocating();
}
return obj;
}
2.1.2 AssociationsHashMap
- 關(guān)聯(lián)屬性的信息會存在一個
AssociationsHashMap
類型的map中脑又,map中有很多的關(guān)聯(lián)對象key-value鍵值對,其中key為DisguisedPtr<objc_object>锐借,value類型是ObjectAssociationMap问麸,可以理解為一個類對應(yīng)一個:ObjectAssociationMap ; - 每個
ObjectAssociationMap
钞翔,也有很多key-value鍵值對严卖,其中key的類型為const void *,value的類型為ObjcAssociation 布轿,ObjcAssociation
是用于包裝polic
y和value
的一個類;
2.2 objc_getAssociatedObject 取值流程
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};//創(chuàng)建空的關(guān)聯(lián)對象
{
AssociationsManager manager;//創(chuàng)建一個AssociationsManager管理類
AssociationsHashMap &associations(manager.get());//獲取全局唯一的靜態(tài)哈希map
AssociationsHashMap::iterator i = associations.find((objc_object *)object);//找到迭代器福铅,即獲取buckets
if (i != associations.end()) {//如果這個迭代查詢器不是最后一個 獲取
ObjectAssociationMap &refs = i->second; //找到ObjectAssociationMap的迭代查詢器獲取一個經(jīng)過屬性修飾符修飾的value
ObjectAssociationMap::iterator j = refs.find(key);//根據(jù)key查找ObjectAssociationMap,即獲取bucket
if (j != refs.end()) {
association = j->second;//獲取ObjcAssociation
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();//返回value
}
其整體流程如下:本來想驗證的启具。結(jié)果我電腦系統(tǒng)升級了钮惠。源碼編譯不了规肴。所以只能等以后再驗證了缺猛。
2.3 補充: objc_removeAssociatedObjects移除關(guān)聯(lián)對象
// 移除關(guān)聯(lián)的對象
// 使用objc_removeAssociatedObjects函數(shù)可以移除某個對象身上的所有關(guān)聯(lián)的對象中姜。
void objc_removeAssociatedObjects(id object)
2.4 總結(jié)關(guān)聯(lián)對象:
-
- 關(guān)聯(lián)對象的主要兩個方法
- set :
objc_setAssociatedObject
- get:
objc_getAssociatedObject
- 關(guān)聯(lián)對象管理類
AssociationsManager
- 關(guān)聯(lián)對象管理類
- 關(guān)聯(lián)對象的數(shù)據(jù)結(jié)構(gòu):
AssociationsHashMap
,ObjectAssociationMap
和ObjcAssociation
- 關(guān)聯(lián)對象的數(shù)據(jù)結(jié)構(gòu):
- 不管set還是get方法,都是先通過類信息找到相應(yīng)的類對,再從類對的value中跨扮,通過關(guān)聯(lián)屬性key找到對應(yīng)的關(guān)聯(lián)屬性序无,進行相應(yīng)操作。
- 關(guān)聯(lián)對象并
不是存儲在被關(guān)聯(lián)對象本身內(nèi)存
中衡创,而是存儲在全局的統(tǒng)一
的一個AssociationsManager
中,如果設(shè)置關(guān)聯(lián)對象為nil,就相當(dāng)于是移除關(guān)聯(lián)對象
- 關(guān)聯(lián)對象并