runtime小序曲一文中举出了runtime的三种应用方式:
- Objective-C源代码,以objc_msgSend方法举例。
- NSObject的方法。
- Runtime的函数。
本文将就第一种方式中的objc_msgSend方法展开讲解。
学习进度:
- runtime小序曲,从运行时多态看这股神秘力量
- runtime进行曲,objc_msgSend的前世今生(一)
- runtime进行曲,objc_msgSend的前世今生(二)
- runtime变奏曲,那些藏在runtime中的接口(一)
- runtime变奏曲,那些藏在runtime中的接口(二)
一、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的存在意义。
图示
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、类调用图示
六、结论
结合四和五的图展示,现在,你看懂这个图了吗。(可以发现本文在四中留下了两个疑问,即动态绑定和消息转发,且等下一篇分享)
七、文献
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