iOS底层收集

iOS进阶-06 Objc_msgSend

2020-02-12  本文已影响0人  ricefun

Runtime

Runtime 是一套为c/c++/汇编提供运行时功能的api。

方法的本质探索

先上代码:

###Person类定义
@interface Person : NSObject
- (void)test_cat;
@end

@implementation Person

- (void)test_cat {
    NSLog(@"%s",__func__);
}
@end

###main函数调用
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        [p test_cat];
    }
    return 0;
}

使用clang 命令,将main.m文件转化为C++文件mian.cpp

//1.先cd到目标路径
cd /Users/ricefun/Desktop/Runtime.demo
//2.1使用clang命令查看(具体版)
clang -rewrite-objc main.m -o mian.cpp

阅读mian.cpp文件

mian.cpp文件部分源码
可以看到Person类的对象方法最终会转化为objc_msgSend()方法。
结论:方法的本质:objc_msgSend发送消息,其中id为消息的接收者,sel为方法的编号

objc_msgSend()

在objc_msgSend()为了找到函数实现过程中总的经历了两部分,方法查找消息转发机制

方法查找

方法查找又分为快速查找(汇编部分)和慢速查找(C/C++部分)```

快速查找(汇编部分)

快速查找部分是用汇编写的,之所以这么做的原因有二:
1.汇编部分其实是在cache中找实现,既然是cache目的就是提高速度,不就要求快吗,而汇编是一种机器语言,运算速度快。
2.在C语言中不可能写一个函数来保留未知的参数并且跳转到任意一个韩函数。简单点:C语言不满足完成这件事的必要特性(能力)。

###汇编部分主要源码
LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    ret

    END_ENTRY _objc_msgSend


    ENTRY _objc_msgLookup
    UNWIND _objc_msgLookup, NoFrame
    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LLookup_NilOrTagged //  (MSB tagged pointer looks negative)
#else
    b.eq    LLookup_Nil
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
LLookup_GetIsaDone:
    CacheLookup LOOKUP      // returns imp

#if SUPPORT_TAGGED_POINTERS
LLookup_NilOrTagged:
    b.eq    LLookup_Nil // nil check

    // tagged
    mov x10, #0xf000000000000000
    cmp x0, x10
    b.hs    LLookup_ExtTag
    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]
    b   LLookup_GetIsaDone

LLookup_ExtTag: 
    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   LLookup_GetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LLookup_Nil:
    adrp    x17, __objc_msgNil@PAGE
    add x17, x17, __objc_msgNil@PAGEOFF
    ret

    END_ENTRY _objc_msgLookup

    
    STATIC_ENTRY __objc_msgNil

    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    ret
    
    END_ENTRY __objc_msgNil


    ENTRY _objc_msgSendSuper
    UNWIND _objc_msgSendSuper, NoFrame

    ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
    CacheLookup NORMAL      // calls imp or objc_msgSend_uncached

    END_ENTRY _objc_msgSendSuper

    // no _objc_msgLookupSuper

    ENTRY _objc_msgSendSuper2
    UNWIND _objc_msgSendSuper2, NoFrame

    ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
    ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
    CacheLookup NORMAL

    END_ENTRY _objc_msgSendSuper2

    
    ENTRY _objc_msgLookupSuper2
    UNWIND _objc_msgLookupSuper2, NoFrame

    ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
    ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
    CacheLookup LOOKUP

    END_ENTRY _objc_msgLookupSuper2


.macro MethodTableLookup
    
    // push frame
    SignLR
    stp fp, lr, [sp, #-16]!
    mov fp, sp

    // save parameter registers: x0..x8, q0..q7
    sub sp, sp, #(10*8 + 8*16)
    stp q0, q1, [sp, #(0*16)]
    stp q2, q3, [sp, #(2*16)]
    stp q4, q5, [sp, #(4*16)]
    stp q6, q7, [sp, #(6*16)]
    stp x0, x1, [sp, #(8*16+0*8)]
    stp x2, x3, [sp, #(8*16+2*8)]
    stp x4, x5, [sp, #(8*16+4*8)]
    stp x6, x7, [sp, #(8*16+6*8)]
    str x8,     [sp, #(8*16+8*8)]

    // receiver and selector already in x0 and x1
    mov x2, x16
    bl  __class_lookupMethodAndLoadCache3

    // IMP in x0
    mov x17, x0
    
    // restore registers and return
    ldp q0, q1, [sp, #(0*16)]
    ldp q2, q3, [sp, #(2*16)]
    ldp q4, q5, [sp, #(4*16)]
    ldp q6, q7, [sp, #(6*16)]
    ldp x0, x1, [sp, #(8*16+0*8)]
    ldp x2, x3, [sp, #(8*16+2*8)]
    ldp x4, x5, [sp, #(8*16+4*8)]
    ldp x6, x7, [sp, #(8*16+6*8)]
    ldr x8,     [sp, #(8*16+8*8)]

    mov sp, fp
    ldp fp, lr, [sp], #16
    AuthenticateLR

.endmacro

简单的捋一下汇编部分:

慢速查找(C/C++部分)

慢速查找(C/C++部分),将以源码的形式展示,主要查看几个函数,关键代码都有注释,不在赘述。
lookUpImpOrForward()

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    //准备一些参数
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    //cache不一定是都是NO的,如果有cache == YES 会去查找一次
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
   //线程锁,防止读写冲突
    runtimeLock.lock();
    //验证下是否是有效类
    checkIsKnownClass(cls);
    //判断下类是否实现,没有就实现下
    if (!cls->isRealized()) {
        realizeClass(cls);//给bits->data.rw&ro ...赋值
    }
    // 初始化配置类的一些参数
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.lock();
    }
   /*****以上都是一些准备工作,下面才是真正的查找过程*/
     
 retry:    
    runtimeLock.assertLocked();
    //再次去cache找一次
    imp = cache_getImp(cls, sel);
    if (imp) goto done;
   //遵循继承链(类->父类->NSObject->nil)查找
   //从 method lists 找
    {//这里括号是为了形成局部作用域,防止命名冲突
        Method meth = getMethodNoSuper_nolock(cls, sel);//看方法名字:不从父类中找,不就是从本身找吗
        if (meth) {//如果找到了,就进行cache缓存
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

   // 找不到,就冲父类的cache和method lists找
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.
   //本类和父类都没有找到时,就会启动消息转发机制
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
       //这个就是启动消息转发机制的方法,将在下面的消息转发机制中具体介绍这里不再过多赘述
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    //内部实现[XXX xxx] unrecognized selector sent to instance逻辑
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
   //线程锁解锁
    runtimeLock.unlock();
   //返回imp
    return imp;
}

getMethodNoSuper_nolock() 函数

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?
    //遍历方法list查找
    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
       //search_method_list()内部其实是用二分法查找
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

_objc_msgForward_impcache 汇编

    STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b   __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

    
    ENTRY __objc_msgForward
       //调用__objc_forward_handler方法
    adrp    x17, __objc_forward_handler@PAGE
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward
        ...
        ...

_objc_forward_handler

void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;

#else

// Default forward handler halts the process.
__attribute__((noreturn)) void 
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

objc_defaultForwardHandler方法中就会展示我们日常熟悉的报错unrecognized selector sent to instance
以上就是方法慢速查找流程

消息转发机制

消息转发机制一般分为4步(也可分为3步):

动态方法决议(解析)

方法在报错之前其实还进行了一段消息转发流程先看源码:
在上面的IMP lookUpImpOrForward()方法中有这样一个方法:

void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {//不是元类,调用Instance方法
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {//是元类,调用Class方法
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

class_resolveInstanceMethod实例方法为例,程序就会对当前类发出一个resolveInstanceMethod()方法,如果当前类重写过,就会进入处理,没有父类也有实现,那么最终会走到根父类NSObject中默认实现, 返回NO

NSObject.mm
+ (BOOL)resolveClassMethod:(SEL)sel {
    return NO;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

所以我们可以在重写的方法中添加方法,类似这样

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(eat)) {
        IMP sleepImp = class_getMethodImplementation(self, @selector(sleep));
        Method sleepMethod = class_getInstanceMethod(self, @selector(sleep));
        const char *sleepType = method_getTypeEncoding(sleepMethod);
        return class_addMethod(self, sel, sleepImp, sleepType);
    }
    return [super resolveInstanceMethod:sel];
}

特别注意:上面是以实例方法为例的如果是类方法,在走了_class_resolveClassMethod类方法后,发现没有做处理,还会走_class_resolveInstanceMethod实例方法;即:
类方法如果找不到 - 动态方法决议
1.resolveClassMethod 处理了-->只注意元类2.resolveClassMethod 没有处理 --> resolveInstanceMethod - 类 - 元类 - NSObject
所以:可以在NSObject分类中重写resolveInstanceMethod类方法,处理所有方法;eg代码如下

#import "NSObject+MessageHandle.h"
#import "runtime.h"
@implementation NSObject (MessageHandle)
//全局防崩溃处理
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    //if一个个判断太麻烦
    if (sel == @selector(say)) {
        IMP sleepImp = class_getMethodImplementation(self, @selector(sleep));
        Method sleepMethod = class_getInstanceMethod(self, @selector(sleep));
        const char *sleepType = method_getTypeEncoding(sleepMethod);
        return class_addMethod(self, sel, sleepImp, sleepType);
    }
//    if (sel == @selector(sleep)) {
//        ...
//    }
    
    //综合处理  rf_路由_事物
    NSString *selString =  NSStringFromSelector(sel);
    if ([selString hasPrefix:@"rf_xxxxx"]) {//rf_是自定义方法开发
        //1.bug收集
        
        //2.异常处理
        if ([selString hasPrefix:@"rf_home_xxxxx"]) {
            RFRountTo(@"home");//跳转到首页
        } else if ([selString hasPrefix:@"rf_mine_xxxxx"]) {
            RFRountTo(@"mine");//跳转到我的
        }
        ...
    }
    
    //当然一般容错不会在方法决议里处理,在这里处理会压缩开发者的处理空间,一般放在完整消息转发中(NSInvocake)中,即最后一步
    
    return NO;
}
@end

快速转发阶段

当方法在动态决议中没有处理后,系统会调用forwardingTargetForSelector方法尝试着转发给其他类来处理

instrumentObjcMessageSends(BOOL flag):调试程序堆栈流程开关;路径:/tmp/msgSends

#import "Animal.h"
#import "runtime.h"

@implementation Person
//自己没有;-- 那就交给其他对象,因为其他对象可能有
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(say)) {
        //返回其他对象
        return [Person alloc];//Person类有该方法
    }
    [super forwardingTargetForSelector:aSelector];
}
@end

慢速转发阶段

如果快速转发流程也没有处理,那么就会进入慢速转发流程-消息签名完整的消息转发

//1.先方法签名:打包方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(say)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法签名
    }
    return [super methodSignatureForSelector:aSelector];
}

//2.完整消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    //事情 - 事物 综合处理
    SEL sel = [anInvocation selector];
    if ([[Person alloc] respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:[Person alloc]];
    }else {
        [super forwardInvocation:anInvocation];
    }

    //当然你可以在这里做综合处理,类似于CTMediator
}

tips:在methodSignatureForSelectorforwardInvocation之间系统还会调用一次resolveInstanceMethod方法,进一步去匹配签名过程

消息无法处理报错

如果上述3步都没有处理,那么久会抛出异常[XXX xxx] unrecognized selector sent to instance

完整代码
#import "Person.h"
#import "runtime.h"

@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(say)) {
        IMP sleepImp = class_getMethodImplementation(self, @selector(sleep));
        Method sleepMethod = class_getInstanceMethod(self, @selector(sleep));
        const char *sleepType = method_getTypeEncoding(sleepMethod);
        return class_addMethod(self, sel, sleepImp, sleepType);
    }
    return [super resolveInstanceMethod:sel];
}
//自己没有;-- 那就交给其他对象,因为其他对象可能有
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(say)) {
        //返回其他对象
        return [Person alloc];//Person类有该方法
    }
    [super forwardingTargetForSelector:aSelector];
}

//1.先方法签名:打包方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(say)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法签名
    }
    return [super methodSignatureForSelector:aSelector];
}

//2.完整消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    //事情 - 事物 综合处理
    SEL sel = [anInvocation selector];
    if ([[Person alloc] respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:[Person alloc]];
    }else {
        [super forwardInvocation:anInvocation];
    }
}

@end

这个时候你再看这张图


消息转发流程图
面试题

1.isKindOfClass&isMemberOfClass

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
    BOOL re2 = [[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL re3 = [[Person class] isKindOfClass:[Person class]];
    BOOL re4 = [[Person class] isMemberOfClass:[Person class]];
    NSLog(@"\nre1:%hhd\nre2:%hhd\nre3:%hhd\nre4:%hhd",re1,re2,re3,re4);
    
    BOOL re5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
    BOOL re6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
    BOOL re7 = [[Person alloc] isKindOfClass:[Person class]];
    BOOL re8 = [[Person alloc] isMemberOfClass:[Person class]];
    NSLog(@"\nre5:%hhd\nre6:%hhd\nre7:%hhd\nre8:%hhd",re5,re6,re7,re8);
    }
    return 0;
}

先思考五秒再看打印结果:

2020-02-11 15:44:25.843470+0800 Test[44114:837672] 
re1:1
re2:0
re3:0
re4:0
2020-02-11 15:44:25.844011+0800 Test[44114:837672] 
re5:1
re6:1
re7:1
re8:1

解析:
我们先看其方法定义

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

####
 object_getClass((id)self)
这个方法中:如果self是对象,那返回类,如果self是类对象,那返回元类

1-4是类方法,5-8是对象方法

上一篇下一篇

猜你喜欢

热点阅读