7.iOS底层学习Runtime 运行时&方法的本质
编译时与运行时
即编译器对语言的编译阶段,编译时只是对语言进行最基本的检查报错,包括词法分析、语法分析等等,将程序代码翻译成计算机能够识别的语言(例如汇编等),编译通过并不意味着程序就可以成功运行。
即程序通过了编译这一关之后编译好的代码被装载到内存中跑起来的阶段,这个时候会具体对类型进行检查,而不仅仅是对代码的简单扫描分析,此时若出错程序会崩溃。
可以说编译时是一个静态的阶段,类型错误很明显可以直接检查出来,可读性也好;而运行时则是动态的阶段,开始具体与运行环境结合起来。
Runtime简介
官方文档的描述和解释是:
Objective-C Runtime
Describes the macOS Objective-C runtime library support functions and data structures.
The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps. Objective-C runtime library support functions are implemented in the shared library found at/usr/lib/libobjc<wbr data-v-78dd96cf="">.A<wbr data-v-78dd96cf="">.dylib
.
简单翻译过来就是提供oc动态特性的一个动态库,这个动态库里包括好多方法和数据结构。
Runtime的版本
有两个版本 ⼀个Legacy版本(早期版本) ,⼀个Modern版本(现⾏版本)
- 早期版本对应的编程接⼝:Objective-C 1.0 ;
- 现⾏版本对应的编程接⼝:Objective-C 2.0 ;
- 早期版本⽤于Objective-C 1.0, 32位的Mac OS X的平台上;
- 现⾏版本:iPhone程序和Mac OS X v10.5 及以后的系统中的 64 位程序;
Objective-C Runtime Programming Guide
Objective-C Runtime Documentation
方法的本质
探究方法的本质我们需要通过clang编译来看一下我们平时写的代码方法调用的时候底层方法是什么。
main.m文件中代码为:
#import <Foundation/Foundation.h>
#import "YYStudent.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
YYStudent * stu1 = [YYStudent new];
[stu1 studentEat];
}
return 0;
}
通过命令:
xcrun -sdk iphonesimulator clang -rewrite-objc main.m
生成main.cpp文件。发现main.cpp中main函数代码为:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8q_b20nplq50_lgns412vjz8cc40000gn_T_main_8687a6_mi_0);
YYStudent * stu1 = ((YYStudent *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YYStudent"), sel_registerName("new"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)stu1, sel_registerName("studentEat"));
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
由此可以了解到我们调用方法 ’studentEat‘在cpp下变成了
’ ((void (*)(id, SEL))(void *)objc_msgSend)((id)stu1, sel_registerName("studentEat")); ‘
所以这个方法调用实际上在C++层调用了方法objc_msgSend,然后我们通过源码来找到objc_msgSend查看下她的具体定义。
objc_msgSend
全局搜索可以看到:
image.png
有好多文件里面有这个方法,根据文件名可以了解到不同架构下的objc_msgSend的实现可能会不一样。我们先找到一个调用objc_msgSend的地方然后点进去来到了他的声明:
image.png
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
这方法有两个重要的参数,一个是 id self:消息的接受者;SEL _Nonnull op:消息主体。
所以我们可以了解到:
objc_msgSend和objc_msgSendSuper
代码示例:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface YYPerson : NSObject
- (void)eat;
- (void)run;
@end
#import "YYPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface YYStudent : YYPerson
- (void)studentRun;
- (void)studentEat;
@end
我们以往的经验可以了解到子类可以直接调用父类暴露出来的方法,我们通过继承让子类调用父类的方法,来看下底层是怎样实现的,编译后生成文件内容如下:
image.png
发现并没有什么变化,但是我们在搜索objc_msgSend时发现下方有一个objc_msgSendSuper方法,定义如下:
/**
* Sends a message with a simple return value to the superclass of an instance of a class.
*
* @param super A pointer to an \c objc_super data structure. Pass values identifying the
* context the message was sent to, including the instance of the class that is to receive the
* message and the superclass at which to start searching for the method implementation.
* @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method identified by \e op.
*
* @see objc_msgSend
*/
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
其中objc_super OBJC2中定义如下:
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
根据官方给的objc_msgSendSuper的注释方法描述:向类实例的超类发送带有简单返回值的消息。
第一个参数的作用是:message and the superclass at which to start searching for the method implementation.(从哪个父类开始查找方法)。
第二个参数:包含方法参数的变量参数列表。
返回:由参数op匹配上的方法的返回。
所以我理解这个方法和objc_msgSend的差别是可以通过控制第一个参数来指定从哪个父类开始查找方法,然后继续上层查找,直到找到方法并返回。可以在某种情况下简化方法在父类中的查找流程。
下一篇继续介绍objc_msgSend的详细流程……