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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容