上一篇中若厚,我們介紹了類是如何從
mach-o
中加載到內(nèi)存的,分析了read_images
方法蜒什,readClass
方法,realizeClassWithoutSwift
方法测秸,methodizeClass
以及attachLists
方法
接下來(lái)我們探索分類的加載,在探索之前我們需要知道分類的結(jié)構(gòu)
category_t結(jié)構(gòu)
1灾常、探索category_t的底層結(jié)構(gòu)
- 首先我們?cè)?code>mian.m函數(shù)里面去定義一個(gè)分類
@interface NSObject (LGB)
@property (nonatomic,strong) NSString * cate_name;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod4;
+ (void)cate_instanceMethod;
@end
@implementation NSObject (LGB)
- (void)cate_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod4{
NSLog(@"%s",__func__);
}
+ (void)cate_instanceMethod{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSObject *obj = [[NSObject alloc]init];
LGPerson *person = [[LGPerson alloc] init];
NSLog(@"%ld - %ld",sizeof(person),class_getInstanceSize(person.class));
}
return 0;
}
使用
clang rewrite-objc main.m -o mian.cpp
終端命令去編譯mian.m
得到mian.cpp
文件在
mian.cpp
里面,我們得到了分類的底層結(jié)構(gòu),instance_methods
表示實(shí)例方法列表霎冯,class_methods
表示類方法列表
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;
const struct _prop_list_t *properties;
};
- 其中
instance_methods表示實(shí)例方法列表
,class_methods表示類方法列表
- 我們發(fā)現(xiàn)了一個(gè)問(wèn)題:查看看
_prop_list_t
钞瀑,明明分類中定義了屬性
沈撞,但是在底層編譯中并沒(méi)有看到屬性,如下圖所示雕什,這是因?yàn)?code>分類中定義的屬性沒(méi)有相應(yīng)的set
缠俺、get
方法显晶,我們可以通過(guò)關(guān)聯(lián)對(duì)象來(lái)設(shè)置
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_NSObject_$_LGB __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"cate_name","T@\"NSString\",&,N"}}
};
當(dāng)然我們通過(guò)objc源碼搜索 category_t,我們也能得到分類category_t的結(jié)構(gòu):
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
2壹士、分類與類拓展的區(qū)別
category : 分類磷雇,類別
- 專門用來(lái)給類
添加新的方法
- 不能給類添加成員變量
- 可以添加
屬性
, 但是只會(huì)生成變量的 getter setter 方法的聲明
墓卦,不會(huì)生成方法的實(shí)現(xiàn)和帶下劃線的成員變量
extension : 類拓展
- 可以說(shuō)是特殊的分類倦春,也稱為匿名分類
- 可以給類添加成員變量和屬性,但是是私有變量
- 可以給類添加方法落剪,但也是私有方法
3、關(guān)聯(lián)對(duì)象
如果想要給分類有效的添加屬性
尿庐,需要在重寫的 getter setter方法里面去關(guān)聯(lián)對(duì)象
#import <Foundation/Foundation.h>
@interface NSObject (LGA)
@property (nonatomic,strong) NSString * lga_name;
@end
#import "NSObject+LGA.h"
#import <objc/runtime.h>
@implementation NSObject (LGA)
- (void)setLga_name:(NSString *)lga_name{
objc_setAssociatedObject(self, "lga_name", lga_name, OBJC_ASSOCIATION_RETAIN);
}
- (NSString *)lga_name{
return objc_getAssociatedObject(self, "lga_name");
}
@end
分類的加載
準(zhǔn)備: 創(chuàng)建LGPerson的兩個(gè)分類:LGA LGB
在分析realizeClassWithoutSwift時(shí)忠怖,realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_categories_nolock -> extAlloc ->attachCategories
中提及了rwe的加載,中分析了分類的data數(shù)據(jù)
是如何 加載到類中的抄瑟,且分類的加載順序是:LGA -> LGB的順序加載到類中凡泣,即越晚加進(jìn)來(lái),越在前面
其中查看methodizeClass
的源碼實(shí)現(xiàn)皮假,可以發(fā)現(xiàn)類的數(shù)據(jù)
和 分類的數(shù)據(jù)
是分開(kāi)處理的鞋拟,主要是因?yàn)?code>類在編譯階段,就已經(jīng)確定好了方法的歸屬位置(即實(shí)例方法存儲(chǔ)在類中惹资,類方法存儲(chǔ)在元類中)贺纲,而分類是后面才加進(jìn)來(lái)的
其中分類需要通過(guò)attatchToClass添加到類,然后才能在外界進(jìn)行使用褪测,在此過(guò)程猴誊,我們已經(jīng)知道了分類加載三步驟的后面兩個(gè)步驟,分類的加載主要分為3步:
- 【第一步】分類數(shù)據(jù)加載時(shí)機(jī):根據(jù)類和分類是否實(shí)現(xiàn)load方法來(lái)區(qū)分不同的時(shí)機(jī)
- 【第二步】attachCategories準(zhǔn)備分類數(shù)據(jù)
- 【第三步】attachLists將分類數(shù)據(jù)添加到主類中
【第一步】分類加載的時(shí)機(jī)
以主類LGPerson + 分類LGA侮措、LGB 均實(shí)現(xiàn)+load方法為例
- load_images
- loadAllCategories
- load_categories_nolock
- attachCategories
拓展:只要有一個(gè)分類是非懶加載分類懈叹,那么所有的分類都會(huì)被標(biāo)記位非懶加載分類
分類與類的搭配下的加載時(shí)機(jī)
【情況1】非懶加載類 + 非懶加載分類
- 類的數(shù)據(jù)加載是通過(guò)_getObjc2NonlazyClassList加載,即ro分扎、rw的操作澄成,對(duì)rwe賦值初始化,是在extAlloc方法中
- 分類的數(shù)據(jù)加載是通過(guò)load_images加載到類中的
其調(diào)用路徑為:
非懶加載類路徑:
map_images -> map_images_nolock -> _read_images -> readClass -> _getObjc2NonlazyClassList -> realizeClassWithoutSwift -> methodizeClass -> attachToClass
,此時(shí)的mlists是一維數(shù)組畏吓,然后走到load_images部分非懶加載分類路徑:
load_images --> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists
墨状,此時(shí)的mlists是二維數(shù)組
【情況2】非懶加載類 + 懶加載分類
非懶加載類 與 懶加載分類的數(shù)據(jù)加載,有如下結(jié)論:
-
類 和 分類的加載
是在read_images就加載數(shù)據(jù)
了 - 其中
data數(shù)據(jù)
在編譯時(shí)期就已經(jīng)完成了
【情況3】懶加載類 + 懶加載分類
懶加載類 與 懶加載分類的數(shù)據(jù)加載是在消息第一次調(diào)用時(shí)加載的
【情況4】懶加載類 + 非懶加載分類
只要分類實(shí)現(xiàn)了load
庵佣,會(huì)迫使
主類提前加載
,即 主類 強(qiáng)行轉(zhuǎn)換為 非懶加載類樣式
歉胶, 加載流程就和情況1是一致的
關(guān)聯(lián)對(duì)象的原理
關(guān)聯(lián)對(duì)象設(shè)置值流程
首先我們先來(lái)了解一下objc_setAssociatedObject
方法的四個(gè)參數(shù):
- 參數(shù)1:要關(guān)聯(lián)的對(duì)象,即給誰(shuí)添加關(guān)聯(lián)屬性
- 參數(shù)2:標(biāo)識(shí)符巴粪,方便下次查找
- 參數(shù)3:
value
-
參數(shù)4:屬性的策略通今,即retain粥谬,copy,
objc_setAssociatedObject源碼實(shí)現(xiàn):
SetAssocHook.get()
是一種接口模式
的設(shè)計(jì)思想辫塌,對(duì)外的接口不變漏策,內(nèi)部的邏輯變化不影響外部的調(diào)用
進(jìn)入
SetAssocHook
,其底層實(shí)現(xiàn)是_base_objc_setAssociatedObject
臼氨,類型是ChainedHookFunction
所以可以理解為
SetAssocHook.get()
等價(jià)于_base_objc_setAssociatedObject
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);//接口模式掺喻,對(duì)外接口始終不變
}
??等價(jià)于
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_base_objc_setAssociatedObject(object, key, value, policy);//接口模式,對(duì)外接口始終不變
}
進(jìn)入_base_objc_setAssociatedObject
源碼實(shí)現(xiàn):_base_objc_setAssociatedObject -> _object_set_associative_reference
進(jìn)入_object_set_associative_reference
源碼實(shí)現(xiàn)
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封裝成一個(gè)數(shù)組結(jié)構(gòu)類型储矩,類型為DisguisedPtr
DisguisedPtr<objc_object> disguised{(objc_object *)object};//相當(dāng)于包裝了一下 對(duì)象object,便于使用
// 包裝一下 policy - value
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();//根據(jù)策略類型進(jìn)行處理
//局部作用域空間
{
//初始化manager變量感耙,相當(dāng)于自動(dòng)調(diào)用AssociationsManager的析構(gòu)函數(shù)進(jìn)行初始化
AssociationsManager manager;//并不是全場(chǎng)唯一,構(gòu)造函數(shù)中加鎖只是為了避免重復(fù)創(chuàng)建持隧,在這里是可以初始化多個(gè)AssociationsManager變量的
AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全場(chǎng)唯一
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的結(jié)果是一個(gè)類對(duì)
if (refs_result.second) {//判斷第二個(gè)存不存在即硼,即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; //得到一個(gè)空的桶子屡拨,找到引用對(duì)象類型,即第一個(gè)元素的second值
auto result = refs.try_emplace(key, std::move(association));//查找當(dāng)前的key是否有association關(guān)聯(lián)對(duì)象
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();//釋放
}
通過(guò)源碼可知呀狼,我們總結(jié)一下關(guān)聯(lián)對(duì)象的設(shè)置流程:
- 1.創(chuàng)建一個(gè)
AssociationsManager
管理類 - 2.獲取唯一的全局靜態(tài)哈希Map:
AssociationsHashMap
- 3.判斷是否插入的關(guān)聯(lián)值value是否存在
- 3.1 如果存在走第4步
- 3.2 如果不存在裂允,關(guān)聯(lián)對(duì)象 -插入空 的流程 - 4.通過(guò)
try_emplace
方法,并創(chuàng)建一個(gè)空的ObjectAssociationMap
去取查詢的鍵值對(duì) - 5.如果發(fā)現(xiàn)沒(méi)有這個(gè) key 就插入一個(gè) 空的 BucketT進(jìn)去并返回true
- 6.通過(guò)
setHasAssociatedObjects
方法標(biāo)記對(duì)象存在關(guān)聯(lián)對(duì)象即置isa指針的has_assoc屬性為true - 7.用當(dāng)
前 policy 和 value
組成了一個(gè)ObjcAssociation
替換原來(lái)BucketT 中的空
- 8.標(biāo)記一下
ObjectAssociationMap
的第一次為 false
關(guān)聯(lián)對(duì)象取值流程
我們通過(guò)源碼 objc_getAssociatedObject() --> _object_get_ associative_reference(object,key)
_object_get_associative_reference的源碼實(shí)現(xiàn):
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};//創(chuàng)建空的關(guān)聯(lián)對(duì)象
{
AssociationsManager manager;//創(chuàng)建一個(gè)AssociationsManager管理類
AssociationsHashMap &associations(manager.get());//獲取全局唯一的靜態(tài)哈希map
AssociationsHashMap::iterator i = associations.find((objc_object *)object);//找到迭代器哥艇,即獲取buckets
if (i != associations.end()) {//如果這個(gè)迭代查詢器不是最后一個(gè) 獲取
ObjectAssociationMap &refs = i->second; //找到ObjectAssociationMap的迭代查詢器獲取一個(gè)經(jīng)過(guò)屬性修飾符修飾的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
}
通過(guò)源碼可知,主要分為以下部分:
- 創(chuàng)建一個(gè)
AssociationManager
管理類 - 獲取唯一的全局靜態(tài)哈希Map:
AssociationMap
- 通過(guò)find方法根據(jù)
DisguisedPtr
找到AssociationsHashMap
中的iterator 迭代查詢器
- 如果這個(gè)迭代查詢器不是最后一個(gè) 獲取 :
ObjectAssociationMap (policy和value)
- 通過(guò)
find
方法找到ObjectAssociationMap
的迭代查詢器獲取一個(gè)經(jīng)過(guò)屬性修飾符修飾
的value - 返回value
關(guān)聯(lián)對(duì)象涉及的哈希Map結(jié)構(gòu)
-
AssociationHashMap
里面存放的是ObjectAssociationMap
-
ObjectAssociationMap
存放的是ObjectAssociation
-
ObjectAssociation
是一種類似字典一樣結(jié)構(gòu)她奥,存放{policy ,value}結(jié)構(gòu)
load_images
load_image的源碼分析:
- 1.首先找到所有懶加載類的load方法:
prepare_load_methods()
- 2.然后進(jìn)行調(diào)用:
call_load_methods()
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
//
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
prepare_load_methods()源碼分析:
- 獲取非懶加載類瓮增,以及繼承鏈上實(shí)現(xiàn)了load方法的類
- 獲取非懶加載分類上實(shí)現(xiàn)了load方法的類
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
//_getObjc2NonlazyClassList 獲取非懶加載類的列表
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 遞歸獲取繼承鏈上的類
schedule_class_load(remapClass(classlist[i]));
}
// 獲取非懶加載分類上的list
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
call_load_methods()源碼分析:
- call_class_loads() 執(zhí)行類的load方法
- call_category_loads() 執(zhí)行分類的load方法
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}