2019-10-17html
在前面6篇文章中,经过分析 runtime 源代码介绍了 Objective-C 的类和对象的实现原理。本文则主要探讨如下问题:面向对象的 Objective-C 语言代码,是如何解析成 C 语言代码的。探讨该问题的过程,能够获得更多 runtime 关于面向对象的实现细节。前端
本文使用的编译环境是 Mac OS X 10.14 + Apple LLVM 10.0.1 (clang-1001.0.46.4)。新版本 Mac OS X 10.15 使用 LLVM 11.x.x,runtime 自己应该也存在更新,所以实际操做时可能会和文中的描述有少量出入,例如:LLVM 11.x.x rewrite Objective-C 时,原__DATA
数据段将根据是否为常量,分别保存于__DATA
、__DATA_CONST
数据段中。这里的__DATA
数据段(data segment)和后文的__objc_classlist
等数据段(data section)的概念是不一样的,data segment 包含 data section。因为没想到合适的译名,因此都笼统地称为数据段,一般名称全大写的数据段是 data segment,全小写则为 data section。linux
注意:前面6篇介绍 runtime 实现类和对象时,并无单独介绍协议,是由于协议的使用、实现、保存、加载过程,与类十分类似。若想了解其实现细节能够看源代码中:
protocol_t
、protocol_list_t
数据结构的定义,objc_getProtocol(...)
函数实现,class_conformsToProtocol(...)
函数实现,以及_read_images(...)
函数中协议信息载入的相关代码。objective-c
Objective-C 工程一般使用xcodebuild
命令行工具编译,Clang 做为前端编译器主要负责预处理、编译生成语言无关的中间代码,LLVM 做为后端编译器主要负责汇编、连接生成平台相关的二进制文件。其中,在 Clang 预处理阶段将工程中的 Objective-C 语言代码转化为 C 语言代码。能够经过clang -rewrite-objc
命令执行该过程,转化的主要内容以下:后端
设计 Objective-C 编写的 demo 程序,保存为main.m
源文件。代码中基本涵盖了 Objective-C 面向对象的基本元素(除了协议外),包括类、成员变量、方法、属性、分类定义,对象构建,对象的成员变量、属性访问,方法的调用。main.m
的代码以下:数组
#import <Foundation/Foundation.h>
#pragma mark - TestClass类定义
@interface TestClass : NSObject {
NSString* stringIvar;
}
@property(strong, nonatomic, getter=stringPropertyGetter, setter=stringPropertySetter:) NSString* stringProperty;
-(NSString*)printContent:(NSString*)content;
@end
@implementation TestClass
+(void)load{
NSLog(@"");
}
-(NSString*) printContent:(NSString*)content {
NSLog(@"Print content: %@", content);
}
@end
#pragma mark - TestClass类的TestCategory分类定义
@interface TestClass (TestCategory)
@property(strong) NSString* stringCategoryProperty;
-(NSString*)printCategoryContent:(NSString*)content;
@end
@implementation TestClass (TestCategory)
-(NSString*) printCategoryContent:(NSString*)content {
NSLog(@"Print category content: %@", content);
}
@end
#pragma mark - 主入口
int main(int argc, char * argv[]) {
@autoreleasepool {
// 构建TestClass类的实例
TestClass* testObj = [[TestClass alloc] init];
// 访问TestClass类的对象的属性值
NSString* strProp = testObj.stringProperty;
// 调用TestClass类的方法
[testObj printContent:@"Something"];
}
}
复制代码
打开 Terminal 命令行工具,cd
到main.m
所在目录,执行命令clang -rewrite-objc main.m
将 Objective-C 语言代码,转化为 C 语言代码,在当前目录生成main.cpp
的 C++ 语言源文件。main.cpp
的源代码就是回答开篇所提出的问题的突破口。main.cpp
总共有十几万行,仅须要关注其中几百行关键代码。xcode
因为objc_class
结构体类型是私有类型,所以须要定义同构的_objc_class
结构体以暴露类的数据结构。针对保存类的数据,只须要定义class_ro_t
的同构结构体,不须要class_rw_t
的同构结构体。由于定义这些同构结构体,是为了描述类的编译时决议数据,而class_rw_t
中的数据是运行时决议的,所以编译时不须要保存类的class_rw_t
数据。全部同构结构体类型的定义代码以下。bash
// 属性
struct _prop_t {
const char *name;
const char *attributes;
};
// 协议结构的定义
struct _protocol_t;
// 方法
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
// 协议结构的实现
struct _protocol_t {
void * isa; // NULL
const char *protocol_name;
const struct _protocol_list_t * protocol_list; // super protocols
const struct method_list_t *instance_methods;
const struct method_list_t *class_methods;
const struct method_list_t *optionalInstanceMethods;
const struct method_list_t *optionalClassMethods;
const struct _prop_list_t * properties;
const unsigned int size; // sizeof(struct _protocol_t)
const unsigned int flags; // = 0
const char ** extendedMethodTypes;
};
// 成员变量
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name;
const char *type;
unsigned int alignment;
unsigned int size;
};
// 类的class_ro_t数据。
// 注意:不须要定义class_rw_t的同构结构体
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout;
const char *name;
const struct _method_list_t *baseMethods;
const struct _objc_protocol_list *baseProtocols;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};
// 类
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
// 分类
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
复制代码
注意:最新版本的 runtime 包含的 preoptimize 选项貌似支持编译时生成
class_rw_t
,这部分代码比较晦涩并且不是主流处理,因此没细看,有兴趣能够去翻看源代码。数据结构
定义_class_t
结构体类型的OBJC_CLASS_$_TestClass
变量表示TestClass
类,其中bits
保存_OBJC_CLASS_RO_$_TestClass
变量的地址(在 3.2 中详细介绍),_OBJC_CLASS_RO_$_TestClass
是TestClass
类的class_ro_t
数据。函数
定义_class_t
结构体类型的OBJC_METACLASS_$_TestClass
变量表示TestClass
类的元类,其中bits
保存_OBJC_METACLASS_RO_$_TestClass
变量的地址(在 3.2 中详细介绍),_OBJC_METACLASS_RO_$_TestClass
是TestClass
的元类的class_ro_t
数据。
static void OBJC_CLASS_SETUP_$_TestClass(void )
静态函数用于初始化TestClass
类及元类的数据。
注意:代码中
__attribute__
用于指定编译特性:包括:Function Attributes、Variable Attributes、Type Attributes。在这里明显是做为修饰变量的 variable attributes。unused
表示变量未必会被使用,section
用于指定变量所保存到的数据段,参数为数据段名。(关于数据段参考:linux目标文件)
// 定义空方法缓冲
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
...
// 导入OBJC_METACLASS_ $_NSObject元类变量
extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject;
// 定义OBJC_METACLASS_ $_TestClass变量表示TestClass类的元类
extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_TestClass __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_NSObject,
0, // &OBJC_METACLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_METACLASS_RO_$_TestClass,
};
// 导入OBJC_CLASS_$_NSObject类变量
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;
// 定义OBJC_CLASS_$_TestClass变量表示TestClass类
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_TestClass __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_TestClass,
0, // &OBJC_CLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_TestClass,
};
// 初始化OBJC_CLASS_$_TestClass变量,即初始化TestClass类的操做
static void OBJC_CLASS_SETUP_$_TestClass(void ) {
// 初始化TestClass类的元类
OBJC_METACLASS_$_TestClass.isa = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_TestClass.superclass = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_TestClass.cache = &_objc_empty_cache;
// 初始化TestClass类
OBJC_CLASS_$_TestClass.isa = &OBJC_METACLASS_$_TestClass;
OBJC_CLASS_$_TestClass.superclass = &OBJC_CLASS_$_NSObject;
OBJC_CLASS_$_TestClass.cache = &_objc_empty_cache;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CLASS_SETUP[] = {
(void *)&OBJC_CLASS_SETUP_$_TestClass,
};
复制代码
用于保存TestClass
类和元类的class_ro_t
数据以下。重点是TestClass
类的class_ro_t
,元类包含的元数据相对较少,主要是类方法:
flags
:标志设置为0
,重点是RO_META
位为0
,表示非元类;instanceStart
:实例偏移量置为第一个成员变量stringIvar
的偏移量。其中,__OFFSETOFIVAR__(struct TestClass, stringIvar)
,用于获取TestClass
结构体中stringIvar
成员的偏移量;instanceSize
:实例大小为TestClass_IMPL
所占用的空间;reserved
:置0
;ivarLayout
:置0
,在 class realizing 阶段再生成;name
:类名为"TestClass"
;baseMethods
:保存_OBJC_$_INSTANCE_METHODS_TestClass
数组的地址,_OBJC_$_INSTANCE_METHODS_TestClass
数组保存类的基本方法列表;baseProtocols
:置0
,TestClass
未继承任何协议;ivars
:保存_OBJC_$_INSTANCE_VARIABLES_TestClass
数组的地址,_OBJC_$_INSTANCE_VARIABLES_TestClass
数组保存类的成员变量列表;weakIvarLayout
:置0
,在 class realizing 阶段再生成;properties
:保存_OBJC_$_PROP_LIST_TestClass
数组的地址,_OBJC_$_PROP_LIST_TestClass
数组保存类的基本属性列表;TestClass
元类的class_ro_t
的方法列表保存了TestClass
中实现的load
类方法。同时也证明了类方法保存在元类的基本方法列表中,实例方法保存在类的基本方法列表中。
static struct _class_ro_t _OBJC_METACLASS_RO_$_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1, sizeof(struct _class_t), sizeof(struct _class_t),
(unsigned int)0,
0,
"TestClass",
(const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_TestClass,
0,
0,
0,
0,
};
// 用于计算TYPE结构体中MEMBER成员的偏移量
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
static struct _class_ro_t _OBJC_CLASS_RO_$_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
0, __OFFSETOFIVAR__(struct TestClass, stringIvar), sizeof(struct TestClass_IMPL),
(unsigned int)0,
0,
"TestClass",
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_TestClass,
0,
(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_TestClass,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_TestClass,
};
复制代码
TestClass
类的对象的数据类型被定义为TestClass_IMPL
结构体。也就是说构建TestClass
对象分配的内存区块的结构是按TestClass_IMPL
的内存结构来布局的。
TestClass_IMPL
的第一个成员,保存的是父类的 ivar layout 须要保存的数据,因为TestClass
继承NSObject
因此才是NSObject_IMPL
,不然是其余****_IMPL
。TestClass_IMPL
的其余成员,保存的类定义的成员的数据;...
struct NSObject_IMPL {
Class isa;
};
...
typedef struct objc_object NSString;
...
struct TestClass_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *stringIvar;
NSString *_stringProperty;
};
复制代码
main.m
的main
函数中构建对象代码TestClass* testObj = [[TestClass alloc] init];
转化为 C 语言代码以下。看起来代码很长,原理其实很简单,就是向TestClass
类发送alloc
消息(类方法向类发送),而后向构建的对象发送init
消息(实例方法向对象发送)。
TestClass* testObj = ((TestClass *(*)(id, SEL))(void *)objc_msgSend)((id)((TestClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestClass"), sel_registerName("alloc")), sel_registerName("init"));
复制代码
TestClass
成员变量列表保存在_OBJC_$_INSTANCE_VARIABLES_TestClass
变量中。此处定义了ivar_list_t
的同构结构体用于保存成员变量列表,列表长度固定为2。成员变量列表其中第一个元素保存stringIvar
成员变量对应的ivar_t
结构体,第二个元素保存_stringProperty
成员变量。以stringIvar
为例:
offset
:成员变量偏移量,类型为unsigned long int *
,初始值设置为OBJC_IVAR_$_TestClass$stringIvar
变量的地址,该地址保存的初始值是TestClass
结构体中stringIvar
成员的偏移量;name
:成员变量名"stringIvar"
;type
:标记成员变量类型"@\"NSString\""
alignment
:置为3
,由于按8字节对齐;size
:置为8
,由于占用8字节;extern "C" unsigned long int OBJC_IVAR_$_TestClass$stringIvar __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct TestClass, stringIvar);
extern "C" unsigned long int OBJC_IVAR_$_TestClass$_stringProperty __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct TestClass, _stringProperty);
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
2,
{{(unsigned long int *)&OBJC_IVAR_$_TestClass$stringIvar, "stringIvar", "@\"NSString\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_TestClass$_stringProperty, "_stringProperty", "@\"NSString\"", 3, 8}}
};
复制代码
注意:之因此将成员变量偏移量设置为
extern
(外部引用全局变量),是为了支持 non-fragile instance variable,运行时类进行到 class realizing 阶段时,须要根据父类的instanceSize
动态调整类的 ivar layout,此时可能须要修改为员变量偏移量。
在 Runtime源代码解读5(属性)介绍属性时,曾提出过关于运行时,对象的成员变量值如何获取的问题。此时能够从代码中找到答案。经过对象的地址self
,以及记录对象成员变量偏移量的OBJC_IVAR_$_TestClass$stringIvar
变量高效获取。代码为(*(NSString **)((char *)self + OBJC_IVAR_$_TestClass$_stringIvar))
。
TestClass
的基本方法列表保存在_OBJC_$_INSTANCE_METHODS_TestClass
变量中。此处定义了method_list_t
的同构结构体用于保存方法列表,列表长度固定为3,注意不包含分类的方法列表的,由于分类的方法列表不属于class_ro_t
。方法列表中:
printContent:
成员变量对应的method_t
结构体;stringPropertyGetter
成员变量;stringPropertySetter:
成员变量;以printContent:
为例:
_cmd
:方法名为"printContent:"
_type
:类型编码设置为"@24@0:8@16"
;_imp
:方法的IMP
为指向_I_TestClass_printContent_
的函数指针;TestClass
的元类的基本方法列表保存在_OBJC_$_CLASS_METHODS_TestClass
变量中,仅包含TestClass
类中实现的load
类方法。
static __NSConstantStringImpl __NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_adaf3a_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Print content: %@",17};
static __NSConstantStringImpl __NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_d92eec_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"",0};
static void _C_TestClass_load(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_d92eec_mi_0);
}
static NSString * _I_TestClass_printContent_(TestClass * self, SEL _cmd, NSString *content) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_adaf3a_mi_0, content);
}
static NSString * _I_TestClass_stringPropertyGetter(TestClass * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_TestClass$_stringProperty)); }
static void _I_TestClass_stringPropertySetter_(TestClass * self, SEL _cmd, NSString *stringProperty) { (*(NSString **)((char *)self + OBJC_IVAR_$_TestClass$_stringProperty)) = stringProperty; }
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} _OBJC_$_INSTANCE_METHODS_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"printContent:", "@24@0:8@16", (void *)_I_TestClass_printContent_},
{(struct objc_selector *)"stringPropertyGetter", "@16@0:8", (void *)_I_TestClass_stringPropertyGetter},
{(struct objc_selector *)"stringPropertySetter:", "v24@0:8@16", (void *)_I_TestClass_stringPropertySetter_}}
};
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_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"load", "v16@0:8", (void *)_C_TestClass_load}}
};
复制代码
main.m
的main
函数中调用方法的代码[testObj printContent:@"Something"];
转化为 C 语言代码以下。又是很长的一串,可是原理也很简单,就是向testObj
对象发送printContent:
消息,传递参数"Something"
,该字符串常量保存在__cfstring
数据段中。
static __NSConstantStringImpl __NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_adaf3a_mi_2 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Something",9};
((NSString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)testObj, sel_registerName("printContent:"), (NSString *)&__NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_adaf3a_mi_2);
复制代码
TestClass
的基本属性列表保存在_OBJC_$_PROP_LIST_TestClass
变量中。因为类声明属性默认指定为@synthesize
,表示自动生成与属性关联的成员变量,以及自动生成属性关联的 getter 和 setter 方法。因此,TestClass
定义了stringProperty
属性,在 3.4 的成员变量列表中生成了关联属性的_stringProperty
成员变量及其对应数据,以及在 3.5 的方法列表中生成属性的 getter 方法stringPropertyGetter
及其IMP
所指向的函数_I_TestClass_stringPropertyGetter
、setter 方法stringPropertySetter:
及其IMP
所指向的函数_I_TestClass_stringPropertySetter_
。
属性列表长度固定为1,仅包含表示stringProperty
属性的_prop_t
机构体:
name
:属性名设置为"stringProperty"
;attributes
:特性置为"T@\"NSString\",&,N,GstringPropertyGetter,SstringPropertySetter:,V_stringProperty"
;static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_TestClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"stringProperty","T@\"NSString\",&,N,GstringPropertyGetter,SstringPropertySetter:,V_stringProperty"}}
};
复制代码
访问属性的代码在_I_TestClass_stringPropertyGetter
、_I_TestClass_stringPropertySetter_
的实现代码是 runtime 自动生成的,其原理是经过记录的关联成员变量的内存偏移量,直接操做关联成员变量的内存,所以有很高的效率。main()
函数中访问属性的代码NSString* strProp = testObj.stringProperty;
被转化如下 C 语言代码。原理是经过属性的attribute
获取 getter 方法名,直接调用 getter 方法。
NSString* strProp = ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)testObj, sel_registerName("stringPropertyGetter"));
复制代码
分类保存在_OBJC_$_CATEGORY_TestClass_$_TestCategory
name
:分类名"TestClass"
(Fixme: 不知道为何是设置成类名);cls
:置0
;instance_methods
:实例方法列表保存_OBJC_$_CATEGORY_INSTANCE_METHODS_TestClass_$_TestCategory
数组的地址,_OBJC_$_CATEGORY_INSTANCE_METHODS_TestClass_$_TestCategory
数组保存TestCategory
分类定义的实例方法列表;class_methods
:置0
,由于TestCategory
分类不包含类方法;protocols
:置0
,由于TestCategory
未遵循任何协议;properties
:置0
,属性列表保存_OBJC_$_PROP_LIST_TestClass_$_TestCategory
数组的地址,_OBJC_$_PROP_LIST_TestClass_$_TestCategory
数组保存TestCategory
分类定义的属性列表;初始化时,设置:
cls
:指向OBJC_CLASS_$_TestClass
;static struct _category_t _OBJC_$_CATEGORY_TestClass_$_TestCategory __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"TestClass",
0, // &OBJC_CLASS_$_TestClass,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_TestClass_$_TestCategory,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_TestClass_$_TestCategory,
};
static void OBJC_CATEGORY_SETUP_$_TestClass_$_TestCategory(void ) {
_OBJC_$_CATEGORY_TestClass_$_TestCategory.cls = &OBJC_CLASS_$_TestClass;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_TestClass_$_TestCategory,
};
复制代码
分类定义的方法保存在
static NSString * _I_TestClass_TestCategory_printCategoryContent_(TestClass * self, SEL _cmd, NSString *content) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qh_b2s8cgys01g3jxc0vq1y5ks80000gn_T_main_adaf3a_mi_1, content);
}
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_TestClass_$_TestCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"printCategoryContent:", "@24@0:8@16", (void *)_I_TestClass_TestCategory_printCategoryContent_}}
};
复制代码
分类定义的属性保存在_OBJC_$_PROP_LIST_TestClass_$_TestCategory
变量。
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_TestClass_$_TestCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"stringCategoryProperty","T@\"NSString\",&"}}
};
复制代码
将源文件定义的类添加到L_OBJC_LABEL_CLASS_$
列表,列表长度为1,由于仅定义了一个类TestClass
,元素保存OBJC_CLASS_$_TestClass
的地址。
将定义的分类添加到L_OBJC_LABEL_CATEGORY_$
列表,列表长度为1,由于仅定义了一个分类TestCategory
,元素保存_OBJC_$_CATEGORY_TestClass_$_TestCategory
的地址。
static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
&OBJC_CLASS_$_TestClass,
};
static struct _class_t *_OBJC_LABEL_NONLAZY_CLASS_$[] = {
&OBJC_CLASS_$_TestClass,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_TestClass_$_TestCategory,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
复制代码
上述代码除定义了保存类元素、分类元素的变量外,还包含_OBJC_LABEL_NONLAZY_CLASS_$
,其中保存了TestClass
类的地址。该列表是用于保存 nonlazy load class(Objective-C: What is a lazy class?)。所谓 non-lazy load class, 是指实现了load
方法的类。这些类须要在镜像(image)加载时当即完成 class realizing 并执行load
方法。Runtime 中定义了_getObjc2NonlazyClassList
函数用于获取镜像中的__objc_nlclslist
数据段中保存的 non-lazy load class。
反之,lazy load class 仅仅表示未实现load
方法的类,然而并非真正意义上的懒加载。严格意义上,类的懒加载是指,将类的解析(包括从镜像中载入类的静态元数据class_ro_t
、class realizing生成类的动态元数据class_rw_t
、class loading执行load
方法初始化类)推迟到正式载入二进制文件阶段。在此以前,runtime 只知道这是一个类,且将其标记为 future class。一般,开发者开发编译的 iOS App、framework、static library等二进制文件,都是静态加载的,其定义的类均在应用的加载阶段静态载入,仅.tbd、.dylib 或者封装了 .tbd、.dylib 的 framework(一般只有 Apple 自带的 framework 才容许封装动态库)才支持动态载入内存
注意:用
clang -rewrite-objc main.m
获得的main.cpp
中,non-lazy load class 列表_OBJC_LABEL_NONLAZY_CLASS_$
并无被添加到__objc_nlclslist
。但从 4.3 中打印的main.o
的数据段内容看,其实是有添加的。关于镜像的概念在下一篇文章中详细介绍,在这里仅须要知道镜像保存了源文件所定义的 Objective-C 元数据便可。
main.cpp
中定义面向对象元素的主要代码,是定义这些元素的 C 语言实现,并将这些元数据定义为静态或全局变量,添加到特定的数据段中。
有添加必有读取。Runtime 经过GETSECT
宏,定义了一系列读取特定数据段中的数据的函数。例如:GETSECT(_getObjc2ClassList, classref_t, "__objc_classlist")
定义:用于获取__objc_classlist
数据段的函数_getObjc2ClassList
,数据段的存储的数据类型为classref_t
。所以,若main.cpp
编译成镜像,则 runtime 调用_getObjc2ClassList
获取镜像中定义的全部类时,将会得到L_OBJC_LABEL_CLASS_$
变量,固然也包括其中保存的TestClass
类的定义OBJC_CLASS_$_TestClass
变量。
// 获取数据段的外部函数,具体实现逻辑隐藏未公开
extern uint8_t *getsectiondata(
const struct mach_header_64 *mhp,
const char *segname,
const char *sectname,
unsigned long *size);
template <typename T>
T* getDataSection(const headerType *mhdr, const char *sectname,
size_t *outBytes, size_t *outCount)
{
unsigned long byteCount = 0;
T* data = (T*)getsectiondata(mhdr, "__DATA", sectname, &byteCount);
if (!data) {
data = (T*)getsectiondata(mhdr, "__DATA_CONST", sectname, &byteCount);
}
if (!data) {
data = (T*)getsectiondata(mhdr, "__DATA_DIRTY", sectname, &byteCount);
}
if (outBytes) *outBytes = byteCount;
if (outCount) *outCount = byteCount / sizeof(T);
return data;
}
#define GETSECT(name, type, sectname) \
type *name(const headerType *mhdr, size_t *outCount) { \
return getDataSection<type>(mhdr, sectname, nil, outCount); \
} \
type *name(const header_info *hi, size_t *outCount) { \
return getDataSection<type>(hi->mhdr(), sectname, nil, outCount); \
}
// function name content type section name
GETSECT(_getObjc2SelectorRefs, SEL, "__objc_selrefs");
GETSECT(_getObjc2MessageRefs, message_ref_t, "__objc_msgrefs");
GETSECT(_getObjc2ClassRefs, Class, "__objc_classrefs");
GETSECT(_getObjc2SuperRefs, Class, "__objc_superrefs");
GETSECT(_getObjc2ClassList, classref_t, "__objc_classlist");
GETSECT(_getObjc2NonlazyClassList, classref_t, "__objc_nlclslist");
GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist");
GETSECT(_getObjc2NonlazyCategoryList, category_t *, "__objc_nlcatlist");
GETSECT(_getObjc2ProtocolList, protocol_t *, "__objc_protolist");
GETSECT(_getObjc2ProtocolRefs, protocol_t *, "__objc_protorefs");
GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func");
复制代码
首先,用clang -framework Foundation -o main.o main.m
命令编译main.m
源文件,在当前目录输出main.o
目标文件。而后,用otool -o -v main.o
(-o print the Objective-C segment)命令查看目标文件中的 Objective-C 相关数据段内容以下。发现_OBJC_IMAGE_INFO
的flag
值,与main.cpp
中的定义不一致,应该以 Clang 编译获得的main.o
目标文件的数据为准。
main.o:
Contents of (__DATA,__objc_classlist) section
00000001000010b8 0x1000012b8 _OBJC_CLASS_$_TestClass
isa 0x100001290 _OBJC_METACLASS_$_TestClass
superclass 0x0 _OBJC_CLASS_$_NSObject
cache 0x0 __objc_empty_cache
vtable 0x0
data 0x100001210 (struct class_ro_t *)
flags 0x0
instanceStart 8
instanceSize 24
reserved 0x0
ivarLayout 0x0
name 0x100000ef0 TestClass
baseMethods 0x1000010d0 (struct method_list_t *)
entsize 24
count 4
name 0x100000f60 printCategoryContent:
types 0x100000f89 @24@0:8@16
imp 0x100000d10 -[TestClass(TestCategory) printCategoryContent:]
name 0x100000f0c printContent:
types 0x100000f89 @24@0:8@16
imp 0x100000c70 -[TestClass printContent:]
name 0x100000f1a stringPropertyGetter
types 0x100000f94 @16@0:8
imp 0x100000cb0 -[TestClass stringPropertyGetter]
name 0x100000f2f stringPropertySetter:
types 0x100000f9c v24@0:8@16
imp 0x100000cd0 -[TestClass stringPropertySetter:]
baseProtocols 0x0
ivars 0x1000011c8
entsize 32
count 2
offset 0x100001288 8
name 0x100000f45 stringIvar
type 0x100000fa7 @"NSString"
alignment 3
size 8
offset 0x100001280 16
name 0x100000f50 _stringProperty
type 0x100000fa7 @"NSString"
alignment 3
size 8
weakIvarLayout 0x0
baseProperties 0x100001138
entsize 16
count 2
name 0x100000ec0 stringCategoryProperty
attributes 0x100000ed7 T@"NSString",&
name 0x100000e47 stringProperty
attributes 0x100000e56 T@"NSString",&,N,GstringPropertyGetter,SstringPropertySetter:,V_stringProperty
Meta Class
isa 0x0 _OBJC_METACLASS_$_NSObject
superclass 0x0 _OBJC_METACLASS_$_NSObject
cache 0x0 __objc_empty_cache
vtable 0x0
data 0x100001180 (struct class_ro_t *)
flags 0x1 RO_META
instanceStart 40
instanceSize 40
reserved 0x0
ivarLayout 0x0
name 0x100000ef0 TestClass
baseMethods 0x100001160 (struct method_list_t *)
entsize 24
count 1
name 0x100000f07 load
types 0x100000f81 v16@0:8
imp 0x100000c40 +[TestClass load]
baseProtocols 0x0
ivars 0x0
weakIvarLayout 0x0
baseProperties 0x0
Contents of (__DATA,__objc_classrefs) section
0000000100001278 0x1000012b8 _OBJC_CLASS_$_TestClass
Contents of (__DATA,__objc_catlist) section
Contents of (__DATA,__objc_imageinfo) section
version 0
flags 0x40 OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES
复制代码
注意到上面数据中不包含用于保存分类的__objc_catlist
数据段,这是由于main.m
代码中没有调用到TestCategory
分类中的方法,所以不会加载该分类。有两种方法修正:
main
函数中加入[testObj printCategoryContent:@"Something"];
;clang -framework Foundation -all_load -o main.o main.m
命令编译,添加-all_load
编译选项强制编译全部 Objective-C 元素;注意:用于保存 non-lazy load class 列表的
__objc_nlclslist
数据段,以及保存 non-lazy load category 列表的__objc_nlcatlist
数据段,不会显示在otool -o
命令打印的 Objective-C 元数据中。但只要TestClass
类有实现load
方法,是能够用otool -v -s __DATA __objc_nlclslist main.o
命令查看到__objc_nlclslist
数据段是确实有保存TestClass
类的地址的。clang -rewrite-objc
生成的main.cpp
中的 Objective-C 元数据,与编译生成的目标文件main.o
实际包含的 Objective-C 元数据之因此有少量出入,极可能是由于 Clang 编译器在 rewrite Objective-C 阶段和编译生成目标文件阶段之间还有其余的元数据处理逻辑。
将 Objective-C 元素转化为 C 语言元素时,须要定义这些元素在 runtime 中的结构体的同构结构体类型,以暴露它们的数据结构。一般将它们定义为这些同构结构体类型的静态变量,并保存到特定数据段中;
Runtime 定义了一些函数用于从特定的数据段读取数据,以加载镜像(image)中保存的 Objective-C 元数据,例如,_getObjc2ClassList
用于获取镜像中__objc_classlist
数据段保存的全部类的元数据,_getObjc2CategoryList
获取镜像中__objc_catlist
数据段中保存的全部分类的元数据;
类在内存中的结构为_class_t
结构体,该结构体与 runtime 中的objc_class
结构体是同构的,包含类名、超类、class_ro_t
中的基本方法列表、基本属性列表、协议列表等元数据。class_rw_t
数据在运行时动态生成;
下一篇介绍应用加载过程。