iOS进阶回顾三「msgSend」

  • OC中的方法的调用,其实都是转换为objc_msgSend函数的调用
  • 继续调用进阶回顾二SFPerson对象,我们可以理解为personrun方法调用最终在c语言的如下方法调用:
  objc_msgSend(person, @selector(run));
  消息接收者(receiver):person
  消息名称:run
  • objc_msgSend的执行流程:消息发送 -> 动态解析 -> 消息转发
    查看源码
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// 寄存器:消息接受者 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:
CacheLookup NORMAL        // calls imp or objc_msgSend_uncached
// 查找缓存
#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  相当于C语言的return

END_ENTRY _objc_msgSend
  • 通过CacheLookup NORMAL方法进行缓存中查找,如果没有找到则从STATIC_ENTRY __objc_msgSend_uncached方法查找MethodTableLookup查找_class_lookupMethodAndCache3进行查找,然后查找IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)有点绕了
    3633CBC2E1B7A08BC498A0172CADD441.png
  • 查看方法是否在当前类对象的缓存中,如果没有就进行查找调用Method meth = getMethodNoSuper_nolock(cls, sel);方法查找
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    for (auto mlists = cls->data()->methods.beginLists(), 
              end = cls->data()->methods.endLists(); 
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

该方法又使用method_t *m = search_method_list(*mlists, sel);进行查找 发现search_method

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

    return nil;
}

再查找到 sorted方法

static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    assert(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

分别使用二分法线性查找进行查找

  • 消息转发流程图如下:


    228F997CCF8C839E11374E1D38CE0FA9.png
  • 总结:首先判断消息接受者receiver是否为nil,如果为nil直接退出,如果不为nil则从当前接受者类对象receiverClass的缓存cache中查找方法,如果查找到就调用,如果没有查找到,去当前接受者类对象的class_rw_t中查找方法,找到调用,没找到去父类的缓存cache中查找,如果找到调用,没找到去父类的class_rw_t中查找方法,找到调用,没找到就行动态方法解析

动态方法解析

  • 方法解析,新建一个SFPerson类增加personRun方法
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SFPerson : NSObject
+(void)personRun;
@end

NS_ASSUME_NONNULL_END

#import "SFPerson.h"
#import <objc/runtime.h>

@implementation SFPerson
struct method_t{
    SEL sel;
    char *types;
    IMP imp;
};
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(personRun)) {
        // 获取其他方法
        struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));

        // 动态添加test方法的实现
        class_addMethod(self, sel, method->imp, method->types);

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
-(void)other{
    NSLog(@"%s",__func__);
}

@end

  • 调用[SFPerson personRun]方法我们会得到other方法的调用,就是我们进行动态方法解析,把消息的实现解析到other方法进行操作.
  • 其中struct method_tMethod是一样的所以第二种实现方式:
+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(personRun)) {
//        获取其他方法
        //struct method_t 和 Method是一样的
        Method method = class_getInstanceMethod(self, @selector(other));
//        struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
        //动态添加方法
//        class_addMethod(self, sel, method->imp, method->types);
        class_addMethod(self, sel, method_getImplementation(method),method_getTypeEncoding(method));
        //返回YES代表动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
  • 还可以使用c语言的函数实现如下:

void c_other(id self,SEL _cmd){
    NSLog(@"c_other-%@ -%@",self,NSStringFromSelector(_cmd));
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(personRun)) {
        //动态添加方法
        class_addMethod(self, sel, (IMP)c_other, "v16@0:8");//v void空返回类型 16 两个指针16个字节 @第一个参数 对象类型 从0开始 :第二个参数 从第8个字节开始
        //返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
  • 如果我们调用的是类方法,那么其实现如下:
void c_other(id self, SEL _cmd)
{
    NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}

+ (BOOL)resolveClassMethod:(SEL)sel
{
    if (sel == @selector(personRun)) {
        // 第一个参数是object_getClass(self)
        class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
        return YES;
    }
    return [super resolveClassMethod:sel];
}
  • 动态解析总结:
  • 如果没有动态解析过,则调用+ (BOOL)resolveClassMethod:(SEL)sel 或者+(BOOL)resolveInstanceMethod:(SEL)sel方法进行解析,调用对应的方法
    1F612CEE19586417D1E7EE9D0319BDDF.png
  • 如果动态解析标记为已解析但是还是没有对应方法的实现则进行消息转发

消息转发

  • 上代码:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        // objc_msgSend([[SFCat alloc] init], aSelector)
        return [[SFCat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
//    anInvocation.target 方法调用者
//    anInvocation.selector 方法名
//    [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//    anInvocation.target = [[SFCat alloc] init];
//    [anInvocation invoke];

    [anInvocation invokeWithTarget:[[SFCat alloc] init]];
}
  • 首先调用forwardingTargetForSelector:方法,返回不是nil则进行objc_msgSend流程,如果返回值为nil则进行调用 methodSignatureForSelector:方法,如果不反悔nil则调用forwardInvocation:方法的调用,如果还是返回nil则调用doesNotRecognizeSelector:方法到此,消息转发完成报出异常。

    65F4944E8A0A4BEF2E290FC98898612D.png

  • 如果调方法有参数输入和返回值:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    // 参数顺序:receiver、selector、other arguments
//    int age;
//    [anInvocation getArgument:&age atIndex:2];
//    NSLog(@"%d", age + 10);
    
    
    // anInvocation.target == [[SFCat alloc] init]
    // anInvocation.selector == test:
    // anInvocation的参数:15
    // [[[SFCat alloc] init] test:15]
    
    [anInvocation invokeWithTarget:[[SFCat alloc] init]];
    
    int ret;
    [anInvocation getReturnValue:&ret];
    
    NSLog(@"%d", ret);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 消息发送和转发流程可以概括为:消息发送(Messaging)是 Runtime 通过 selector 快速查找 ...
    lylaut阅读 5,878评论 2 3
  • 关于OC中的消息发送的实现,在去年也看过一次,当时有点不太理解,但是今年再看却很容易理解。 我想这跟知识体系的构建...
    咖啡绿茶1991阅读 4,518评论 0 1
  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    made_China阅读 4,930评论 0 7
  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    SOI阅读 21,955评论 3 63
  • 以前不太喜欢和老公参加他们的校友活动,这次的主题和心理学相关,是我感兴趣的,同时也是为了增加新名单就去了,满载而归...
    庆云Teresa阅读 1,605评论 0 0