OC Runtime小记 (二) 消息

OC Runtime小记 (二) 消息

OC中对象调用一个方法叫做消息传递,其中调用方法一般有两种:类方法,实例方法

从日常崩溃说起

日常写OC代码的时候总会出现以下熟悉的错误:

unrecognized selector sent to instance XXXX

例如下面代码:

id new = @2333;    
NSLog(@"%@",new);
[new appendString:@"newStr"];    
NSLog(@"%@",new);

其中new对象作为消息的接收者 appendString作为sector@“newStr”作为参数,sector参数共同做为消息,发送给new这个消息接收者。
上面代码在编译时并不会报错,只有在运行的时候会报错,原因是nsnumber类型的id 找不到appendString的方法,于是就崩溃了。

objc_msgSend

先来看个例子吧

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface teacher:NSObject
@property (nonatomic , copy) NSString * name;
@property (nonatomic , copy) NSString * age;
- (void)class;
+ (void)teach;
@end

@implementation teacher
-(void)class{
    NSLog(@"teacher's class");
}
+(void)teach{
    NSLog(@"teach");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        teacher * teacherZH = [[teacher alloc]init];
        [teacherZH class];
        [teacher teach];
    }
    return 0;
}

我们这里用clang 重写编译一下,查看一下runtime 源码

clang -rewrite-objc main.m

打开main.cpp 重写出来的代码大概有9W多行,主要看如下代码

static void _I_teacher_class(teacher * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_tt1p_4td3yq9xlbl7c2t4jn00000gn_T_main_fbd52a_mi_0);}

static void _C_teacher_teach(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_tt1p_4td3yq9xlbl7c2t4jn00000gn_T_main_fbd52a_mi_1);}

static NSString * _I_teacher_name(teacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_teacher$_name)); }

extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_teacher_setName_(teacher * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct teacher, _name), (id)name, 0, 1); }

static NSString * _I_teacher_age(teacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_teacher$_age)); }

static void _I_teacher_setAge_(teacher * self, SEL _cmd, NSString *age) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct teacher, _age), (id)age, 0, 1); }
// @end


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        teacher * teacherZH = ((teacher *(*)(id, SEL))(void *)objc_msgSend)((id)((teacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("teacher"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)teacherZH,sel_registerName("class"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("teacher"), sel_registerName("teach"));
    }
return 0;
}

我们从main 函数开始看,C语言将实例化方法改写成了静态函数

teacher * teacherZH = ((teacher *(*)(id, SEL))(void *)objc_msgSend)((id)((teacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("teacher"), sel_registerName("alloc")), sel_registerName("init"));

将上面的函数做一下拆分:其中alloc方法

(id)((teacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("teacher"), sel_registerName("alloc"))

这行代码中首先获取teacher类,然后注册了alloc方法,通过objc_msgSend给类发送消息。这一段在OC代码中为:

[teacher alloc]

方便起见我们用[teacher alloc] 来替换前面那段C代码

((teacher *(*)(id, SEL))(void *)objc_msgSend)([teacher alloc], sel_registerName("init"));

这段代码和上面一样,首先获得teacher类,然后注册init 方法 通过msgSend给类发送消息给接收者 teacherZH,这一段在OC代码中为:

[teacher init]
 ((void (*)(id, SEL))(void *)objc_msgSend)((id)teacherZH,sel_registerName("class"));    

源码为:[teacherZH class];

((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("teacher"), sel_registerName("teach"));

源码为:[teacher teach];

上面代码可以关注下类对象和实例对象发送消息的不同根据上篇中可以理解objc_getClass(teacher)为:objc_class,另一个teacherZH为objc_object

实例对象的method_list结构    
static struct /*_method_list_t*/ {
unsigned int entsize;  // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[5];
} _OBJC_$_INSTANCE_METHODS_teacher __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
5,
{{(struct objc_selector *)"class", "v16@0:8", (void *)_I_teacher_class},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_teacher_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_teacher_setName_},
	{(struct objc_selector *)"age", "@16@0:8", (void *)_I_teacher_age},
	{(struct objc_selector *)"setAge:", "v24@0:8@16", (void *)_I_teacher_setAge_}}
};

类对象method_list结构
static struct /*_method_list_t*/ {
unsigned int entsize;  // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_teacher __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"teach", "v16@0:8", (void *)_C_teacher_teach}}
};

struct _objc_method {
    struct objc_selector * _cmd;
    const char *method_type;
    void  *_imp;
};

在往下看可以看到两个_method_list_t 其中一个为_INSTANCE_METHODS_teacher实例方法的方法列表,另一个为_CLASS_METHODS_teacher类方法的方法列表,可看出实例方法共有5个(-)方法,类方法只有一个(+)方法,下面还有一个结构体_objc_method 里面存储着选择子的,方法类型,以及方法具体实现。可以看到其实具体实现就是一个函数指针,selector可以理解为字符串类串的名称,用于查找对应的函数实现。

这样objc_msgSende 工作原理简述如下:

  • 1.匹配消息的接受者和选择子
  • 2.匹配objc_method_list 中的方法列表
  • 3.若找到了就直接跳转到具体实现
  • 4.若未找会通过该类的super_class 指针往上查找,父类
  • 5.若未找到会走消息转发
  • 6.如果还未找到,最后会调用doesNotRecognizeSelector,就会报unrecognized selector的错误了

具体的有关于消息转发的相关内容会在下一篇介绍