本文主要是針對類的加載的擴展,探索下分類的底層實現(xiàn)原理。
1. 類擴展和分類介紹
1.1 category 類別、分類
- 專門用來給類添加新的方法
- 不能給類添加成員變量
- 可以利用
runtime實現(xiàn)關(guān)聯(lián)屬性,重寫setter、getter方法 - 分類中用
@property定義的屬性,只有setter、getter方法的聲明,不能生成方法實現(xiàn)和帶下劃線的成員變量
1.2 extension 類擴展
- 可以說成時特殊的分類,也可稱作匿名分類
- 可以給類添加成員屬性,但是是私有變量
- 可以給類添加方法,也是私有方法
2. 類擴展底層探索
類的擴展有兩種創(chuàng)建方式:
- 直接在類中書寫:永遠(yuǎn)在聲明之后,在實現(xiàn)之前(寫在
.m文件中) - 通過
command + N 新建->Objective-C File-> 選擇Extension
image.jpeg
2.1 通過clang 底層編譯
-
寫一個類擴展
image.jpeg -
通過
clang -rewrite-objc main.mm -o main.cpp命令生成cpp文件,打開cpp文件,搜索ext_name屬性:
image.jpeg -
查看
LGTeacher類擴展的方法,在編譯過程中,方法就直接添加到了methodlist中,作為類的一部分,即編譯時期直接添加到本類里面
image.jpeg
2.2 總結(jié)
- 類的擴展在編譯期會作為類的一部分,和類一起編譯進(jìn)來
- 類的擴展只是聲明,依賴于當(dāng)前的主類,沒有
.m文件,可以理解為一個.h文件
3. 分類關(guān)聯(lián)對象底層探索
其底層原理的實現(xiàn),主要分為兩個部分:
- 通過
objc_setAssociatedObject設(shè)值流程 - 通過
objc_getAssociatedObject取值流程
3.1 關(guān)聯(lián)對象 - 設(shè)值流程
-
在分類 LG 中重寫
cate_name的setter、getter方法,通過runtime的屬性關(guān)聯(lián)方法實現(xiàn)
image.jpeg -
運行程序,斷點端在
main中cate_name賦值處
image.jpeg -
繼續(xù)往下運行,斷在
setCate_name方法中:
image.jpeg
其中objc_setAssociatedObject方法有四個參數(shù),分別表示:
參數(shù) 1:要關(guān)聯(lián)的對象,即給誰添加關(guān)聯(lián)屬性
參數(shù) 2:標(biāo)識符,方便取值
參數(shù) 3:
value-
參數(shù) 4:屬性的
策略,即nonatomic、atomic、assign等,如下所示:
image.jpeg -
進(jìn)入
objc_setAssociatedObject源碼實現(xiàn)
這種設(shè)計模式是接口模式,對外的接口不變,內(nèi)部的邏輯變化不影響外部的調(diào)用,類似于set方法的底層源碼實現(xiàn):
image.jpeg -
進(jìn)入
SetAssocHook.get()方法實現(xiàn),其中ChainedHookFunction是一個函數(shù)指針
image.jpeg -
進(jìn)入
SetAssocHook,其底層實現(xiàn)是_base_objc_setAssociatedObject,類型是ChainedHookFunction
image.jpeg 所以可以理解為
SetAssocHook.get()等價于_base_objc_setAssociatedObject
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);//接口模式,對外接口始終不變
}
??等價于
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_base_objc_setAssociatedObject(object, key, value, policy);//接口模式,對外接口始終不變
}
-
進(jìn)入
_base_objc_setAssociatedObject,通過斷點調(diào)試,確實會來到這里:
image.jpeg 進(jìn)入
_object_set_associative_reference方法
關(guān)鍵的方法,關(guān)于關(guān)聯(lián)對象底層原理的探索,主要是看value存到了哪里,以及如何取出value,下面是源碼:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
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封裝一層,類型為DisguisedPtr
DisguisedPtr<objc_object> disguised{(objc_object *)object};//相當(dāng)于包裝了一下 對象object,便于使用
// 包裝一下 policy - value
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
// 根據(jù)策略類型對value進(jìn)行處理
association.acquireValue();//根據(jù)策略類型進(jìn)行處理
//局部作用域空間
{
//初始化manager變量,相當(dāng)于自動調(diào)用AssociationsManager的析構(gòu)函數(shù)進(jìn)行初始化
AssociationsManager manager;//并不是全場唯一,構(gòu)造函數(shù)中加鎖只是為了避免重復(fù)創(chuàng)建,在這里是可以初始化多個AssociationsManager變量的
AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全場唯一
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的結(jié)果是一個類對
if (refs_result.second) {//判斷第二個存不存在,即bool值是否為true
/* it's the first association we make 第一次建立關(guān)聯(lián)*/
object->setHasAssociatedObjects();//nonpointerIsa ,標(biāo)記位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();//釋放
}
通過源碼可以知道,主要分為以下幾部分:
1.創(chuàng)建一個 AssociationsManager 管理類
2.獲取唯一的全局靜態(tài)哈希表:AssociationsHashMap
3.判斷插入的關(guān)聯(lián)值 value 是否存在
. 3.1 存在則繼續(xù)向下走
. 3.2 不存在就走:關(guān)聯(lián)對象-插入空流程,也就是移除關(guān)聯(lián)對象流程
4.通過try_emplace方法,并創(chuàng)建一個空的ObjectAssociationMap去取查詢的鍵值對
5.如果發(fā)現(xiàn)沒有這個key,就插入一個空的BuckeT 進(jìn)去并返回 true
6.通過setHasAssociatedObjects方法標(biāo)記對象存在關(guān)聯(lián)對象即置isa指針的has_assoc屬性為true
7.用當(dāng)前policy 和value 組成了一個ObjcAssociation 替換原來 BucketT 中的空
8.標(biāo)記一下ObjectAssociationMap的第一次為 false
下面進(jìn)行源碼調(diào)試:
定義
AssociationsManager類型的變量,相當(dāng)于自動調(diào)用AssociationsManager的構(gòu)造函數(shù)進(jìn)行初始化
加鎖lock,并不代表唯一,只是為了避免多線程重復(fù)創(chuàng)建,其實在外面是可以定義多個AssociationsManager manager;的-
定義
AssociationsHashMap類型的HashMap,這個是唯一的,從哪里可以體現(xiàn)呢?
通過_mapStorage.get()生成HashMap,其中_mapStorage是一個靜態(tài)變量,所以永遠(yuǎn)是唯一的
image.jpeg
可以看到這個 hashMap 的key 是前面對于 object的封裝:DisguisedPtr<objc_object>,value 是ObjectAssociationMap類型。
-
經(jīng)過調(diào)試,可以查看當(dāng)前的數(shù)據(jù)結(jié)構(gòu):
p disguised: 其中的 value 是來自 object 還原出來的
p association
p manager
p associations: 目前的associations為0x0,表示還沒有查找到相應(yīng)的遞歸查找域中
image.jpeg -
走到局部作用域的
if判斷,此時value是有值的,為KC
image.jpeg -
如果傳入的
value是空值,則會走到else流程,通過源碼得知,相當(dāng)于解除關(guān)聯(lián)
image.jpeg -
繼續(xù)往下執(zhí)行,查看
refs_result,通過p refs_result,其中的類型很多,可以進(jìn)行拆解查看
associations調(diào)用try_emplace方法,傳入一個對象disguised和一個空的關(guān)聯(lián)map:ObjectAssociationMap{}
image.jpeg
//pair -- 表示有鍵值對
(std::__1::pair<
objc::DenseMapIterator<DisguisedPtr<objc_object>,
objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >,
objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
objc::DenseMapInfo<DisguisedPtr<objc_object> >,
objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
false>,
bool>)
//可以簡寫為
(std::__1::pair<
objc
bool>)
- 進(jìn)入
try_emplace方法的源碼實現(xiàn)
有兩個返回值,都是通過std::make_pair生成相應(yīng)的鍵值對;
通過LookupBucketFor方法查找桶子,如果map中已經(jīng)存在,則直接返回,其中make_pair的第二個參數(shù)bool值為false;
如果沒有找到,則通過InsertIntoBucket插入map,其中make_pair的第二個參數(shù)bool值為true
std:: make_pair主要作用是將兩個數(shù)據(jù)合成一個數(shù)據(jù),然后通過返回值的.first和.second進(jìn)行使用

-
進(jìn)入
LookupBucketFor源碼,有兩個同名方法,其中第二個方法屬于重載函數(shù),區(qū)別于第一個的是第二個參數(shù)沒有const修飾,通過調(diào)試可知,外部的調(diào)用是調(diào)用的第二個重載函數(shù),而第二個LookupBucketFor方法,內(nèi)部的實現(xiàn)是調(diào)用第一個LookupBucketFor方法:
image.jpeg -
第一個
LookupBucketFor方法實現(xiàn):
image.jpeg -
斷點運行值
try_emplace方法中,獲取bucket部分TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);:
然后p TheBucket
image.jpeg
其中 TheBucket 的類型與refs_result中屬性的類型是一致的:

