runtime进行曲,objc_msgSend的前世今生(一)

runtime小序曲一文中举出了runtime的三种应用方式:

  • Objective-C源代码,以objc_msgSend方法举例。
  • NSObject的方法。
  • Runtime的函数。

本文将就第一种方式中的objc_msgSend方法展开讲解。
学习进度:

一、objc_msgSend的两种姿势

很容易想到,OC中的两种方法调用的姿势:实例方法和类方法。下面我们通过一个简单样例的clang处理后的代码看一下。

// OC代码
// main.m
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
+ (void)c;
@end
@implementation A
- (void)b {
    NSLog(@"b");
}
+ (void)c {
    NSLog(@"c");
}
@end
int main(int argc, char * argv[]) {
    A *aObject = [[A alloc] init];
    // 实例方法调用
    [aObject b];
    // 类方法调用
    [A c];
}

通过xcrun -sdk iphonesimulator9.3 clang -rewrite-objc main.m将其转换为编译后的伪代码,即运行时执行的代码的伪代码(代码比较多,只列出我们想要的)。

// A类定义
typedef struct objc_object A;
// objc_getClass方法
struct objc_class *objc_getClass(const char *);
// sel_registerName方法
SEL sel_registerName(const char *str) __attribute__((availability(ios,introduced=2.0)));

// 对应上述main函数
int main(int argc, char * argv[]) {
    A *aObject = ((A *(*)(id, SEL))(void *)objc_msgSend)((id)((A *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("A"), sel_registerName("alloc")), sel_registerName("init"));
    // 实例方法调用
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)aObject, sel_registerName("b"));
    // 类方法调用
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("A"), sel_registerName("c"));
}

将上述实例方法调用和类方法调用的强制转换干掉之后看下。

实例方法

objc_msgSend(aObject, sel_registerName("b"));

可知,aObject是一个objc_object结构体,sel_registerName返回值为SEL。所以,这句话的意思大致是:向一个objc_object发送一个SEL。当然,具体怎么发送的这里先不谈。

类方法

objc_msgSend(objc_getClass("A"), sel_registerName("c"));

可知,objc_getClass返回值为objc_class,sel_registerName返回值为SEL。所以,这句话的意思大致是:向一个objc_class发送一个SEL。当然,具体怎么发送的这里先不谈。下文会说。

本节过后,大概有两个疑问:
1、objc_object、objc_class、SEL分别是什么东西?
2、objc_msgSend到底如何工作?

二、objc_object和objc_class

上一节留了两个问题,这里先回答第一个问题的一部分,即objc_object、objc_class是什么?可以参照runtime源码。在objc.h和runtime.h里面分别可以找到objc_object和objc_class的实现。

objc_object

// 在objc.h中找到相关代码
typedef struct objc_class *Class;
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

动态类型id其实就是一个objc_object。

可以看出一个类的实例,即一个对象,其实在runtime时刻是一个objc_class结构体,而结构体里面只有一个指向objc_class的指针isa。哎吆,objc_class好像在哪里见过?对,就是上面提到的调用类方法使用的结构体,下面看一下它的结构。

objc_class

// 在runtime.h中找到相关代码
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

objc_class的结构体的东西就比较多了,因为现在讨论的是调用类的方法,所以我们只关注三个东西isa、super_class和objc_method_list。
isa指向一个objc_class对象,而super_class比较容易理解,即指向这个类的父类。而objc_method_list会在下一节和SEL一起讲解。

本节并没有讲解一些东西,只是说了下两个结构体的实现,让大家对isa、super_class有一个概念。本节过后,大家的疑问应该变多了,结合一中疑问,大概以下几个:
1、objc_method_list、SEL。
2、isa、super_class有什么用?怎么用?
3、objc_msgSend到底如何工作?

三、objc_method、SEL和IMP

二中第一个疑问是objc_method_list、SEL,而引入objc_method_list是objc_method的列表,所以我们不研究objc_method_list,改为研究objc_method和SEL,而objc_method结构体中还有一个IMP,与本节内容息息相关,所以以objc_method、SEL和IMP为中心讲解。

objc_method

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

可知,OC中每一个方法都是如上的结构体,里面的两个关键字段method_name和method_imp共同为方法的查找和使用服务。

SEL
从SEL类型的成员为method_name可以知道,SEL大概代表一个方法的名字,可以通过以下方式检验。

