美文网首页
iOS 底层 - runtime之objc_msgSend

iOS 底层 - runtime之objc_msgSend

作者: 水中的蓝天 | 来源:发表于2020-04-02 17:36 被阅读0次

本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !

Runtime的消息机制

方法调用

在OC中的方法调用都转换为运行时API--> objc_msgSend()函数的调用; objc_msgSend如果找不到合适的方法进行调用,会报unrecognized selector sent to instance(该方法找不到)的错误

关于SEL

  • SEL: @selecter 方法选择器 和 runtime的API-->sel_registerName()函数作用等同

objc_msgSend()的执行流程三阶段:消息发送动态方法解析消息转发

消息发送

  • 消息发送调用成功,是不会进入动态方法解析、消息转发;
消息发送流程@2x.png

逻辑解读:

    1. 判断消息接收者(receiver)是否为空(是否为0),是直接return;
    1. 查找缓存 -- >CacheLookup,找到直接调用结束查找;
    1. 拿到class_rw_t中的methods进行遍历(如果是有序的就直接二分查找(折半查找),无序直接for循环),找到了底层执行 goto done 返回imp,并添加到缓存中并结束查找;
    1. 当前类找不到,通过superclass找到父类的缓存和方法列表中查找;步骤和以上三条相同;
    1. 如果父类方法列表中依然没有找到,就去父类的父类中找;一旦找到基类的方法列表中还是没有,就需要进入下一个阶段:动态方法解析;

动态方法解析

  • 动态方法解析调用成功,是不会进入消息转发;
动态方法解析@2x.png

底层源码:

/***********************************************************************
* _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);
        }
    }
}

逻辑解读:

    1. 判断消息接收者是否曾经进行过动态解析
      是:直接走消息转发-->_objc_msgForward_impcache
      否:调用[cls resolveInstanceMethod:sel]或者[cls resolveClassMethod:sel]方法来动态解析方法
    1. 解析成功 底层执行 goto retry,标记为已经动态解析;
    1. 走消息发送流程:‘从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

逻辑解读:

前提:在当前消息接收者体系中找不到方法的具体实现(自己没有能力来处理,将消息转发给别人来处理)

    1. 进入 int forwarding(void *frameStackPointer, int isStret) 函数
    1. 调用forwardingTargetForSelector:方法
      返回值为空:表示没有提供消息接收者,这时就需要调用methodSignatureForSelector:方法
      返回值不为空:objc_msgSend(返回值,SEL)

      forwardingTargetForSelector: 用来返回一个可以接收当前消息的消息接收者

    1. 调用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的类自己有没有该方法,如果没有就跳过消息发送和动态解析,直接来到消息转发阶段;
  1. (NSMethodSignature *)methodSignatureForSelector:(SEL)sel方法必须实现
  2. (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.png

ENTRY _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
 

相关文章

网友评论

      本文标题:iOS 底层 - runtime之objc_msgSend

      本文链接:https://www.haomeiwen.com/subject/vnbauhtx.html