一、消息傳遞
1、什么是消息
[a func1];
我們會把這種用方括號來調函數的方式稱為發消息。對于這個例子,就相當于我們給 a 這個對象發了個 func1 的消息(個人認為指令更好理解)。
2、什么是 selector
selector 就是一個函數區分器。它只會給這個方法名一個唯一的哈希值。也就是說,如果只要兩個函數的方法名是一樣的,那么他們的 selector 的哈希值就是一樣的。
3、什么是 isa 指針
每個對象都有一個 isa 指針。
實例對象的?isa 指針指向的是該實例所屬的類對象,而類對象的 isa 指針指向元類對象。
那么如何區分類對象和元類對象呢?類對象存的是實例方法,不存類方法,而元類存的是類方法。
4、消息傳遞過程
1. 當一個方法要傳給一個實例對象,那么 runtime 系統就會通過這個實例對象的 isa 指針找到該對象屬于哪個類對象
2. 在這個類對象里的 dispatch table 找有沒有符合當前 selector 的函數實現
3. 如果有,直接調用。
4. 如果沒有,就不斷地沿著這個類對象的 superclass 指針一路沿著繼承鏈向上找,直到找到符合的函數實現或找到 NSObject 類。
5. 如果直到 NSObject 類都沒找到,就進入“消息轉發”的環節。
根據 selector 找對應的函數實現,runtime 做了一個小小的優化。就是給每個類對象一個緩存。這個緩存存的是在該類找過的實例方法實現和該類通過繼承得來的函數實現的地址。于是在找函數實現時,runtime 會先在這個類的緩存里找。如果沒找到才去這個類的 dispatch table 里找;再找不到就沿著繼承鏈往上找。
二、消息轉發的過程
1、什么是消息轉發
消息轉發就是當 runtime 在繼承鏈中找不到 selector 對應的函數實現后,就會進行消息轉發,即用其他對象來調這個方法。如果沒有其他類能成功調用這個方法,才會報錯。所以可以理解成消息轉發就是報錯前的最后一道防線。
2、消息轉發的過程
2.1. 動態方法解析(resolveInstanceMethod:)
+ (BOOL)resolveInstanceMethod:(SEL)selector //實例對象的動態方法解析調+ (BOOL)resolveClassMethod:(SEL)selector //類對象的動態方法解析調
@implementation Person// 動態方法解析入口:當找不到實例方法 sayHello 時會調用這個方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {if (sel == @selector(sayHello)) {// 使用 class_addMethod 動態添加方法實現class_addMethod(self, sel, (IMP)dynamicSayHello, "v@:");return YES;}return [super resolveInstanceMethod:sel];
}// 動態添加的方法實現
void dynamicSayHello(id self, SEL _cmd) {NSLog(@"Hello from dynamic method!");
}@end
?其中 selector 是未處理的方法。?返回值表示能否新增一個方法來處理。如果可以,返回 YES;否則就看看基類能不能處理這個 selector。
2.2. 備用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector;
// 備用接收者類
@interface BackupHandler : NSObject
- (void)sayHello;
@end@implementation BackupHandler
- (void)sayHello {NSLog(@"👉 BackupHandler 收到了 sayHello 消息!");
}
@end// 原始類:沒有實現 sayHello,但可以轉發給 backup
@interface MainObject : NSObject
@property (nonatomic, strong) BackupHandler *backup;
@end@implementation MainObject// 快速消息轉發:返回備用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {if (aSelector == @selector(sayHello)) {return self.backup; // 轉發給備用對象}return [super forwardingTargetForSelector:aSelector];
}@end
?這里 selector 也是未處理的方法。返回值為當前找到的備援接受者,如果沒有則返回nil,進入下一階段。
2.3. 完整消息轉發
- (void)forwardInvocation:(NSInvocation *)anInvocation;
// 目標處理者類
@interface RealHandler : NSObject
- (void)sayHello;
@end@implementation RealHandler
- (void)sayHello {NSLog(@"? RealHandler 處理了 sayHello 方法!");
}
@end// 原始類:完全不實現 sayHello
@interface MainObject : NSObject
@property (nonatomic, strong) RealHandler *handler;
@end@implementation MainObject// 第一步:提供方法簽名,告訴 runtime 方法是怎樣的
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if (aSelector == @selector(sayHello)) {// v@: → 返回 void, 參數是 self 和 _cmdreturn [NSMethodSignature signatureWithObjCTypes:"v@:"];}return [super methodSignatureForSelector:aSelector];
}// 第二步:收到完整調用對象,自己決定怎么處理
- (void)forwardInvocation:(NSInvocation *)anInvocation {SEL sel = [anInvocation selector];if ([self.handler respondsToSelector:sel]) {[anInvocation invokeWithTarget:self.handler]; // 手動轉發} else {[super forwardInvocation:anInvocation]; // 沒法處理就崩潰}
}@end
?其中 anInvocation 里面有原來消息的接收者、selector?、全部實參、返回值。
2.4. 報錯中斷
三、方法交換
1、dispatch table
每個類結構都包括以下兩個基本元素:指向基類的指針 & dispatch table。其中 dispatch table 就是一個 2 列的函數表,左邊是某個函數的?selector,右邊是這個函數的具體實現的地址。?
2、方法交換
本質就是把 dispatch table 里的兩個 address 的值交換。