應(yīng)用程序加載(一) -- dyld流程分析
應(yīng)用程序加載(二) -- dyld&objc關(guān)聯(lián)以及類的加載初探
應(yīng)用程序加載(三)-- 類的加載
應(yīng)用程序加載(四)-- 分類的加載
應(yīng)用程序加載(五)-- 類擴(kuò)展和關(guān)聯(lián)對(duì)象
1吼拥、類擴(kuò)展extension
類擴(kuò)展需要對(duì)應(yīng)著分類(category)比較研究凿可。
分類的作用:
- 添加方法,可以添加實(shí)例方法和類方法
- 添加協(xié)議
- 添加屬性(@property)枯跑,只會(huì)生成
getter
和setter
的聲明敛助,而不會(huì)生成方法的實(shí)現(xiàn)和帶下劃線的成員變量
類擴(kuò)展的作用:
- 可以添加方法聲明,需要在原類的.m中實(shí)現(xiàn)
- 可以添加屬性
- 可以添加成員屬性
擴(kuò)展寫法
通常在創(chuàng)建工程中時(shí)會(huì)有一個(gè)ViewController
類或者創(chuàng)建自定義的UIViewController
子類的時(shí)候续扔,Xcode
會(huì)在類中自動(dòng)創(chuàng)建一個(gè)類擴(kuò)展焕数,如圖:
我們經(jīng)常把不想暴露的一些屬性定義在這里,需要外部調(diào)用的屬性寫在.h文件中识脆。
需要注意類擴(kuò)展有一個(gè)位置要求,必須寫在類的interface
和implementation
之間仑荐。寫一個(gè)簡(jiǎn)單的例子纵东,就在VierController.m
文件中,寫一個(gè)Person
類洒扎,然后為Person
添加一個(gè)類擴(kuò)展衰絮。
正確的寫法:
如果將類擴(kuò)展寫interface
之前或者implementation
之后,編譯器會(huì)報(bào)錯(cuò)胡诗。
interface
之前:
implementation
之后:
擴(kuò)展和分類的區(qū)別
分類中添加屬性是沒有getter
和setter
實(shí)現(xiàn)的煌恢,而擴(kuò)展中是有的震庭,我們可以通過(guò)c++層看到擴(kuò)展添加屬性會(huì)有什么效果。
首先在擴(kuò)展中添加一個(gè)dzName
屬性
然后進(jìn)入到ViewController.m
文件所在的路徑二汛,執(zhí)行xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m
命令拨拓,生成ViewController.cpp
文件,打開文件苫昌,搜索dzName
通過(guò)c++的代碼可以看到祟身,生成了屬性對(duì)應(yīng)的setter
和getter
方法物独,還有帶有下劃線的成員變量(_dzName
)
在擴(kuò)展中還寫了一個(gè)ext_method
方法,在c++中也能夠看到
直接加入到方法列表中婉陷。這點(diǎn)可以說(shuō)明,擴(kuò)展是類的一部分秽澳,而且方法在編譯期就加入到類中担神。
2、關(guān)聯(lián)對(duì)象
上面了解了妄讯,擴(kuò)展可以給類增加屬性亥贸。但是分類添加屬性的時(shí)候只是增加了getter
和setter
的聲明。例如:
有如圖中的分類荣挨,里面定義了一個(gè)屬性cat_name
讹俊。當(dāng)我們調(diào)用的時(shí)候:
此時(shí)編譯器是不會(huì)報(bào)錯(cuò)的,說(shuō)明cat_name
的setter
方法的sel
是可以找到的。當(dāng)運(yùn)行起來(lái)的時(shí)候寡壮,就會(huì)報(bào)錯(cuò)况既。因?yàn)檎也坏綄?duì)應(yīng)的imp
。所以說(shuō)分類中添加屬性只是添加了getter
和setter
方法的聲明棒仍,而沒有方法的實(shí)現(xiàn)莫其。
那么如何分類中的屬性的實(shí)現(xiàn)呢?就是通過(guò)系統(tǒng)給我們提供的關(guān)聯(lián)對(duì)象乱陡。
關(guān)聯(lián)對(duì)象用法
#import <objc/runtime.h>
@interface Person (DZ)
@property (nonatomic, copy) NSString *cate_name;
@end
@implementation Person (DZ)
- (void)setCate_name:(NSString *)cate_name{
/**
1: 對(duì)象
2: 標(biāo)識(shí)符
3: value
4: 策略
*/
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
return objc_getAssociatedObject(self, "cate_name");
}
@end
- 分類中定義了一個(gè)屬性
cate_name
- 實(shí)現(xiàn)了
cate_name
的getter
和setter
方法憨颠,方法中調(diào)用了關(guān)聯(lián)對(duì)象的兩個(gè)方法:- objc_setAssociatedObject:需要四個(gè)參數(shù)积锅,分別是對(duì)象养盗、標(biāo)識(shí)符、值和關(guān)聯(lián)策略
- objc_getAssociatedObject:相對(duì)簡(jiǎn)單蹬跃,只有兩個(gè)參數(shù)铆铆,分別是對(duì)象和標(biāo)識(shí)符
- 注意,使用關(guān)聯(lián)對(duì)象要引入
runtime
的頭文件
用法很容易翁都,但是底層是如何實(shí)現(xiàn)的呢谅猾?接下來(lái)我們來(lái)探索一下底層實(shí)現(xiàn)。
關(guān)聯(lián)對(duì)象的底層實(shí)現(xiàn)
還是在objc
的源碼中坐搔,可以看到相關(guān)源碼敬矩,先來(lái)看看關(guān)聯(lián)對(duì)象的set實(shí)現(xiàn):
關(guān)聯(lián)對(duì)象set
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);
}
- 此處與以往的
objc
開源代碼不同,在“781”
的這個(gè)版本的源碼中凳忙,增加了一層代碼封裝禽炬,通過(guò)command
點(diǎn)擊SetAssocHook
,能夠看到這行代碼:static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
- 底層調(diào)用的就是
_base_objc_setAssociatedObject
函數(shù)
static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
-
_base_objc_setAssociatedObject
實(shí)現(xiàn)中柳恐,調(diào)用的是_object_set_associative_reference
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
//1热幔、相關(guān)的容錯(cuò)處理
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));
//2断凶、包裝了一下 對(duì)象
DisguisedPtr<objc_object> disguised{(objc_object *)object};
//3、 包裝一下 policy - value
ObjcAssociation association{policy, value};
//4认烁、根據(jù)關(guān)聯(lián)策略對(duì)值處理
// retain the new value (if any) outside the lock.
association.acquireValue();
// 5、核心代碼
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
object->setHasAssociatedObjects();
}
/* establish or replace the association */
auto &refs = refs_result.first->second; // 空的桶子
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
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();
}
首先代碼進(jìn)行了相關(guān)的容錯(cuò)處理嘹承,這些不用關(guān)心如庭,簡(jiǎn)單看看就好
將傳入的參數(shù)
object
進(jìn)行了一次包裝。對(duì)第三個(gè)參數(shù)
value
和第四個(gè)參數(shù)policy
包裝一下骤竹。-
association.acquireValue();
根據(jù)關(guān)聯(lián)策略(policy
參數(shù))往毡,對(duì)值進(jìn)行處理,看看源碼實(shí)現(xiàn):inline void acquireValue() { if (_value) { switch (_policy & 0xFF) { case OBJC_ASSOCIATION_SETTER_RETAIN: _value = objc_retain(_value); break; case OBJC_ASSOCIATION_SETTER_COPY: _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy)); break; } } }
- 此處根據(jù)關(guān)聯(lián)策略對(duì)
value
進(jìn)行retain
或者copy
的操作懒震。
- 此處根據(jù)關(guān)聯(lián)策略對(duì)
-
此處進(jìn)入核心代碼部分
{ AssociationsManager manager; AssociationsHashMap &associations(manager.get()); if (value) { auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); if (refs_result.second) { /* it's the first association we make */ object->setHasAssociatedObjects(); } /* establish or replace the association */ auto &refs = refs_result.first->second; // 空的桶子 auto result = refs.try_emplace(key, std::move(association)); if (!result.second) { association.swap(result.first->second); } } else { 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); } } } } }
- 初始化一個(gè)
AssociationsManager
變量manager
个扰,它不是一個(gè)單例葱色,只是用它來(lái)給下面訪問(wèn)哈希表進(jìn)行加鎖
- 初始化一個(gè)
構(gòu)造函數(shù)和析構(gòu)函數(shù)就是加鎖解鎖操作。
- 通過(guò)manager.get()
函數(shù)獲取到關(guān)聯(lián)對(duì)象的哈希表的引用&associations
恐锣,類型是AssociationsHashMap
- 判斷value
存在時(shí):
- 從哈希表中讀取disguised
對(duì)應(yīng)的結(jié)果refs_result
舞痰。disguised
就是傳入的object
參數(shù)诀姚。
- 通過(guò)結(jié)果refs_result
中的second
判斷是不是第一次來(lái)。如果第一次來(lái)就會(huì)調(diào)用object->setHasAssociatedObjects();
函數(shù)
- setHasAssociatedObjects
就是給對(duì)象object
設(shè)置狀態(tài)呀打。代表這個(gè)對(duì)象是有關(guān)聯(lián)對(duì)象存在的糯笙。如果object
是nonpointer
,那么這個(gè)關(guān)聯(lián)狀態(tài)就存儲(chǔ)在isa
中
- 然后根據(jù)參數(shù)key
創(chuàng)建關(guān)聯(lián)豺憔,如果之前關(guān)聯(lián)過(guò)就進(jìn)行替換關(guān)聯(lián)。
- 此處就可以知道恭应,是兩層哈希表。第一層是以對(duì)象作為哈希函數(shù)境肾,第二層是以闖入的參數(shù)key
為哈希函數(shù)胆屿。
- value
不存在時(shí):傳入的參數(shù)value
為nil。就進(jìn)行擦除环鲤。因?yàn)槭请p層哈希彻秆,所以得做兩層擦除。
關(guān)聯(lián)對(duì)象set流程圖:
關(guān)聯(lián)對(duì)象get
此時(shí)對(duì)關(guān)聯(lián)對(duì)象的set有所了解了酒朵,再看看get實(shí)現(xiàn)就很簡(jiǎn)單扎附,就是通過(guò)傳入的兩個(gè)參數(shù),進(jìn)行哈希的雙層查找匙铡。源碼如下:
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{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
關(guān)聯(lián)對(duì)象雙層哈希示意圖
- 圖中包含示例中的關(guān)聯(lián)對(duì)象例子
關(guān)聯(lián)對(duì)象表的移除
表的創(chuàng)建我們了解后鳖眼,就得看看什么時(shí)候?qū)Ρ磉M(jìn)行移除操作的嚼摩。移除是在dealloc
中處理的,看看源碼:
- (void)dealloc {
_objc_rootDealloc(self);
}
???
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
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);
}
}
???
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
???
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;
}
???
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();
}
}
- 通過(guò)代碼的層層調(diào)用愿卒,最后調(diào)用到
_object_remove_assocations
函數(shù) - 在
_object_remove_assocations
函數(shù)中潮秘,找到object
為key的關(guān)聯(lián)對(duì)象表(第二層哈希表),然后對(duì)表進(jìn)行所有數(shù)據(jù)擦除