runtime学习系列之方法调用

苹果开源网站
官方文档

Objective-C里面,方法分为实例方法和动态方法,但是不管是实例方法还是动态方法,最终都会变为一句函数调用objc_msgSend;通常给对象发送一个未知消息的时候,程序往往会崩溃,并提示unrecognized selector send to instance,那么runtime是如何实现方法的调用呢?

首先要知道,Objective-C的继承体系是什么样子的?

在方法的调用的时候,实际上,实例方法是存方法类中,类对象是一个单例对象,在内存中只有一份,而类方法是存放在元类中。

断点调试方法调用

// 创建对象,并发送消息
Person *p = [[Person alloc] init];
[p testInstanceMethod]; // add a breakpoint

在这个方法打上断点,进入堆栈信息

屏幕快照 2019-07-21 20.35.51.png

点击 control + step into 就可以进入堆栈信息

屏幕快照 2019-07-21 20.39.00.png

objc_msgSend处添加断点,进入objc_msgSend函数中查看调用的方法:

屏幕快照 2019-07-21 20.39.35.png

这里调用了_objc_msgSend_uncached,因为这里是第一次调用,猜想如果是之后调用的话,会直接调用缓存,进入这个函数里面查看:

屏幕快照 2019-07-21 20.50.20.png

这里会调用_class_lookupMethodAndLoadCache3这样一个函数,然后在runtime的源码里面搜索,找到它的实现

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

这里的核心就是这个方法lookUpImpOrForward ,它所做的事情就是,查找cls的方法列表,匹配sel,找到对应的函数实现IMP并返回,否则,沿着继承链查找父类的方法列表,如果还是没有,会进入动态方法解析,看有没有动态添加方法,如果有,就将sel和添加的这个方法实现绑定,并添加到缓存中,否则,进入消息转发。

// 1.
// Try this class's method lists.
    {
        // 查找这个类的方法列表
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            // 缓存起来
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }
    
    // 2.
    for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        { // 查询父类的方法列表
            
            // 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;
            }
        }
    
    // 3.
    // 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;
    }
    
    // 4.
    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    // 进入消息转发流程
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);
消息调用流程图.png

在动态方法解析中,需要实现这样两个方法:

+resolveClassMethod // 类方法
+resolveInstanceMethod // 实例方法
void sendMsg(id self, SEL _cmd) {
    NSLog(@"来了");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *methodName = NSStringFromSelector(sel);
    if ([methodName isEqualToString:@"testInstanceMethod"]) {
        return class_addMethod([self class], sel, (IMP)sendMsg, "v@:");
    }
    return NO;
}
常见面试题

1.

NSLog(@"%@", [self class]); // objc_mgsSend
// 会构造一个结构体(self, getSuperClass)
// 接收消息的对象还是self 所以打印出来的时候是一样的
NSLog(@"%@", [super class]); // objc_msgSendSuper

这里需要注意的是 [super class]的调用实际上是调用的objc_msgSendSuper这个函数,

 * id objc_msgSendSuper(struct objc_super *super, SEL _cmd,...);
 *
 * struct objc_super {
 *      id  receiver;
 *      Class   class;
 * };

官方文档解释:
When it encounters a method call, the compiler generates a call to one of the functions objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, or objc_msgSendSuper_stret. Messages sent to an object’s superclass (using the super keyword) are sent using objc_msgSendSuper; other messages are sent using objc_msgSend. Methods that have data structures as return values are sent using objc_msgSendSuper_stret and objc_msgSend_stret.

super

A pointer to an objc_super data structure. Pass values identifying the context the message was sent to, including the instance of the class that is to receive the message and the superclass at which to start searching for the method implementation.

op

A pointer of type SEL. Pass the selector of the method that will handle the message.

...

A variable argument list containing the arguments to the method.

super是一个结构体,那么在调用class方法的时候,接受的对象其实还是self,所以两者最终打印的结构都是当前的类对象。

2.

NSLog(@"----%d", [[self class] isKindOfClass:[SubClass class]]);
NSLog(@"----%d", [self isKindOfClass:[SubClass class]]);

isKindOfClass比较的是什么?,首先这个调用的是类方法,进入方法实现

+ (BOOL)isKindOfClass:(Class)cls {
    // self: SubClass 类对象
    // object_getClass:
    // 1.如果self是类对象:得到的是元类对象
    // 2.如果self是对象:得到的是类对象
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

如果是 [obj isKindOfClass:[SubClass class]],obj是实例对象,调用的是

- (BOOL)isKindOfClass:(Class)cls {
    // tcls: 类对象
    // tcls == cls 比较的就是cls是不是类对象
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

你可能感兴趣的:(runtime学习系列之方法调用)