-
進(jìn)入
if(refs_result.second)的流程,通過setHasAssociatedObjects將nonpointerIsa的has_assoc標(biāo)記為true:
image.jpeg -
繼續(xù)往下執(zhí)行,查看
refs
p refs,執(zhí)行try_emplace前查看
p refs,執(zhí)行try_emplace后查看
image.jpeg
第一次執(zhí)行try_emplace插入的是一個空桶,還沒有值,第二次執(zhí)行 try_emplace才插入值,即往空桶中插入ObjectAssociationMap(value,policy),返回true,可以通過調(diào)試驗證:

p result.second,返回的true,到此就將屬性和 value 關(guān)聯(lián)上了:

3.1.1 全部流程
所以,關(guān)聯(lián)對象的設(shè)值流程,有點類似于cache_t中的insert方法插入sel-imp的邏輯,如下圖所示:

屬性關(guān)聯(lián)涉及的hashMap 結(jié)構(gòu):

-
AssociationsManager可以有多個,通過AssociationsManagerLock鎖可以得到一個AssociationsHashMap類型的map
-map中有很多關(guān)聯(lián)對象 map,類型是ObjectAssociationMap,其中 key 為DisguisedPtr<objc_object>
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
-
ObjectAssociationMap哈希表中有很多key-value鍵值對,其中key的類型為const void *,這個key也就是我們關(guān)聯(lián)屬性時設(shè)置的字符串,value的類型為ObjcAssociation:
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
-
其中ObjcAssociation是用于包裝policy和value的一個類
image.jpeg
3.1.2 對象插入空流程
根據(jù)源碼可知,主要是局部作用域中的else流程,其實這個流程可以通俗的理解為當(dāng)傳入的value為nil時,則移除關(guān)聯(lián),主要分為以下幾步:

