1. Runtime
1.1 Runtime介绍
Runtime称之为运行时,它与编译时的区别:
- 编译时:源代码翻译成机器能识别的代码的过程,主要是对语言进行最基本的检查报错,即词法分析、语法分析等,是一个静态的阶段
- 运行时:代码跑起来,被装载到内存中的过程,如果此时出错,则程序会崩溃,是一个动态阶段
1.2 Runtime的两个版本
Legacy版本:早期版本
◦ 对应Objective-C 1.0编程接⼝
◦ ⽤于32位的Mac OS X平台上
Modern版本:现⾏版本
◦ 对应Objective-C 2.0编程接⼝
◦ 用于iPhone程序和Mac OS X v10.5及以后系统中的64位程序
1.3 使用Runtime的三种方式
- 通过
Objective-C代码,例如:[person sayNB] - 通过
Foundation框架的NSObject类定义的方法,例如:isKindOfClass - 通过
Runtime API,例如:class_getInstanceSize

Compiler:编译器Runtime System Libarary:Runtime的底层系统库
官方文档:Objective-C Runtime Programming Guide
2. 方法本质
2.1 方法底层的实现
在main函数中,写入以下代码:
LGPerson *person = [LGPerson alloc];[person sayNB];[person say:@"NB"];
生成cpp文件,底层代码如下:
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("say:"), (NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_d8842a_mi_3);
方法的本质:objc_msgSend消息发送
objc_msgSend的参数:
- 消息接收者
- 消息主体(
SEL+ 参数)
2.2 objc_msgSend
在Build Setting中,将Enable Strict Checking of obc_msgSend Calls设置为NO
导入头文件:
#import <objc/message.h>
在main函数中,写入以下代码:
@interface LGPerson : NSObject- (void)sayNB;@end@implementation LGPerson- (void)sayNB{NSLog(@"666");}@endint main(int argc, const char * argv[]) {@autoreleasepool {LGPerson *person = [LGPerson alloc];((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));}return 0;}-------------------------//输出结果:666
- 和调用
OC方法的执行结果相同 sel_registerName为@selector()的底层实现
2.2 objc_msgSendSuper
子类调用父类方法时,可使用objc_msgSendSuper,向父类发送消息
在objc源码中,找到objc_msgSendSuper的定义
OBJC_EXPORT voidobjc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
- 传入
objc_super类型的结构体指针
找到objc_super结构体的定义
struct objc_super {/// Specifies an instance of a class.__unsafe_unretained _Nonnull id receiver;/// Specifies the particular superclass of the instance to message.__unsafe_unretained _Nonnull Class super_class;/* super_class is the first class to search */};
receiver:消息接收者super_class:传入第一查找的类,如果找不到,继续向所属父类一层一层的查找
在项目中调用objc_msgSendSuper
定义LGTeacher,继承于LGPerson
@interface LGTeacher : LGPerson- (void)sayNB;@end@implementation LGTeacher@end
- 定义
sayNB方法,但不实现
在main函数中,写入以下代码:
struct lg_objc_super {__unsafe_unretained _Nonnull id receiver;__unsafe_unretained _Nonnull Class super_class;};int main(int argc, const char * argv[]) {@autoreleasepool {LGTeacher *teacher = [LGTeacher alloc];struct lg_objc_super lgSuper;lgSuper.receiver=teacher;lgSuper.super_class=[LGPerson class];objc_msgSendSuper(&lgSuper, @selector(sayNB));}return 0;}-------------------------//输出结果:666
- 按照
objc_super相同结构,定义lg_objc_super结构体 - 我们已经知道
LGTeacher中没有sayNB方法的实现,所以super_class直接传入LGPerson的类对象 - 如果
super_class传入LGTeacher的类对象,打印结果相同。区别在于需要向父类多查找一层
3. objc_msgSend汇编代码
在objc4-818.2源码中,不同系统架构的汇编指令都有差异,我们只针对最常用的arm64架构下的汇编代码进行探索
3.1 objc_msgSend
ENTRY _objc_msgSendUNWIND _objc_msgSend, NoFramecmp p0, #0 // nil check and tagged pointer check#if SUPPORT_TAGGED_POINTERSb.le LNilOrTagged // (MSB tagged pointer looks negative)#elseb.eq LReturnZero#endifldr p13, [x0] // p13 = isaGetClassFromIsa_p16 p13, 1, x0 // p16 = class
ENTRY _objc_msgSend:入口p0寄存器,存储消息接收者cmp p0, #0:消息接收者和#0比较
◦ 汇编代码中,没有nil,只有0和1
◦ 这里的#0比较,可以理解消息接收者为nil
SUPPORT_TAGGED_POINTERS:真机arm64架构,SUPPORT_TAGGED_POINTERS定义为1b.le LNilOrTagged:小于等于0,进入LNilOrTagged流程b.eq LReturnZero:等于0,进入LReturnZero流程- 否则,消息接收者存在,继续执行代码
◦ ldr p13, [x0]:将x0寄存器取地址,赋值p13寄存器
◦ p13:存储消息接收者的isa
- 进入
GetClassFromIsa_p16流程,传入isa、1、消息接收者
3.2 LNilOrTagged
LNilOrTagged:b.eq LReturnZero // nil checkGetTaggedClassb LGetIsaDone
b.eq:等于0,进入LReturnZero流程- 否则,小于
0,继续执行代码 - 进入
GetTaggedClass流程 - 进入
LGetIsaDone流程
3.3 LReturnZero
LReturnZero:// x0 is already zeromov x1, #0movi d0, #0movi d1, #0movi d2, #0movi d3, #0ret
- 返回
nil
3.4 GetClassFromIsa_p16
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */#if SUPPORT_INDEXED_ISA// Indexed isamov p16, \src // optimistically set dst = srctbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa// isa in p16 is indexedadrp x10, _objc_indexed_classes@PAGEadd x10, x10, _objc_indexed_classes@PAGEOFFubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract indexldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array1:#elif __LP64__.if \needs_auth == 0 // _cache_getImp takes an authed class alreadymov p16, \src.else// 64-bit packed isaExtractISA p16, \src, \auth_address.endif#else// 32-bit raw isamov p16, \src#endif.endmacro
- 入参:
◦ src:isa
◦ needs_auth:1
◦ auth_address:消息接收者
- 当前不符合
SUPPORT_INDEXED_ISA条件,跳过 - 符合
__LP64__条件,不满足needs_auth等于0的条件,进入else分支 ExtractISA p16, \src, \auth_address:进入ExtractISA流程
◦ 传入p16寄存器,isa,消息接收者
◦ p16寄存器的值,存储的是什么不重要,因为传入到ExtractISA中会被赋值
3.5 ExtractISA
.macro ExtractISAand $0, $1, #ISA_MASK.endmacro
$0:p16寄存器$1:isaand $0, $1, #ISA_MASK:将isa & ISA_MASK的结果,存储到p16寄存器
◦ p16:存储类对象地址
3.6 LGetIsaDone
LGetIsaDone:// calls imp or objc_msgSend_uncachedCacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
objc_msgSend的后续流程
◦ 消息接收者存在,并且得到类对象,继续LGetIsaDone流程
- 进入
CacheLookup缓存查找流程,也就是所谓的sel-imp快速查找流程 - 内部会调用
imp或objc_msgSend_uncached
3. 流程图

为什么要获取类对象?
- 在快速查找流程中,查找的
cache存储在类对象中,所以必须拿到类对象才能进行后面的流程
总结
Runtime介绍:
Runtime称之为运行时- 与编译时的区别:
◦ 编译时:源代码翻译成机器能识别的代码的过程,主要是对语言进行最基本的检查报错,即词法分析、语法分析等,是一个静态的阶段
◦ 运行时:代码跑起来,被装载到内存中的过程,如果此时出错,则程序会崩溃,是一个动态阶段
Runtime的两个版本:
Legacy版本:早期版本
◦ 对应Objective-C 1.0编程接⼝
◦ ⽤于32位的Mac OS X平台上
Modern版本:现⾏版本
◦ 对应Objective-C 2.0编程接⼝
◦ 用于iPhone程序和Mac OS X v10.5及以后系统中的64位程序
使用Runtime的三种方式:
- 通过
Objective-C代码,例如:[person sayNB] - 通过
Foundation框架的NSObject类定义的方法,例如:isKindOfClass - 通过
Runtime API,例如:class_getInstanceSize
方法本质:
- 方法的本质:
objc_msgSend消息发送 objc_msgSend的参数:
◦ 消息接收者
◦ 消息主体(SEL + 参数)
sel_registerName为@selector()的底层实现objc_msgSendSuper:向父类发送消息
objc_msgSend汇编代码:
ENTRY _objc_msgSend:汇编代码的入口- 消息接收者
receiver不存在,返回nil - 否则,
receiver存在:
◦ 获取isa
◦ isa & ISA_MASK,得到类对象
◦ 成功得到类对象,进入CacheLookup缓存查找流程,也就是所谓的sel-imp快速查找流程
