本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !
Runtime的消息机制
方法调用
在OC中的方法调用都转换为运行时API-->
objc_msgSend()
函数的调用; objc_msgSend如果找不到合适的方法进行调用,会报unrecognized selector sent to instance
(该方法找不到)的错误
关于SEL
- SEL: @selecter 方法选择器 和 runtime的API-->
sel_registerName()
函数作用等同
objc_msgSend()的执行流程三阶段:消息发送
、动态方法解析
、消息转发
消息发送
- 消息发送调用成功,是不会进入动态方法解析、消息转发;
逻辑解读:
- 判断消息接收者(receiver)是否为空(是否为0),是直接return;
- 查找缓存 -- >CacheLookup,找到直接调用结束查找;
- 拿到class_rw_t中的methods进行遍历(如果是有序的就直接二分查找(折半查找),无序直接for循环),找到了底层执行 goto done 返回imp,并添加到缓存中并结束查找;
- 当前类找不到,通过superclass找到父类的缓存和方法列表中查找;步骤和以上三条相同;
- 如果父类方法列表中依然没有找到,就去父类的父类中找;一旦找到基类的方法列表中还是没有,就需要进入下一个阶段:动态方法解析;
动态方法解析
- 动态方法解析调用成功,是不会进入消息转发;
底层源码:
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
static void
_class_resolveMethod(id inst, SEL sel, Class cls)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
_class_resolveInstanceMethod(inst, sel, cls);
}
}
}
逻辑解读:
- 判断消息接收者是否曾经进行过动态解析
是:直接走消息转发-->_objc_msgForward_impcache
否:调用[cls resolveInstanceMethod:sel]或者[cls resolveClassMethod:sel]方法来动态解析方法
- 判断消息接收者是否曾经进行过动态解析
- 解析成功 底层执行 goto retry,标记为已经动态解析;
- 走消息发送流程:‘从receverClass的cache中查找方法’ 这一步开始执行
#import <objc/runtime.h>
###一个类只做了 - (void) test 方法的声明,没有实现
- (void)other
{
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 获取其他方法
Method method = class_getInstanceMethod(self, @selector(other));
// 动态添加test方法的实现
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有动态添加方法,注意这里返回的布尔值底层并没有做任何逻辑处理,只是哪来做一个日志输出;
return YES;
}
return [super resolveInstanceMethod:sel];
}
###一个类只做了 + (void) test 方法的声明,没有实现
void c_other(id self, SEL _cmd)
{
NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 第一个参数是object_getClass(self)
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
动态添加方法
动态添加方法@2x.png@dynamic 关键字
- @dynamic是告诉编译器不用自动生成setter和getter方法的实现,等到运行时在添加方法实现,不影setter和getter方法的声明;
// 提醒编译器不要自动生成setter和getter的实现、不要自动生成成员变量
@dynamic age;
void setAge(id self, SEL _cmd, int age)
{
NSLog(@"age is %d", age);
}
int age(id self, SEL _cmd)
{
return 120;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(setAge:)) {
class_addMethod(self, sel, (IMP)setAge, "v@:i");
return YES;
} else if (sel == @selector(age)) {
class_addMethod(self, sel, (IMP)age, "i@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
消息转发@2x.png消息转发
知识点:
-
_objc_msgForward_impcache
消息转发底层入口函数,函数内部由汇编代码实现,内部核心代码:__objc_msgForward_stret
、__objc_msgForward
这两个函数是汇编函数; 底层最终会调用__forwarding__
方法,__forwarding__
方法
__objc_msgForward--> _objc_msgForward--> __forwarding__
-
_objc_msgForward
: 是 IMP 类型; 用于消息转发的;
当向一个对象发送一条消息,但它并没有实现的时候会尝试做消息转发。
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band condition register is NE for stret, EQ otherwise.
je __objc_msgForward_stret
// int __forwarding__
jmp __objc_msgForward
END_ENTRY __objc_msgForward_impcache
逻辑解读:
前提:在当前消息接收者体系中找不到方法的具体实现(自己没有能力来处理,将消息转发给别人来处理)
- 进入 int forwarding(void *frameStackPointer, int isStret) 函数
-
调用forwardingTargetForSelector:方法
返回值为空:表示没有提供消息接收者,这时就需要调用methodSignatureForSelector:方法
返回值不为空:objc_msgSend(返回值,SEL)forwardingTargetForSelector: 用来返回一个可以接收当前消息的消息接收者
-
-
调用methodSignatureForSelector:方法,要求返回方法签名 (所谓的方法签名:方法的返回值类型和参数类型)
返回值为空:调用doesNotRecognizeSelector:方法,表示这是未识别的选择器,抛出 unrecognized selector sent to instance (该方法找不到)
返回值不为空:调用forwardInvocation:方法forwardInvocation:方法中开发者可以自定义任何逻辑处理,这样就不会因为方法找不到而崩溃
-
doesNotRecognizeSelector:源码实现
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal(
"-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), 方法调用者类名
sel_getName(sel), 方法名
self 方法调用者
);
}
- int forwarding(void *frameStackPointer, int isStret) 函数底层实现是不开源的,并不是所有的runtimeAPI都开放源码,有部分深层次的API还是没有开源。
代码示例
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// objc_msgSend([[XYHCat alloc] init], aSelector)
如果返回空,就会触发methodSignatureForSelector:方法
return [[XYHCat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
// return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
// return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
anInvocation.target 方法调用者
anInvocation.selector 方法名
[anInvocation getArgument:NULL atIndex:0]
//此方法中的所有实现就相当于是当初调用的方法的一个实现,比如:[XYHPerson test] , test方法没有具体实现,当执行到forwardInvocation:方法时,其内部的实现最终就是test的实现
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
不带参数指定target,方法调用
// anInvocation.target = [[XYHCat alloc] init];
// [anInvocation invoke];
[anInvocation invokeWithTarget:[[XYHCat alloc] init]];
带参数指定target,方法调用
// 参数顺序:0 --> receiver、1 --> selector、2 --> other arguments
// int age;
// [anInvocation getArgument:&age atIndex:2];
// NSLog(@"%d", age + 10);
// anInvocation.target == [[XYHCat alloc] init]
// anInvocation.selector == test:
// anInvocation的参数:15
// [[[XYHCat alloc] init] test:15]
[anInvocation invokeWithTarget:[[XYHCat alloc] init]];
int ret;
//拿到返回值
[anInvocation getReturnValue:&ret];
NSLog(@"%d", ret);
}
类方法的消息转发
和对象方法一样,但在forwardingTargetForSelector:、methodSignatureForSelector:、forwardInvocation是需要执行 '+'开头的方法
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
// objc_msgSend([XYHCat class], @selector(test))
// [[[MJCat alloc] init] test]
if (aSelector == @selector(test)) return [XYHCat class];
return [super forwardingTargetForSelector:aSelector];
}
疑问:如果在类方法的forwardingTargetForSelector:中返回的不是类对象,而是一个实例对象且有对应的实例方法,能够调用成功吗 ?为什么?
可以的,因为在这里返回的对象不管是什么对象,只要其已经实现了对应的方法都会通过objc_msgSend()函数调用成功,只要有这个方法就行;
@implementation XHYCat
+ (void)test
{
NSLog(@"%s", __func__);
}
- (void)test
{
NSLog(@"%s", __func__);
}
@end
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test))
//这么写最终会执行 - (void)test
return [[XHYCat alloc] init];
return [super forwardingTargetForSelector:aSelector];
}
特殊的NSProxy
-
NSProxy
是一个为对象定义接口的抽象父类,并且为其它对象或者一些不存在的对象扮演了替身
角色。具体的可以看下NSProxy的官方文档
说白了NSProxy
就是一个万能替身 - NSProxy是专门用来做消息转发的,内部有一个target属性,定位更加精准,效率非常高;
- NSProxy和NSObject是同一个级别的类,都是基类;
特殊:
- NSProxy对象不需要调用init初始化,因为它本来就没有init方法
- 查找方法时先判断当前继承自NSProxy的类自己有没有该方法,如果没有就跳过消息发送和动态解析,直接来到消息转发阶段;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel方法必须实现
- (void)forwardInvocation:(NSInvocation *)invocation必须实现
@interface NSProxy <NSObject> {
Class isa;
}
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
应用:
- 两个对象循环引用,为了不让彼此强引用就使用NSProxy来作为其中一个中间对象:target;
+ (instancetype)proxyWithTarget:(id)target
{
XYHProxy *proxy = [XYHProxy alloc];
proxy.target = target;
return proxy;
}
//消息转发到target
//返回方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
//内部实现方法调用
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
objc_msgSend()的执行流程 - 源码跟读
QQ20200402-161206@2x.pngENTRY _objc_msgSend 汇编源码
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
//x0寄存器:消息接收者-->receiver
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret //ret相当于c语言的return
END_ENTRY _objc_msgSend
网友评论