- 根據(jù)
DisguisedPtr找到AssociationsHashMap中的iterator迭代查詢器 - 清理迭代器
- 其實如果插入空值,相當(dāng)于清除
3.2 關(guān)聯(lián)對象-取值流程
-
main中 打印
person.cate_name的值,斷點來到分類中重寫的屬性get方法
image.jpeg -
進(jìn)入
objc_getAssociatedObject源碼實現(xiàn)
image.jpeg _object_get_associative_reference源碼如下:
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
}
通過源碼可知,主要分為以下幾部分:
1.創(chuàng)建一個AssociationsManager 管理類
2.獲取唯一的全局靜態(tài)哈希Map:AssociationsHashMap
3.通過find方法根據(jù) DisguisedPtr 找到 AssociationsHashMap中的 iterator 迭代查詢器
4.如果這個迭代查詢器不是最后一個 獲取 :ObjectAssociationMap(policy和value)
5.通過find方法找到ObjectAssociationMap的迭代查詢器獲取一個經(jīng)過屬性修飾符修飾的value
6.返回 value
3.2.1 調(diào)試取值流程
- 接著上一步調(diào)試,進(jìn)入
_object_get_associative_reference源碼實現(xiàn)
進(jìn)入find方法:根據(jù)關(guān)聯(lián)對象迭代查找AssociationsHashMap,即buckets:
image.jpeg

- 再次通過
find方法,在buckets中查找與key配對的bucket
find方法執(zhí)行之前,j的打印,此時的value為nil
image.jpeg
find方法查詢之后,j的打印,此時的value為KC

3.3 總結(jié)
所以,綜上所述,所以關(guān)聯(lián)對象的底層調(diào)用流程如下圖所示

總的來說,關(guān)聯(lián)對象主要就是兩層哈希map的處理,即存取時都是兩層處理。
4. 關(guān)聯(lián)對象的釋放
上面介紹了關(guān)聯(lián)對象的取值流程和設(shè)值流程,那么關(guān)聯(lián)對象是在什么時候釋放的呢?
我們知道對象的銷毀會調(diào)用dealloc方法,先來看下這個方法:
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
其中調(diào)用了_objc_rootDealloc方法,源碼如下:
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
繼續(xù)調(diào)用了obj->rootDealloc(),源碼如下:
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
這里發(fā)現(xiàn)了一個關(guān)鍵點:isa.hass_assoc,我們在設(shè)值流程中,就是給 isa.hass_assoc進(jìn)行賦值了,標(biāo)志這個對象有關(guān)聯(lián)的屬性,所以這里會走else 流程,也就是object_dispose((id)this);,這個方法源碼如下:
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
free(obj)是釋放對象,那么關(guān)聯(lián)對象的移除就是在objc_destructInstance(obj);里面了,我們看下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();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
這個方法中發(fā)現(xiàn)if (assoc) _object_remove_assocations(obj);是移除關(guān)聯(lián)屬性的方法,我們繼續(xù)探索_object_remove_assocations方法,源碼如下:
void
_object_remove_assocations(id object)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
associations.erase(i);
}
}
// release everything (outside of the lock).
for (auto &i: refs) {
i.second.releaseHeldValue();
}
}
可以看到這里是將對象 object 關(guān)聯(lián)的表全部移除了,從全局的 HashMap 找到object 的迭代器,將對象關(guān)聯(lián)的ObjectAssociationMap全部移除。
調(diào)用鏈為:dealloc -> _objc_rootDealloc -> object_dispose() ->
objc_destructInstance() -> _object_remove_assocations。


























