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
數據在運行時動態生成;
下一篇介紹應用加載過程。