// OC代码
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
+ (void)c;
@end
@implementation A
- (void)b {
}
+ (void)c {
    NSLog(@"%s", @selector(b));
}
@end
int main(int argc, char * argv[]) {
    [A c];
}

// 输出
2016-12-29 15:23:03.850 block[57622:533311] b

IMP
SEL的作用很容易理解,即想找到想要的objc_method结构体,需要通过SEL来遍历,那么,IMP是什么呢?一般来说,通过SEL找到想要的objc_method,下一步就是调用方法的实现了,objc_method结构体中唯一有可能和方法实现相关的就是IMP了。所以,IMP是一个函数指针,指向objc_method对应方法的实现部分。

objc_method中的method_types指的是方法的返回值和参数。以返回值开始,依次把参数拼在后面,比如"i@",即为返回值为int类型,参数为一个对象,参照对照表

简单样例

// 考虑到只用文字描述有点空洞,这里通过class_addMethod方法的使用做个样例
//先看下class_addMethod定义,几个参数容易理解。cls为需要增加方法的类,后三个参数对应objc_method结构体中的几个成员。
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

// OC代码
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
@end
@implementation A
- (void)b {
}
@end
int c(NSString *str) {
    NSLog(@"c");
    return 0;
}
int main(int argc, char * argv[]) {
    class_addMethod([A class], @selector(c:), (IMP)c, "i@");
    A *aObject = [[A alloc] init];
    [aObject performSelector:@selector(c:)];
}

// 输出,可见完成了方法c的调用。
2016-12-29 15:51:17.931 block[60624:567679] c

本节内容大致到这,SEL、IMP你应该搞懂了,现在只剩下两个问题:
1、isa、super_class有什么用?怎么用?
2、objc_msgSend到底如何工作?

四、objc_msgSend工作原理和实例方法调用

1、objc_msgSend工作原理

伪代码

// 首先看一下objc_msgSend的方法实现的伪代码
id objc_msgSend(id self, SEL op, ...) {
   if (!self) return nil;
   // 关键代码(a)
   IMP imp = class_getMethodImplementation(self->isa, SEL op);
   imp(self, op, ...); // 调用这个函数,伪代码...
}
// 查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if (!cls || !sel) return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) {
      ... // 执行动态绑定
    }
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) return _objc_msgForward; // 这个是用于消息转发的
    return imp;
}
// 遍历继承链,查找IMP
IMP lookUpImpOrNil(Class cls, SEL sel) {
    if (!cls->initialize()) {
        _class_initialize(cls);
    }
    Class curClass = cls;
    IMP imp = nil;
    do { // 先查缓存,缓存没有时重建,仍旧没有则向父类查询
        if (!curClass) break;
        if (!curClass->cache) fill_cache(cls, curClass);
        imp = cache_getImp(curClass, sel);
        if (imp) break;
    } while (curClass = curClass->superclass); // 关键代码(b)
    return imp;
}

分析
首先在Class中的缓存查找imp(没缓存则初始化缓存),如果没找到,则向父类的Class查找。如果一直查找到根类仍旧没有实现,则用_objc_msgForward函数指针代替imp。最后,执行这个imp。

2、实例方法调用

代码

// 实例方法调用,参照一
objc_msgSend(aObject, sel_registerName("b"));

// 参照二中实例对象的结构
typedef struct objc_class *Class;
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

// 顺便给出objc_class结构
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

分析
结合关键代码(a)、关键代码(b)和上述结构,大概可以猜到实例方法的调用顺序:
1、将一个objc_object结构体的isa指针(即其对应的objc_class结构体)和一个方法名SEL传入class_getMethodImplementation。
2、在class_getMethodImplementation方法中,使用lookUpImpOrNil遍历继承链,若返回nil,则消息转发(消息转发下一章在讲)。
3、在lookUpImpOrNil遍历继承链,即先在当前objc_class的cache中查找SEL,若没有,则在methodLists中查找,若存在,则将其放入该objc_class的cache中,然后返回IMP。若不存在,则通过super_class指针找到该class的父class,继续该步骤直到NSObject停止(NSObject的super_class指向nil)。
4、执行得到的IMP。

调用顺序的第3步也解释了objc_class 结构体中cache的存在意义。

图示

