上一篇中,我們介紹了類是如何從
mach-o
中加載到內存的,分析了read_images
方法,readClass
方法,realizeClassWithoutSwift
方法,methodizeClass
以及attachLists
方法
接下來我們探索分類的加載,在探索之前我們需要知道分類的結構
category_t結構
1、探索category_t的底層結構
- 首先我們在
mian.m
函數里面去定義一個分類
@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
里面,我們得到了分類的底層結構,instance_methods
表示實例方法列表,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表示實例方法列表
,class_methods表示類方法列表
- 我們發現了一個問題:查看看
_prop_list_t
,明明分類中定義了屬性
,但是在底層編譯中并沒有看到屬性,如下圖所示,這是因為分類中定義的屬性
沒有相應的set
、get
方法,我們可以通過關聯對象來設置
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"}}
};
當然我們通過objc源碼搜索 category_t,我們也能得到分類category_t的結構:
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、分類與類拓展的區別
category : 分類,類別
- 專門用來給類
添加新的方法
- 不能給類添加成員變量
- 可以添加
屬性
, 但是只會生成變量的 getter setter 方法的聲明
,不會生成方法的實現和帶下劃線的成員變量
extension : 類拓展
- 可以說是特殊的分類,也稱為匿名分類
- 可以給類添加成員變量和屬性,但是是私有變量
- 可以給類添加方法,但也是私有方法
3、關聯對象
如果想要給分類有效的添加屬性
,需要在重寫的 getter setter方法里面去關聯對象
#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
分類的加載
準備: 創建LGPerson的兩個分類:LGA LGB
在分析realizeClassWithoutSwift時,realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_categories_nolock -> extAlloc ->attachCategories
中提及了rwe的加載,中分析了分類的data數據
是如何 加載到類中的,且分類的加載順序是:LGA -> LGB的順序加載到類中,即越晚加進來,越在前面
其中查看methodizeClass
的源碼實現,可以發現類的數據
和 分類的數據
是分開處理的,主要是因為類在編譯階段,就已經確定好了方法的歸屬位置(即實例方法存儲在類中,類方法存儲在元類中)
,而分類是后面才加進來的
其中分類需要通過attatchToClass添加到類,然后才能在外界進行使用,在此過程,我們已經知道了分類加載三步驟的后面兩個步驟,分類的加載主要分為3步:
- 【第一步】分類數據加載時機:根據類和分類是否實現load方法來區分不同的時機
- 【第二步】attachCategories準備分類數據
- 【第三步】attachLists將分類數據添加到主類中
【第一步】分類加載的時機
以主類LGPerson + 分類LGA、LGB 均實現+load方法為例
- load_images
- loadAllCategories
- load_categories_nolock
- attachCategories
拓展:只要有一個分類是非懶加載分類,那么所有的分類都會被標記位非懶加載分類
分類與類的搭配下的加載時機
【情況1】非懶加載類 + 非懶加載分類
- 類的數據加載是通過_getObjc2NonlazyClassList加載,即ro、rw的操作,對rwe賦值初始化,是在extAlloc方法中
- 分類的數據加載是通過load_images加載到類中的
其調用路徑為:
非懶加載類路徑:
map_images -> map_images_nolock -> _read_images -> readClass -> _getObjc2NonlazyClassList -> realizeClassWithoutSwift -> methodizeClass -> attachToClass
,此時的mlists是一維數組,然后走到load_images部分非懶加載分類路徑:
load_images --> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists
,此時的mlists是二維數組
【情況2】非懶加載類 + 懶加載分類
非懶加載類 與 懶加載分類的數據加載,有如下結論:
-
類 和 分類的加載
是在read_images就加載數據
了 - 其中
data數據
在編譯時期就已經完成了
【情況3】懶加載類 + 懶加載分類
懶加載類 與 懶加載分類的數據加載是在消息第一次調用時加載的
【情況4】懶加載類 + 非懶加載分類
只要分類實現了load
,會迫使
主類提前加載
,即 主類 強行轉換為 非懶加載類樣式
, 加載流程就和情況1是一致的
關聯對象的原理
關聯對象設置值流程
首先我們先來了解一下objc_setAssociatedObject
方法的四個參數:
- 參數1:要關聯的對象,即給誰添加關聯屬性
- 參數2:標識符,方便下次查找
- 參數3:
value
-
參數4:屬性的策略,即retain,copy,
objc_setAssociatedObject源碼實現:
SetAssocHook.get()
是一種接口模式
的設計思想,對外的接口不變,內部的邏輯變化不影響外部的調用
進入
SetAssocHook
,其底層實現是_base_objc_setAssociatedObject
,類型是ChainedHookFunction
所以可以理解為
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);//接口模式,對外接口始終不變
}
進入_base_objc_setAssociatedObject
源碼實現:_base_objc_setAssociatedObject -> _object_set_associative_reference
進入_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
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};//相當于包裝了一下 對象object,便于使用
// 包裝一下 policy - value
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();//根據策略類型進行處理
//局部作用域空間
{
//初始化manager變量,相當于自動調用AssociationsManager的析構函數進行初始化
AssociationsManager manager;//并不是全場唯一,構造函數中加鎖只是為了避免重復創建,在這里是可以初始化多個AssociationsManager變量的
AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全場唯一
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的結果是一個類對
if (refs_result.second) {//判斷第二個存不存在,即bool值是否為true
/* it's the first association we make 第一次建立關聯*/
object->setHasAssociatedObjects();//nonpointerIsa ,標記位true
}
/* establish or replace the association 建立或者替換關聯*/
auto &refs = refs_result.first->second; //得到一個空的桶子,找到引用對象類型,即第一個元素的second值
auto result = refs.try_emplace(key, std::move(association));//查找當前的key是否有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();//釋放
}
通過源碼可知,我們總結一下關聯對象的設置流程:
- 1.創建一個
AssociationsManager
管理類 - 2.獲取唯一的全局靜態哈希Map:
AssociationsHashMap
- 3.判斷是否插入的關聯值value是否存在
- 3.1 如果存在走第4步
- 3.2 如果不存在,關聯對象 -插入空 的流程 - 4.通過
try_emplace
方法,并創建一個空的ObjectAssociationMap
去取查詢的鍵值對 - 5.如果發現沒有這個 key 就插入一個 空的 BucketT進去并返回true
- 6.通過
setHasAssociatedObjects
方法標記對象存在關聯對象即置isa指針的has_assoc屬性為true - 7.用當
前 policy 和 value
組成了一個ObjcAssociation
替換原來BucketT 中的空
- 8.標記一下
ObjectAssociationMap
的第一次為 false
關聯對象取值流程
我們通過源碼 objc_getAssociatedObject() --> _object_get_ associative_reference(object,key)
_object_get_associative_reference的源碼實現:
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};//創建空的關聯對象
{
AssociationsManager manager;//創建一個AssociationsManager管理類
AssociationsHashMap &associations(manager.get());//獲取全局唯一的靜態哈希map
AssociationsHashMap::iterator i = associations.find((objc_object *)object);//找到迭代器,即獲取buckets
if (i != associations.end()) {//如果這個迭代查詢器不是最后一個 獲取
ObjectAssociationMap &refs = i->second; //找到ObjectAssociationMap的迭代查詢器獲取一個經過屬性修飾符修飾的value
ObjectAssociationMap::iterator j = refs.find(key);//根據key查找ObjectAssociationMap,即獲取bucket
if (j != refs.end()) {
association = j->second;//獲取ObjcAssociation
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();//返回value
}
通過源碼可知,主要分為以下部分:
- 創建一個
AssociationManager
管理類 - 獲取唯一的全局靜態哈希Map:
AssociationMap
- 通過find方法根據
DisguisedPtr
找到AssociationsHashMap
中的iterator 迭代查詢器
- 如果這個迭代查詢器不是最后一個 獲取 :
ObjectAssociationMap (policy和value)
- 通過
find
方法找到ObjectAssociationMap
的迭代查詢器獲取一個經過屬性修飾符修飾
的value - 返回value
關聯對象涉及的哈希Map結構
-
AssociationHashMap
里面存放的是ObjectAssociationMap
-
ObjectAssociationMap
存放的是ObjectAssociation
-
ObjectAssociation
是一種類似字典一樣結構,存放{policy ,value}結構
load_images
load_image的源碼分析:
- 1.首先找到所有懶加載類的load方法:
prepare_load_methods()
- 2.然后進行調用:
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()源碼分析:
- 獲取非懶加載類,以及繼承鏈上實現了load方法的類
- 獲取非懶加載分類上實現了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() 執行類的load方法
- call_category_loads() 執行分類的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;
}