03.27 Runtime-iOS解讀

Class 結構詳解:
struct objc_class : objc_object {
Class isa;

Class superclass;
cache_t cache;--> 方法緩存
class_data_bits_t bits;
}
struct cache_t {
struct bucket_t *_buckets;//散列表
mask_t _mask;//散列表長度-1
mask_t _occupied;//已經緩存的方法數量
}
struct bucket_t {
cache_key_t _key;//@selecter(xxx) 作為key
MethodCacheIMP _imp;//函數的執行地址
}

Runtime-iOS解讀

Class 結構

函數調用底層走的是objc_msgSend:

正常的流程:

  1. 對象通過isa,找到函數所在的類對象
  2. 這時候先做緩存查找,如果緩存的函數列表中沒找到該方法
  3. 就去類的class_rw中的methods中找,如果找到了,調用並緩存該方法
  4. 如果類的class_rw中沒找到該方法,通過superclass到父類中,走的邏輯還是先查緩存,緩存沒有查類裡面的方法。
  5. 最終如果在父類中調用到了,會將方法緩存到當前類的方法緩存列表中

方法緩存

Runtime-iOS解讀

OC的消息機制

三個階段

  • 消息發送
  • 動態方法解析
  • 消息轉發

消息發送

當前類查找順序

  • 排序好的列表,採用二分查找算法查找對應的執行函數
  • 未排序的列表,採用一般遍歷的方法查找對象執行函數

父類逐級查找

Runtime-iOS解讀

Runtime-iOS解讀

動態方法解析

@interface IOSer : NSObject

- (void)interview;

@end

@implementation IOSer

- (void)test{

NSLog(@"%s",__func__);

}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(interview)) {

Method method = class_getInstanceMethod(self, @selector(test));

//動態添加interview方法
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));

return YES;

}
return [super resolveInstanceMethod:sel];
}

@end

----------------------------------------------

//調用
IOSer *ios = [[IOSer alloc]init];
[ios interview];

---------------------------------------------
結果,不會crash,進入了動態添加的方法了
2019-03-27 17:33:51.475810+0500 Runtime-TriedResolverDemo[11419:807797] -[IOSer test]

Runtime-iOS解讀

動態方法解析

消息轉發流程

  • 消息轉發流程1:forwardingTargetForSelector
@implementation IOSer

- (void)interview{

NSLog(@"%s",__func__);
}
@end

@interface Forwarding : NSObject

- (void)interview;

@end

@implementation Forwarding

- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(interview)) {

//objc_msgSend([[IOSer alloc]init],aSelector)
//由IOSer作為消息轉發的接收者
return [[IOSer alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}

@end

---------------------------------------------------------------
調用
Forwarding *obj = [[Forwarding alloc]init];
[obj interview];


---------------------------------------------
結果,不會crash,進入了動態添加的方法了

2019-03-27 17:57:45.130805+0800 Runtime-TriedResolverDemo[13776:9355195] -[IOSer interview]
  • 消息轉發流程2:forwardingTargetForSelector
@implementation Forwarding

//返回方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(interview)) {

//v16@0:8 = void xxx (self,_cmd)
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}

//NSInvocation - 方法調用
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//設置方法調用者
[anInvocation invokeWithTarget:[[IOSer alloc]init]];
}

@end

NSInvocation 其實封裝了一個方法調用,包括:

  • 方法名 - anInvocation.selector
  • 方法調用 - anInvocation.target
  • 方法參數 - anInvocation getArgument: atIndex:
Runtime-iOS解讀

消息轉發流程

類方法也可以實現消息轉發,但是用的是+ (id)forwardingTargetForSelector:(SEL)aSelector函數:因為__forwarding底層,是用receiver去發送 forwardingTargetForSelector消息,如果是類方法,receiver是類對象,所以要調用的是 “+” 方法;默認是沒有+ (id)forwardingTargetForSelector:(SEL)aSelector方法,可以先打- (id)forwardingTargetForSelector:(SEL)aSelector,“-” 替換成“+”,over。


分享到:


相關文章: