C 函数如何执行
事实上,如果不理解 C 函数调用的时候内存发生了什么,就没办法理解 Objective-C 的消息转发。
传统的 C 函数,编译后,直接生成了对应的虚拟地址,写入到了二进制可执行文件;执行的时候,该地址直接指向栈中函数的入口,中间没有任何其他步骤。
选读内容: 现代计算机,为了追求更高效的运行效率,在硬件上引入了寄存器,寄存器和内存(RAM)都是易失性存储器。但是寄存器属于 SRAM(Static Random Access Memory),而内存属于 DRAM(Dynamic Random Access Memory),区别在于 DRAM 属于破坏性读出,即一次数据读取后,数据就会被彻底破坏,需要重新刷新该内存区域,一般刷新周期为 2ms。而 SRAM 则是非破坏性读出,所以不需要刷新,这让 SRAM 的读写速度远高于 DRAM,但是 SRAM 的成本会更高,内部电路会更复杂。所以计算机内部只有少量的高速 Cache 和 寄存器选用 SRAM 结构。内存则采用 DRAM 结构。
Objective-C 的消息机制
回到正题,相比 C 语言,
Objective-C
语言则做了新的设计,称为消息机制,即给某个对象发个消息,发消息
这个用词很微妙,因为发了消息,仅仅代表目标对象收到了某个消息,并不代币它会直接调用最终的目标函数;所以在 Objective-C 中叫 Method(方法),而不直接叫函数 (Function)。设计上,所有 Objective-C 的消息,都交给一个全局函数objc_msgSend
去处理。该函数由 C 和部分汇编共同实现,主要的目的就是让消息经过多次流转,从而让使用者拥有更多,更灵活的方式处理该消息,这也就实现了语言的部分动态化。具体,可以看下图:通过上图可以看到,对任意一个对象发任意消息,它在栈中第一个进入的都是
objc_msgSend
函数,该函数内部有关消息转发的逻辑主要有 4步,如上图所示。编译器如何编译一个方法
首先来看编译器的作用,编译器干了什么,才真正体现了该语言的设计。编译器首先会做一次
--rewrite-objc
,用 C++ 重新翻译一遍 OC 语言,然后在这个过程中会偷偷的加了一个中间层–objc_msgSend
。从函数调用栈上来看,objc_msgSend
实现的功能,相当于用硬编码实现了一次间接寻址。对于一个看似普通的 OC method,会翻译出来一堆东西:1、selector 选择器,底层是 objc_selector 结构体,它的类型实际上是一个
char *
字符串,内容是方法名+参数,然后用特定的书写格式记录。有了 selector 作为 Key,将来就可以根据这个 Key 在对象的内存结构中去寻找真正与之对应的 IMP。2、IMP 函数指针,它是真正最终被调用的函数指针(如果有具体的实现),编译器参考原 OC 方法,编译出一个 C++ 函数。IMP 就是该函数的地址,这里假设,OC 的方法是
doSomethingWithParam:
,那么翻译后的样子是下边这样的:// 实现 doSomethingWithParam: 方法 void MyClass::doSomethingWithParam(NSString *param) { NSLog((NSString *)&__NSConstantStringImpl__var_folders...__, param); }
3、Method,实为一个 object_method 结构体
struct objc_method { SEL method_name; char *method_types; IMP method_imp; };
method 可以简单的理解为一对
[key value]
,Key
为selector
,Value
就是IMP
。此外还有一个方法类型签名method_types
,这个在将来能够用来解析该Method
的参数和返回值具体是什么类型,它的值是一种约定好的特殊编码,例如"v@:ii"
,表示void (int, int)
,一个接收两个int
型参数,没有返回值的方法。事实上每种编程语言为了方便编译器高效执行,都有自己的类型编码表,所以,你无需惧怕v@ii
这些奇形怪状的编码,它们只是为了让编译器识别和解析。当然语言本身提供了通过类型解析出具体编码的 OC 方法(objcType
)和语法糖(@encode
),例如:@encode(long) // 输出: 'l' NSNumber *foo = [NSNumber numberWithBool: YES]; [foo objCType] // 输出: 'c'
这些编码可以在苹果官方文档中查询。
4、编译出一条
objc_msgSend
函数调用语句,作为执行的入口:objc_msgSend(myObject, @selector(doSomethingWithParam:), param);
所以,可以看到在编译器和
msg_msgSend()
的共同作用下,实现了 Objective-C 的消息机制,当然整个过程中调用了很多runtime
的 C函数。文末我会放一个精简的代码示例,展示 OC (编译为)–> C++ 的前后对比。这里先集中注意力到消息机制的原理上。objc_msgSend 中对消息机制的 4 步处理
1、 第一步是在类对象本身的继承体系中寻找与
selector
的实现,如果成功找到,就直接调用目标函数,消息机制处理完毕!这里涉及到 OC 对象模型,Objective-C 对象模型 中会详细讨论。2、 如果第一步中没有找到,
objc_msgSend
函数则会尝试调用resolveInstanceMethod
方法,进行一次方法的动态解析,该方法的一般实现如下:#import <objc/runtime.h> void dynamicMethodIMP(id self, SEL _cmd) { NSLog(@"Dynamic method implementation"); } @implementation MyClass // 1、或者给类对象实例添加方法 + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(testMethod)) { class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:sel]; } // 2、或者给类添加方法 + (BOOL)resolveClassMethod:(SEL)sel { if (sel == @selector(printMessage)) { class_addMethod(object_getClass(self), sel, (IMP)dynamicMethodIMP, "v@:"); return YES; } return [super resolveClassMethod:sel]; } @end
方法返回
BOOL
,表示是否成功实现了动态解析;该方法的目的是让用户动态的给selector
添加一个函数实现,从而调用它。这里的class_addMethod
是runtime
库提供的C
函数。另外这里有两个解析方法,resolveClassMethod
和resolveInstanceMethod
,分别解析实例方法和类方法;由于实例方法在类对象中,类方法在元类对象中,所以这两个方法本身都是+
方法。这样[self class]
就可以表示类对象,object_getClass(self)
表示元类对象。如果该方法返回
true
,则直接执行函数,消息机制结束!3、 如果 2 失败,则会发生消息转发
forwardingTargetForSelector
,这里才是真实意义上的消息转发,实现将消息转发给其他对象,具体使用:@implementation MyClass - (void)foo { NSLog(@"MyClass foo"); } @end @interface MyForwardingClass : NSObject @end @implementation MyForwardingClass - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(foo)) { return [[MyClass alloc] init]; } return [super forwardingTargetForSelector:aSelector]; } @end
forwardingTargetForSelector
需要返回一个接收消息的对象,然后消息会被再次转发到目标对象上,在目标对象上会重新从第一步开始执行消息处理流程。4、 这里故事到了结尾,很遗憾,前边的各种机会都没成功,这里将进行
NSInvocation
打包,运行时把所有有关消息的数据都收集一遍,打包进NSInvocation
对象。能走到这里,说明目前还没找到能处理该selector
的目标对象,所以运行时无法自己拿到该selector
对应的方法签名(前文提到如果有目标对象,则目标对象会编译出一个 objc_method
结构体,该结构体内部有selector
, imp
, method_type
,其中method_type
就是函数的签名或者叫编码
,问题是到目前还没找到这个目标对象,所有imp
和method_type
都没有)。因此我们必须先实现一个methodSignatureForSelector
方法,返回方法签名,然后在处理NSInvocation
:@implementation MyForwardingClass - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if (aSelector == @selector(foo:bar:)) { return [NSMethodSignature signatureWithObjCTypes:"v@:ii"]; // 返回方法签名 } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { MyClass *obj = [[MyClass alloc] init]; if ([obj respondsToSelector:[anInvocation selector]]) { // 这里我们可以对 anInvocation 做更灵活的调用,比如一次派发给多个对象,来响应该消息。 [anInvocation invokeWithTarget:obj]; } else { [super forwardInvocation:anInvocation]; } } @end
完整版本编译器翻译 OC –> C++
假设原 OC 代码:
// 定义一个类 @interface MyClass : NSObject - (void)doSomethingWithParam:(NSString *)param; @end @implementation MyClass - (void)doSomethingWithParam:(NSString *)param { NSLog(@"Received parameter: %@", param); } @end // 在 main 函数中调用 MyClass 的方法 int main(int argc, const char * argv[]) { @autoreleasepool { MyClass *myObject = [[MyClass alloc] init]; NSString *param = @"Hello World"; [myObject doSomethingWithParam:param]; } return 0; }
编译为 C++ 后:
// 定义一个类 struct objc_class { struct objc_class *isa; char *class_name; struct objc_method_list *method_list; }; struct objc_method { SEL method_name; char *method_types; IMP method_imp; }; struct objc_method_list { struct objc_method_list *next; int count; struct objc_method method_list[1]; }; class NSObject { public: virtual void release(); virtual void retain(); virtual id autorelease(); virtual id init(); static id alloc(); void dealloc(); }; class MyClass : public NSObject { public: void doSomethingWithParam(NSString *param); static void __doSomethingWithParam(MyClass *self, SEL _cmd, NSString *param); static Class class$MyClass; }; Class MyClass::class$MyClass = (Class)&OBJC_CLASS_$_MyClass; // 实现 doSomethingWithParam: 方法 void MyClass::doSomethingWithParam(NSString *param) { NSLog((NSString *)&__NSConstantStringImpl__var_folders...__, param); } void MyClass::__doSomethingWithParam(MyClass *self, SEL _cmd, NSString *param) { self->doSomethingWithParam(param); } // MyClass 的方法列表 static struct objc_method_list _OBJC_INSTANCE_METHODS_MyClass __attribute__ ((used, section ("__OBJC, __inst_meth")))= { 1, {{(SEL)"doSomethingWithParam:", "@24@0:8@16", (IMP)&MyClass::__doSomethingWithParam}} }; // 在 main 函数中调用 MyClass 的方法 int main(int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; MyClass *myObject = ((MyClass *(*)(id, SEL))(void *)objc_msgSend)((id)((MyClass *(*)(id, SEL))(void *)objc_msgSend)(MyClass::class$MyClass, sel_registerName("alloc")), sel_registerName("init")); NSString *param = ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("initWithUTF8String:"), "Hello World"); ((void (*)(id, SEL, id))(void *)objc_msgSend)((id)myObject, sel_registerName("doSomethingWithParam:"), (id)param); ((void (*)(id))(void *)objc_msgSend)((id)pool, sel_registerName("drain")); return 0; }
关键的代码在最终的
main
函数中: ...objc_msgSend)((id)myObject, sel_registerName("doSomethingWithParam:"), (id)param);