iOS之OC深入理解iOS-Android-私房菜iOS从入门到放弃

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

2016-12-29  本文已影响2241人  天口三水羊

runtime小序曲一文中举出了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

上一篇下一篇

猜你喜欢

热点阅读