说到 iOS runtime
机制,首先就需要了解 Objective-C
语言的起源。
Objective-C
是在 C
语言的基础上添加了面向对象特性,但是 C
语言是一门静态语言,而 Objective-C
是一门动态语言。
静态语言会在编译时就会决定运行时所应调用的函数方法,而动态语言只有到了运行时才会确定要执行的函数方法。而 runtime
机制就是 Objective-C
的动态语言特性的体现。
通常在调用方法函数时,我们会采用如下实现:
SomeObject *object = [[SomeObject alloc] init];
[object doSomething];
我们也可以这样写:
SomeObject *object = [[SomeObject alloc] init];
[object performSelector:@selector(doSomething)];
然而实现第一种调用方法的前提是,需要在接口文件 SomeObject.h
声明 doSomething
这个函数方法:
/**
测试函数
*/
- (void)doSomething;
如果这个方法没有在接口文件被声明的话,第一种调用方式在编译的时候就会报错。相对的,第二种调用方式在编译时,虽然 Xcode
会多了个警告提示,提示说这个方法未被声明,但是编译还是会成功。
讲到这里,有人可能会认为第一种调用方式只对已经声明的方法进行调用,是不是说明了 Objective-C
也是一门静态语言呢?
那么我们现在就来做一个实验,把实现文件 SomeObject.m
的 doSomething
的定义方法给注释掉:
///**
// 测试函数
// */
//- (void)doSomething {
//
// NSLog(@"Runtime 测试");
//}
然后编译,这个时候会看到 Xcode
多了个警告提示,说没有找到 doSomething
的方法定义,但是编译还是成功了。可是,当程序在运行过程中,执行到调用 doSomething
的代码时,程序闪退了。
这个实验说明,编译器在编译时并没有去确定 doSomething
的方法定义是否有被实现,只有到了运行的时候才会寻找这个实现方法。所以这种调用方式说明了 Objective-C
是一门动态语言。
那么如果使用可以不用在接口文件声明方法的第二种调用方式,在运行时被执行到又会怎么样呢?
其实和分析第一种调用方式要考虑的东西是一样的,实现文件 SomeObject.m
是否定义了 doSomething
方法。
如果没有找到定义方法,程序会在执行到调用代码时闪退。相对的,如果找到定义方法,程序会执行 doSomething
的方法内容。
其实无论是要声明的第一种调用方式,还是可以不用声明的第二种调用方式,编译器在编译时,都会转换成一条标准的 C
语言函数调用。
objc_msgSend(object, @selector(doSomething));
objc_msgSend
函数会依据接收者 object
与 选择子 @selector(doSomething)
的类型来调用适当的方法。该方法会在接收者所属的类中搜寻其“方法列表”,如果能找到与选择子名称相符的方法,就跳至其实现代码。找不到的话,程序就会闪退。
那么如果我们想要在不闪退的情况下,要怎么确定某个方法的定义是被实现呢?
答案是
- (BOOL)respondsToSelector:(SEL)aSelector;
例子:
if ([object respondsToSelector:@selector(doSomething)]) {
[object performSelector:@selector(doSomething)];
}
了解 iOS runtime
机制让我们能够了解 Objective-C
底层的工作原理。