在iOS底層系列12 -- 消息流程的快速查找和iOS底層系列13 -- 消息流程的慢速查找這兩篇文章中分別介紹了objc_msgSend
的快速查
找與方法列表的慢查找
,如果都沒有找到方法實現就會進入動態方法決議和消息轉發。
-
動態方法決議
:慢速查找流程未找到方法實現時,會執行一次動態方法決議; -
消息轉發
:如果動態方法決議仍然沒有找到方法實現時,則進行消息轉發; - 如果
動態方法決議
與消息轉發
都沒有做任何操作,就會出現崩潰報錯,即unrecognized selector sent to instance xxxx
動態方法決議
- 在慢速查找中沒有找到方法實現,會嘗試進行一次動態方法決議,源碼實現如下:
static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
- 判斷當前類如果不是元類,執行實例方法的動態方法決議
resolveInstanceMethod
; - 當前類是元類,執行類方法的動態方法決議
resolveClassMethod
,如果在元類中沒有找到或者為空,則在元類的實例方法的動態方法決議resolveInstanceMethod中查找,主要是因為類方法在元類中是實例方法,所以還需要查找元類中實例方法的動態方法決議
- 實例方法的動態方法決議
resolveInstanceMethod
源代碼實現如下:
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
- 在發送resolveInstanceMethod消息前,首先查找cls類中是否有該方法的實現,即通過lookUpImpOrNil方法又會進入lookUpImpOrForward慢速查找流程,但這次查找的是
resolveInstanceMethod方法
- 如果沒有實現,則直接返回;
- 如果有實現,則執行resolveInstanceMethod方法;
- 實例方法的動態方法決議代碼測試:
@interface YYPerson : NSObject
- (void)walk;
+ (void)speak;
@end
#import "YYPerson.h"
#import <objc/runtime.h>
@implementation YYPerson
- (void)walk_resolve{
NSLog(@"walk_resolve");
}
//給當前類動態添加一個方法和方法實現
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if ([NSStringFromSelector(sel) isEqualToString:@"walk"]) {
NSLog(@"walk -- ");
IMP imp = class_getMethodImplementation(self, @selector(walk_resolve));
Method method = class_getInstanceMethod(self,@selector(walk_resolve));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self,sel,imp,type);
}
return [super resolveInstanceMethod:sel];
}
@end
- 在resolveInstanceMethod方法內部打下斷點,當應用停在斷點,控制臺輸入bt命令,打印出函數調用堆棧如下所示:
Snip20210301_112.png
- 類方法的動態方法決議代碼測試:
#import "YYPerson.h"
#import <objc/runtime.h>
@implementation YYPerson
+ (void)speak_resolve{
NSLog(@"speak_resolve");
}
+ (BOOL)resolveClassMethod:(SEL)sel{
if ([NSStringFromSelector(sel) isEqualToString:@"speak"]) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("YYPerson"), @selector(speak_resolve));
Method method = class_getInstanceMethod(objc_getMetaClass("YYPerson"), @selector(speak_resolve));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("YYPerson"),sel,imp,type);
}
return [super resolveClassMethod:sel];
}
@end
- 斷點同上設置,函數調用堆棧如下:
Snip20210301_113.png
- 若動態方法決議沒有手動去實現,就會進入消息轉發的流程;
消息轉發
- 消息轉發的處理主要分為兩個部分:
-
快速轉發
:當慢速查找,以及動態方法決議均沒有找到實現時,進行消息轉發,首先是進行快速消息轉發,即執行forwardingTargetForSelector
方法;- 如果返回消息接收者,在消息接收者中還是沒有找到,則進入另一個方法的查找流程;
- 如果返回nil,則進入慢速消息轉發;
-
慢速轉發
:執行到methodSignatureForSelector
方法;- 如果返回的方法簽名為nil,則直接崩潰報錯;
- 如果返回的方法簽名不為nil,走到
forwardInvocation
方法中,對invocation事務進行處理,如果不處理也會造成崩潰報錯;
-
- 消息快速轉發的代碼測試如下:
#import <Foundation/Foundation.h>
@interface YYStudent : NSObject
- (void)walk;
@end
#import "YYStudent.h"
@implementation YYStudent
- (void)walk{
NSLog(@"%s",__func__);
}
@end
#import "YYPerson.h"
#import <objc/runtime.h>
#import "YYStudent.h"
@implementation YYPerson
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(walk)) {
return [YYStudent new];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
YYPerson類沒有實現walk實例方法,實現
forwardingTargetForSelector
函數可將消息轉發給YYStudent實例對象(實現了walk方法);forwardingTargetForSelector
函數內部斷點調試如下:
Snip20210302_114.png
- 可以看出消息的快速轉發調用了CoreFoundation框架;
- 若當消息的快速轉發沒有進行處理,就會進入消息的慢速轉發流程,測試代碼如下:
#import "YYPerson.h"
#import <objc/runtime.h>
#import "YYStudent.h"
@implementation YYPerson
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(walk)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
SEL sel = anInvocation.selector;
YYStudent *student = [[YYStudent alloc]init];
if ([student respondsToSelector:sel]) {
[anInvocation invokeWithTarget:student];
}else{
[anInvocation doesNotRecognizeSelector:sel];
}
}
@end
-
methodSignatureForSelector
為需要慢速轉發的消息提供方法簽名; -
forwardInvocation
系統會為需要轉發的消息創建一個NSInvocation事務對象,我們可以對NSInvocation事務進行處理,如果不處理也不會崩潰報錯;
總結
綜合 iOS底層系列12 -- 消息流程的快速查找,iOS底層系列13 -- 消息流程的慢速查找以及本篇,objc_msgSend
發送消息的整體流程就分析完成了,現作出如下總結:
-
快速查找流程
:首先在類的緩存cache中查找指定方法的實現; -
慢速查找流程
:如果緩存中沒有找到,則在類的方法列表中查找(二分法),如果還是沒找到,則根據類/元類的繼承鏈在父類的緩存和方法列表中查找,一直遞歸到nil; -
動態方法決議
:如果慢速查找還是沒有找到時,第一次補救機會就是嘗試一次動態方法決議,即實現resolveInstanceMethod/resolveClassMethod 方法; -
消息轉發
:如果動態方法決議沒有處理,則進行消息轉發,消息轉發中有兩次補救機會:快速轉發+慢速轉發
- 如果消息轉發也沒有處理,則程序直接報錯崩潰
unrecognized selector sent to instance
super的本質
- 定義類YYPerson,實現一個run方法;
- 定義一個子類YYStudent,繼承自YYPerson;
- 測試代碼如下:
#import "YYStudent.h"
@implementation YYStudent
- (instancetype)init{
self = [super init];
if (self) {
NSLog(@"[self class] = %@",[self class]);
NSLog(@"[self superclass] = %@",[self superclass]);
NSLog(@"[super class] = %@",[super class]);
NSLog(@"[super superclass] = %@",[super superclass]);
}
return self;
}
- (void)run{
[super run];
NSLog(@"%s",__func__);
}
@end
- 終端輸入
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc YYStudent.m
,看到YYStudent類的run方法的C++底層實現如下:
static void _I_YYStudent_run(YYStudent * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("YYStudent"))}, sel_registerName("run"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_c5_l8bnxw0d2w92f4439t_r8qjc0000gn_T_YYStudent_1b9e06_mi_4,__func__);
}
- 看到[super run],轉成了
objc_msgSendSuper(struct objc_super,selector)
- 其中第一個參數是一個
objc_super
類型的結構體,內部有兩個參數分別為:self
與self的父類
,也就是YYStudent的實例對象與YYPerson類; -
[super message],底層轉成objc_msgsendSuper({self,父類對象},@selector(message))
,消息的接受者依然是當前實例對象,只不過消息的查找越過了當前類,直接去其父類YYPerson中去查找;
#import <Foundation/Foundation.h>
#import "YYStudent.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYStudent *student = [[YYStudent alloc]init];
}
return 0;
}
- 控制臺打印結果:
Snip20210630_37.png
- 看到 [self class]與[super class]打印結果相同;
- 首先[self class] -->
objc_msgSend(self,@selector(class))
- 然后[super class] -->
objc_msgSendSuper({self,YYPerson},@selector(class)
-
class
方法實現是在NSObject基類里面的,其實現如下:
- (Class)class{
object_getClass(self);
}
- 也就是說class方法返回的結果,取決于消息的接受者self;
- 所以 [self class]與[super class] 消息的接受者都是self,即YYStudent類的實例對象,所以最終的調用結果是相同的,都返回YYStudent類;
-
superClass
的方法實現是在NSObject基類里面,其實現如下:
- (Class) superClass{
object_get SuperClass(object_getClass(self));
}
- 所以[self superClass]與[super superClass] 返回的都是YYPerson類;
objc_msgsend(instance,@selector)底層實現
- 底層實現邏輯如下圖所示:
objc_msgSend.png
- 主要分為三個階段:
- 第一個階段:在緩存Cache_t中查找,是用匯編語言實現的;
- 第二個階段:在類class結構體內部的class_rwe_t中的方法列表查找,是通過C語言實現的;
- 第三個階段:動態方法決議與消息的轉發;