iOS-OC runtime - objc_msgSend( )详解与应用

本文主要从源码层面梳理消息发送的整个流程,内容包括
1.消息发送,
2.动态方法解析,
3.消息转发

文中涉及的代码我会标记 行号 和 方法名,便于读者自行验证,我使用的runtime源码版本是objc4-779.1,不同版本 行号可能出现差异.

1>文件名objc-msg-arms.s,可以看到,它是由汇编编写的,这样运行效率更高, ENTRY代表方法的入口,消息发送从ENTRY _objc_msgSend这里开始执行,首先通过isa指针拿到对象的类,然后再到方法缓存中查找对应的方法,对应的代码是GetClassFromIsaCacheLookup NORMAL, _objc_msgSend.

380:
    ENTRY _objc_msgSend
    
    cbz r0, LNilReceiver_f

    ldr r9, [r0]        // r9 = self->isa
    GetClassFromIsa         // r9 = class
    CacheLookup NORMAL, _objc_msgSend
    找到方法缓存的实现地址
    bx  r12         在这就直接调用方法了

    CacheLookup2 NORMAL, _objc_msgSend
    没有找到缓存的方法
    ldr r9, [r0]        // r9 = self->isa
    GetClassFromIsa         // r9 = class
    b   __objc_msgSend_uncached 既然没有找到方法,就去从类,父类,元类中查找

LNilReceiver:
    // r0 is already zero
    mov r1, #0
    mov r2, #0
    mov r3, #0
    FP_RETURN_ZERO
    bx  lr  

    END_ENTRY _objc_msgSend

2>缓存查找的方法是,通过 SEL & mask来找到方法缓存的位置,如果有值就直接取出,对这个不太清楚的,可以看我的上一篇 传送门

