數據結構
- objc_object
- objc_class
- isa指針
- method_t
objc_object id == objc_object
- isa_t
- 關于isa操作相關
- 弱引用相關
- 關聯對象相關
- 內存管理相關
objc_class
- 對應OC中的類(Class),class也是一個對象。類對象
- 繼承自objc_object
- 包含superClass指針,指向一個class
cache_t cache(成員變量),一個方法緩存的結構
① 快速查找方法執行函數
② 可增量擴展的哈希表結構
③ 局部性原理的最佳應用
局部性原理:我們將調用頻次較高的幾種方法,放到一塊開辟的空間中,這樣我們在下次調用時就能更加快速的調用方法
構成:由一個數組來實現的
數組中由多個bucket_t元素構成,bucket_t 包含:IMP指針和key
class_data_bits_t bits
class_data_bits_t主要是對class_rw_t的封裝
class_rw_t代表了類相關的讀寫信息、對class_ro_t的封裝
class_data_bits_t bits 組成
-
class_rw_t
① class_ro_t 代表了類相關的只讀信息
② protocols
③ properties
④ methods
class_rw_t.png -
class_ro_t
Class_ro_t.png - isa指針
① 指針型isa 的值代表Class的地址
② 非指針型isa 的值的部分代表Class的地址
③ 產生這兩種的初衷:我們在尋址實際上30~40位數就可以保證我們尋找到所有class的地址,多出來的位可以存儲一些別的內容,來達到節省內存的目的
④ 指向問題:- 關于對象,其指向類對象
- 關于類對象,其指向元類對象
-
如果是實例方法從類對象查找,如果是類方法從元類對象查找
isa指針.png
method_t
- SEL name:方法的名稱
- const char *types:函數返回值和參數的組合
- IMP imp:無類型的函數指針
type Encodings技術
- 使method指向了函數體的返回值,參數等
-
返回值占據第一位,返回值只有一個或者沒有
20210622-155020.png
數據結構總結
對象、類對象、元類對象
- 類對象存儲實例方法列表等信息
- 元類對象存儲類方法類表等信息
- 類對象和元類對象都是objc_class數據結構,由于繼承了objc_object數據結構所以才有isa指針,
- 所以才能實例對象找到類對象訪問它的實例方法列表等信息,
- 類對象通過isa可以找到元類對象訪問類方法列表等信息
- 元類對象的isa指針(包含根元類對象)都指向根元類對象
- 根元類對象的superclass指向的根類對象
注:當在根類中的類方法沒找到,會指向根類對象中去查找它的同名的實例方法
消息傳遞過程
實例方法消息傳遞過程
1、 調用了一個實例對象A對象的實例方法
2、 通過A實例對象的isa指針找到它的類對象,類對象中遍歷方法列表去查找同名的方法實現,找到后Runtime發送消息調用
3、 如果沒有找到就會根據superclass指針指向的父類對象,并查找其方法列表。。。直到根類對象中的方法列表,如果找到Runtime發送消息調用,如果到根類對象還沒有找到就會返回nil
類方的消息傳遞過程
1、 調用一個類對象的類方法。
2、 通過類對象的isa指針找到元類對象,元類對象中遍歷方法列表查找同名的類方法實現,找到后直接調用
3、如果沒有找到就會通過superclass指針指向父元類對象,并查找其類方法列表。。。直到根元類對象中的類方法列表,如果找到直接調用,如果沒有找到進入到消息轉發
4、根元類對象如果沒有找到會superclass會指向根類對象,并查找同名的實例方法,如果找到直接調用,如果沒有找到返回nil
消息傳遞
1、緩存查找
例: 給定值是SEL 目標值是對應bucket_t的IMP。
- 首先給定的函數選擇器通過一個函數來映射出bucket_t在數組當中位置,這個過程是hash查找
- 哈希查找實際上,通過給定的一個值比如說方法的選擇器經過哈希函數的算法酸楚的值實際上就是給定值在對應數組當中所對應的索引位置。
① f(key) = key & mask
② 通過哈希查 解決查找效率的問題
③ 通過哈希查找,找到對應的bucket_t,就可以拿到IMP
2、當前類中查找
對于已排序好的列表,采用二分查找算法查找方法對應執行函數
對于沒有排序的列表,采用一般遍歷查找方法對應執行函數
3、父類逐級查找
消息轉發
Method-Swizzling
Method method1 = class_getInstanceMethod(self, @selector(test));
Method method2 = class_getInstanceMethod(self, @selector(Mytest));
method_exchangeImplementations(method1, method2);
使用場景
替換系統的viewdidLoad或者viewwillDismiss中,在替換的方法中,添加我們自己的代碼,例如添加時長統計,來統計進入當前界面的時長
動態添加方法
編譯的時候沒有這個方法,運行時有這個方法需要調用performSelector方法,在消息轉發中的第一次處理函數resolveInstanceMethod動態添加DoThings:Num:方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@",NSStringFromSelector(sel));
if(sel == @selector(DoThings:Num:)){
class_addMethod([self class], sel, (IMP)MyMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void MyMethodIMP(id self,SEL _cmd,NSString * name,NSInteger num){
NSLog(@"調用了消息轉發的內容");
NSLog(@"%@",NSStringFromSelector(_cmd));
NSLog(@"%@",name);
NSLog(@"%ld",(long)num);
}
動態添加調用的函數
class_addMethod([self class], sel, (IMP)MyMethodIMP, "v@:");
動態添加函數參數說明
動態方法解析
@dynamic
-動態運行時語言將函數決議推遲到運行時。
① 當我們把一個屬性標示為@dynamic的時候代表著不需要編譯器在編譯時,為我們生成屬性的setter跟getter方法的具體實現,而在在具體運行時我們調用了setter、geter方法的時候,再去添加具體的實現。
② 這種功能也只有動態運行時特性的語言支持
- 編譯時語言在編譯期進行函數決議
MST
nsobject有父類嗎?
沒有nsobject是根類
當在跟類中的類方法沒找到,但是有同名的實例方法實現,那么會不會崩潰?會不會產生實際的調用?
由于根元類對象的superclass指針指向了根類對象,去查找實例同名的實例方法,如果找到就會執行同名的實例方法調用
下圖中打印的是什么?
[self class]會轉化成
objc_msgSend(<#id _Nullable self#>, <#SEL _Nonnull op, ...#>)
phone的實例對象,[self class],通過isa找到phone類對象,phone類對象是沒有class的,通過superclass找到父類的。。。直到查找到根類nsobject,有calss的實現,打印出來的自然是phone
[super class]會轉化成
objc_msgSendSuper(<#struct objc_super * _Nonnull super#>, <#SEL _Nonnull op, ...#>)
objc_msgSendSuper實際的接收者仍然是phone的這個實例對象,objc_msgSendSuper傳遞的含義,從phone的這個實例對象的父類開始查找, 直到查找到根類nsobject有class的實現,所以打印出來的結果還是phone
打印結果都是phone
對頁面的進出添加進出信息
使用MethodSwizzling來交換viewDidLoad和viewWillAppear,在替換代碼中添加一些埋點操作
[obj foo]和objc_msgSend()函數之間有什么關系?
- 向 obj對象發送一條foo的消息,[obj foo]在編譯期處理以后就變成了objc_msgSend(obj,@selector(foo))
- 由于沒有參數,所以 objc_msgSend函數調用只有2個參數
- 然后就開始了runtime的消息傳遞過程
runtime如何通過Selector找到對應的IMP地址的?
- 查找當前實例所對應類對象的緩存,是否有selelctor對應imp實現如果緩存命中了,我們就把命中的緩存函數返回給調用方。
- 如果緩存沒有就去當前類的方法列表查找selector對應的imp實現
- 當前類如果沒有命中,就根據當前類的superclass逐級查找父類的方法列表。。。直到根類對象,直到查到selector方法的imp實現
能否向編譯后的類中增加實例變量?
class_ro_t ro代表的是readonly
編譯后的類是無法添加實例變量的。
能否向動態添加的類中增加實例變量?
可以的,動態添加的這個類過程當中只要在它調用注冊方法之前去完成實例變量的添加就是可以實現的
isa指針的含義?
- 非指針類型 isa值的部分代表calss地址
- 指針類型isa 代表class地址