实例对象的isa和继承链

3、结论与问题

本节介绍了objc_msgSend工作原理、objc_object的isa和objc_class的super_class等的使用方式,但是有一个和本章相关的成员没有提到,即objc_class结构体的isa指针。无疑,它是和类方法的调用直接相关的。

五、类方法调用和元类(metaClass)

上面留下的最后一个问题,objc_class结构体的isa指针问题。无疑是和类方法调用直接相关的。

1、类方法调用与实例方法调用区别

类方法的调用和实例方法显然不用,后者是需要先创建一个实例,而这个实例在堆中有自己对应的objc_object结构体,是以这个结构体对应的其“私有”的objc_class结构体为基础进行消息传递的。
那么,类方法呢?类方法不需要创建实例,每个类都有一个自己对应的现成的objc_class结构体。而类方法的调用也是以这个“公有”的objc_class结构体做操作的。

公有、私有指的是每个对象实例都有自己的objc_class(私有),实例方法的调用也是通过这个私有的objc_class,而每个类只有一个对应的objc_class(公有),任何地方调用类方法,都是通过这个公有的objc_class。

2、methodLists中的方法

代码

// OC代码
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
+ (void)d;
@end
@implementation A
- (void)b {
    NSLog(@"b");
}
+ (void)d {
    NSLog(@"d");
}
@end
int main(int argc, char * argv[]) {  
    // 在A对应的objc_class结构体的继承链中找到实例方法b
    IMP bIMP = class_getMethodImplementation([A class], @selector(b));
    // 执行IMP
    bIMP();
}

// 输出
2016-12-29 18:21:39.810 block[74277:689120] b

分析
可知,可以从A的objc_class的methodLists找到方法b。但是,尝试将

IMP bIMP = class_getMethodImplementation([A class], @selector(b));

改为

IMP bIMP = class_getMethodImplementation([A class], @selector(d));

执行代码,会crash。可以得出结论,objc_class的methodLists中只存有实例方法,并没有类方法。那么类方法在哪里?

类方法的位置

// OC代码
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
+ (void)d;
@end
@implementation A
- (void)b {
    NSLog(@"b");
}
+ (void)d {
    NSLog(@"d");
}
@end
int main(int argc, char * argv[]) {
    // 获取A类对应的metaClass
    Class aMeta = objc_getMetaClass(class_getName([A class]));
    // 在metaClass中找类方法d
    IMP dIMP = class_getMethodImplementation(aMeta, @selector(d));
    dIMP();
}

// 输出
2016-12-29 18:27:19.290 block[74821:695164] d

可知,A的objc_class结构体有一个现成的metaClass,而它存有A类的类方法。这里大概可以想到A类的objc_class结构体里面的isa指针指向这个metaClass。

当然,metaClass里面只存了类方法,没有实例方法。

3、元类(metaClass)

由代码

Class aMeta = objc_getMetaClass(class_getName([A class]));

可知,元类也是一个objc_class结构体。而相应的也有isa和super_class成员。super_class比较好理解,参照四中objc_msgSend实现,用于在继承链上方法的查找。而元类的isa均指向NSObject对应的元类,这里不多解释(NSObject对应元类的isa指向自己,其super_class指向NSObject,也不解释了)。

4、类方法调用流程

// 类方法调用,参照一
objc_msgSend(objc_getClass("A"), sel_registerName("c"));

// 顺便给出objc_class结构
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

可知,objc_getClass("A"),即为获取A的class,结合四中objc_msgSend实现,将其isa指针指向的metaClass传入class_getMethodImplementation来进行查找,符合我们的预期。其后的步骤与实例方法相同。

5、类调用图示

类的isa和继承链

六、结论

结合四和五的图展示,现在,你看懂这个图了吗。(可以发现本文在四中留下了两个疑问,即动态绑定和消息转发,且等下一篇分享)


runtime经典图

七、文献

1、http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
2、http://blog.csdn.net/reylen/article/details/50440450
3、http://cocoasamurai.blogspot.jp/2010/01/understanding-objective-c-runtime.html
4、http://blog.ibireme.com/2013/11/26/objective-c-messaging/
5、https://github.com/opensource-apple/objc4

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,165评论 0 7
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 724评论 0 2
  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 743评论 0 1
  • 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的...
    lylaut阅读 791评论 0 4