244:
.macro CacheLookup
cache-miss
LLookupStart$1:

    ldrh    r12, [r9, #CACHE_MASK]  // r12 = mask
    ldr r9, [r9, #CACHE]    // r9 = buckets
.if $0 == STRET
SEL & mask 获取方法imp的地址
    and r12, r12, r2        // r12 = index = SEL & mask
.else
    and r12, r12, r1        // r12 = index = SEL & mask
.endif
    add r9, r9, r12, LSL #3 // r9 = bucket = buckets+index*8
    ldr r12, [r9, #CACHED_SEL]  // r12 = bucket->sel
6:
.if $0 == STRET
    teq r12, r2
.else
    teq r12, r1
.endif
    bne 8f
    ldr r12, [r9, #CACHED_IMP]  // r12 = bucket->imp

.if $0 == STRET
    tst r12, r12        // set ne for stret forwarding
.else
    // eq already set for nonstret forwarding by `teq` above
.endif

.endmacro

3> 缓存中没有方法,_objc_msgSend_uncached 从这里继续跟进

710: STATIC_ENTRY __objc_msgSend_uncached
715: MethodTableLookup NORMAL
684: blx _lookUpImpOrForward 这里就到类中去查找方法了

4> 接下来就是核心代码 在文件objc-runtime-new.mm

5988: 
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    这里再到缓存中查找一遍,防止动态添加了方法
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }
    runtimeLock.lock();
    checkIsKnownClass(cls);

    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

    runtimeLock.assertLocked();
    curClass = cls;
    for (unsigned attempts = unreasonableClassCount();;) {
    拿到当前类的方法列表去找sel,找到就跳到 done,
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        利用父类的指针做方法寻找
        if (slowpath((curClass = curClass->superclass) == nil)) {
            imp = forward_imp;
            break;
        }

        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

       查找父类的缓存
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            break;
        }
        if (fastpath(imp)) {
        父类中找到缓存,跳出,并缓存到自己的类中
            goto done;
        }
    }

    在缓存 父类中都没有找到方法,这个时候尝试动态方法解析
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done: 
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

5> 动态方法解析

5928:
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
    如果不是元类,调用这个方法[cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        如果是元类,调用这个方法[nonMetaClass resolveClassMethod:sel]
        和 [cls resolveInstanceMethod:sel] 这个方法.
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

5.1 动态方法解析代码实践:

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    sel:你需要动态解析的方法名
    other: 你指派需要响应的方法,
    返回值为YES 证明需要动态解析
    if (sel == @selector(test)) {
        Method method = class_getInstanceMethod(self, @selector(other));
        class_addMethod(self,
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));
        return YES;
    }
    NSLog(@"%s",__func__);

    return [super resolveInstanceMethod:sel];
}

5.1.1 同样可以调用C语言函数

void c_other(id self ,SEL _cmd){
    NSLog(@"%s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    c_other :C语言函数名,
    v16@0:8 函数编码
    if (sel == @selector(test)) {
        class_addMethod(self,
                        sel,
                        (IMP)c_other,
                        "v16@0:8");
        return YES;
    }
    NSLog(@"%s",__func__);

    return [super resolveInstanceMethod:sel];
}

6>源码中,我没有找到相关消息转发的代码,是不开源的,

这里贴出MJ老师的消息发送的流程图
image.png

动态方法解析
image.png

7> 消息转发就在这四个方法里进行,这个是类方法的调用,对象方法把"+ " 换成 "-"即可

方法的调用顺序 由上至下
+ (BOOL)resolveClassMethod:(SEL)sel{  
    return NO;  是否需要动态解析方法,默认返回为NO
}

+ (id)forwardingTargetForSelector:(SEL)aSelector{
    返回值为nil 说明要消息转发 ,会调用methodSignatureForSelector,默认返回值为nil
    返回其他类:则调用其他类的 aSelector 方法,
    注意:如果返回 对象,则调用对象方法,返回类,则调用类方法
//    return nil; 
    return [[MJCat alloc]init] ;
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    判断方法名,返回方法签名
    if(aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
       return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    这里可以指派其他类,去响应方法,如果anInvocation.selector 不指定,
    则调用传过来的同名的方法,如果指定,则调用指定的方法.
    anInvocation.selector = @selector(catTest);
    [anInvocation invokeWithTarget:[MJCat class]];
}

如图:

image.png

说明
1> 消息转发的疑问: 类方法里面如果返回的对象,即return [[MJCat alloc]init] ;,会调对象方法,为什么类方法里面能够调用对象方法???

解答: 我们知道,对象方法可以访问外部属性,类方法不可以,本质来说就是,所有的对象方法,成员变量都放在类对象中,而类方法存放在元类中,而元类中只保存了类方法,所以我们平时开发中,类方法中不能调用对象方法,也不能访问外部变量,关于这点可以看我之前的文章 传送门,而消息转发是单纯的objc_msgSend([MJCat class], test),你给我类,我就去调用+(void)test,你给我objc_msgSend(cat, test), 我就调用-(void)test.

2> 消息转发可以做到指派其他的类响应同名的方法,也可以指派其他类的任意方法来响应该方法,比如我调用- [person instanceMethod],消息转发后,可以是- [cat intanceMethod],-[cat instanceOtherMethod],+[MJCat classMethod].

小结:

1.以后说明问题最好还是用图片,或者举例,文字说明晦涩难懂,自己看都云里雾里~~~ 2. 所有涉及的知识点是我自己实践过的,简友们可放心食用,如果有不对的地方,欢迎留言.

小知识点记录

#define MJMask 1<<2  //0b0000 0100
~MJMask => 0b1111 1011//按位取反

方法还可以这样转换

struct method_t{
    SEL sel;
    char *types;
    IMP imp;
};

+ (BOOL)resolveInstanceMethod:(SEL)sel{

    if (sel == @selector(test)) {
        这两个是等价的
        struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
        Method method1 = class_getInstanceMethod(self, @selector(other));

        class_addMethod(self,
                        sel,
                        method->imp,
                        method->types);
        return YES;
    }
    NSLog(@"%s",__func__);

    return [super resolveInstanceMethod:sel];
}

你可能感兴趣的:(iOS-OC runtime - objc_msgSend( )详解与应用)