在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语言实现的;
- 第三个阶段:动态方法决议与消息的转发;