Objective-c runtime (一)
一 運(yùn)行時(shí)系統(tǒng)相關(guān)特性
- 使用
Objective-C
中的消息傳遞特性可以調(diào)用類和對(duì)象中的方法坏瞄。對(duì)象消息傳遞是一種動(dòng)態(tài)特性,既接收器和接收器中被調(diào)用的方法是在運(yùn)行時(shí)確定的。 -
消息傳遞表達(dá)式包含接收器(接受消息的對(duì)象/類)和消息秃踩,而消息又由選擇器和相應(yīng)的輸入?yún)?shù)構(gòu)成衷快。如圖。
- 選擇器是一種分段的文本字符串搔啊,每個(gè)分段以冒號(hào)結(jié)尾并且后跟參數(shù)柬祠。如上圖。
- 選擇器數(shù)據(jù)類型(SEL)是一種特殊的
Objective-C
數(shù)據(jù)類型负芋,它用于在編譯源代碼時(shí)使用具有唯一性的標(biāo)志符替換選擇器漫蛔。使用@selector
關(guān)鍵字或Foundation
框架中的NSSelectorFromString()
函數(shù),可以創(chuàng)建類型為SEL
的變量旧蛾。 - 使用動(dòng)態(tài)類型功能可以在運(yùn)行時(shí)決定對(duì)象的類型莽龟,因而能夠由運(yùn)行時(shí)因素決定在程序中使用哪種類型的對(duì)象,
Objective-C
通過(guò)id
數(shù)據(jù)類型支持動(dòng)態(tài)類型锨天。 - 使用動(dòng)態(tài)方法決議能夠以動(dòng)態(tài)方式實(shí)現(xiàn)方法毯盈。使用
Objective-C
的@dynamic
指令可以告知編譯器與某個(gè)屬性關(guān)聯(lián)的方法會(huì)以動(dòng)態(tài)方式實(shí)現(xiàn)。NSObject
中含有resolveInstanceMethod:
和resolveClassMethod:
方法病袄,使用他們能夠以動(dòng)態(tài)方式分別實(shí)現(xiàn)由選擇器指定的實(shí)例和類方法搂赋。 -
Foundation
框架中NSObject
類的API含有非常多用于執(zhí)行對(duì)象內(nèi)省的方法。在運(yùn)行程序時(shí)益缠,這些API能夠以動(dòng)態(tài)方式查詢方法的信息脑奠。它們還可以測(cè)試方法的繼承性、行為和一致性左刽。
二 運(yùn)行時(shí)系統(tǒng)的結(jié)構(gòu)
運(yùn)行時(shí)系統(tǒng)的組成部分
Objective-C
的運(yùn)行時(shí)系統(tǒng)由兩個(gè)主要部分構(gòu)成:編譯器和運(yùn)行時(shí)系統(tǒng)庫(kù)捺信。
編譯器
Objective-C
語(yǔ)言中的面向?qū)ο笤睾蛣?dòng)態(tài)特性都是通過(guò)運(yùn)行時(shí)系統(tǒng)實(shí)現(xiàn)的。概括來(lái)講,運(yùn)行時(shí)系統(tǒng)由下列部分組成:
- 類元素(接口迄靠、實(shí)現(xiàn)代碼秒咨、協(xié)議、分類掌挚、方法雨席、屬性、實(shí)例變量)
- 類實(shí)例(對(duì)象)
- 對(duì)象消息傳遞(包括動(dòng)態(tài)類型和動(dòng)態(tài)綁定)
- 動(dòng)態(tài)方法決議
- 動(dòng)態(tài)加載
- 對(duì)象內(nèi)省
當(dāng)編譯器解析使用了這些語(yǔ)言元素和特性的Objective-C源代碼時(shí)吠式,它會(huì)使用適當(dāng)?shù)倪\(yùn)行時(shí)系統(tǒng)庫(kù)數(shù)據(jù)結(jié)構(gòu)和實(shí)現(xiàn)該語(yǔ)言特定行為的函數(shù)陡厘,生成可執(zhí)行代碼。例如
-
生成對(duì)象消息傳遞代碼
當(dāng)編譯解析對(duì)象消息時(shí)特占,如:
[obj show]
它會(huì)生成調(diào)用運(yùn)行時(shí)系統(tǒng)庫(kù)中函數(shù)objc_msgSend()
的代碼糙置。該函數(shù)將接收器、選擇器和消息傳遞的參數(shù)作為輸入?yún)?shù)是目。因此編譯器會(huì)將源代碼中的所有消息傳遞表達(dá)式轉(zhuǎn)換為調(diào)用運(yùn)行時(shí)系統(tǒng)庫(kù)函數(shù)objc_msgSend()
的代碼谤饭。
源文件1.m內(nèi)容如下:
#import <Foundation/Foundation.h>@interface MyClass:NSObject - (void)show; @end @implementation MyClass - (void)show{ NSLog(@"%@", @"show"); } @end int main(int argc, char *argv[]) { @autoreleasepool { MyClass *obj = [[MyClass alloc] init]; [obj show]; } }
經(jīng)過(guò)
clang -rewrite-objc 1.m
會(huì)得到編譯后的cpp文件,相關(guān)內(nèi)容如下:
-
**生成類和對(duì)象的代碼
當(dāng)編譯器解析含有類定義和對(duì)象的源代碼時(shí)懊纳,它會(huì)生成相應(yīng)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)揉抵。
Objective-C
中的類與運(yùn)行時(shí)系統(tǒng)中的Class數(shù)據(jù)結(jié)構(gòu)對(duì)應(yīng)。Class
是指向objc_class
不透明數(shù)據(jù)類型的指針嗤疯。
typedef struct objc_class Class
Objective-C
對(duì)形象與運(yùn)行時(shí)系統(tǒng)中的objc_object
數(shù)據(jù)結(jié)構(gòu)對(duì)應(yīng)冤今。
struct objc_object
{
Class isa;
//含有實(shí)例變量的長(zhǎng)度可變數(shù)據(jù)
}
實(shí)際上所有的Objective-C對(duì)象的類和對(duì)象對(duì)應(yīng)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)(objc_object
和objc_class
)都是以isa
指針開(kāi)頭(對(duì)應(yīng)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)的包含的第一個(gè)元素是isa
)。
下面的實(shí)例程序可以驗(yàn)證茂缚。
#import <Foundation/Foundation.h>
#import "LZPerson.h"
#import <objc/message.h>
#import <objc/runtime.h>
@interface LZDog: NSObject{
NSInteger _age;
}@end @implementation LZDog - (instancetype)initWithAge:(NSInteger) age{ self = [super init]; if (self) { _age = age; } return self; } @end int main(int argc, const char * argv[]) { @autoreleasepool { LZDog *dog1 = [[LZDog alloc] initWithAge:0x123456780a0b0c0d]; LZDog *dog2 = [[LZDog alloc] initWithAge:0xaabbccdd12345678]; //get the objec size NSInteger objcSize = class_getInstanceSize([LZDog class]); //打印對(duì)象的內(nèi)存數(shù)據(jù) NSData *dog1Data = [NSData dataWithBytes:(__bridge void*)dog1 length:objcSize]; NSData *dog2Data = [NSData dataWithBytes:(__bridge void*)dog2 length:objcSize]; //查看對(duì)象內(nèi)存 NSLog(@"dog1: %@", dog1Data); NSLog(@"dog2: %@", dog2Data); //打印類地址 NSLog(@"class address:%p", [LZDog class]); //查看類內(nèi)存 id dogClass = objc_getClass("LZDog"); NSInteger classSize = class_getInstanceSize([dogClass class]); NSData *classData = [NSData dataWithBytes:(__bridge void*)dogClass length:classSize]; NSLog(@"dogclass: %@", classData); //打印父類(NSobject)地址 NSLog(@"superclass address: %p", [LZDog superclass]); } return 0; }
運(yùn)行結(jié)果如下:
2015-10-04 11:25:01.297 testRuntime01[1186:87309] dog1: <78240000 01000000 0d0c0b0a 78563412>
2015-10-04 11:25:01.298 testRuntime01[1186:87309] dog2: <78240000 01000000 78563412 ddccbbaa>
2015-10-04 11:25:01.298 testRuntime01[1186:87309] class address:0x100002478
2015-10-04 11:25:01.298 testRuntime01[1186:87309] dogclass: <a0240000 01000000 f0300c78 ff7f0000>
2015-10-04 11:25:01.298 testRuntime01[1186:87309] superclass address: 0x7fff780c30f0
分析結(jié)果如圖:
運(yùn)行時(shí)系統(tǒng)庫(kù)
蘋(píng)果公司提供的Objective-C
運(yùn)行時(shí)系統(tǒng)庫(kù)實(shí)現(xiàn)了Objective-C
的面向?qū)ο筇匦院蛣?dòng)態(tài)屬性戏罢。我們可以在代碼中直接運(yùn)用運(yùn)行時(shí)系統(tǒng)提供的API。下面代碼實(shí)例展示了如何在代碼中使用運(yùn)行時(shí)系統(tǒng)API阱佛。
import <Foundation/Foundation.h>
import "LZPerson.h"
import <objc/message.h>
import <objc/runtime.h>
/*
1. 動(dòng)態(tài)創(chuàng)建一個(gè)類 LZStudent(繼承自NSObject)
2. 動(dòng)態(tài)添加實(shí)例變量 name
3. 動(dòng)態(tài)添加成員方法 show
*/
static void show(id self, SEL _cmd){
NSLog(@"show");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//創(chuàng)建一個(gè)類帖汞,繼承自NSobject
Class LZStudent = objc_allocateClassPair([NSObject class], "LZStudent", 0);
BOOL isOk;
//向該類添加一個(gè)實(shí)例變量 NSString *_name
isOk = class_addIvar(LZStudent, "_name", sizeof(NSString *), log2(sizeof(id)), @encode(NSString));
if (!isOk) {
NSLog(@"addivar fail");
}
//想該類添加一個(gè)實(shí)例方法戴而。
isOk = class_addMethod(LZStudent, NSSelectorFromString(@"show"), (IMP)show, "v@:");
if (!isOk) {
NSLog(@"addMethod fail");
}
objc_registerClassPair(LZStudent);
unsigned int count ;
Ivar *arr = class_copyIvarList(LZStudent, &count);
//打印類變量的個(gè)數(shù)
NSLog(@"variable count: %u", count);
//打印變量的名字和類型
for (NSInteger i = 0; i < count; i ++) {
const char* ivarName = ivar_getName(arr[i]);
const char* ivarType = ivar_getTypeEncoding(arr[i]);
NSLog(@"name: %s, type: %s", ivarName, ivarType);
}
id obj = [[LZStudent alloc] init];
[obj performSelector:NSSelectorFromString(@"show")];
}
return 0;
}
運(yùn)行結(jié)果如下
2015-10-04 15:25:19.057 testRuntime01[1537:152205] variable count: 1
2015-10-04 15:25:19.058 testRuntime01[1537:152205] name: _name, type: {NSString=#}
2015-10-04 15:25:19.058 testRuntime01[1537:152205] show
運(yùn)行時(shí)系統(tǒng)庫(kù)數(shù)據(jù)類型和函數(shù)為運(yùn)行時(shí)系統(tǒng)庫(kù)提供了實(shí)現(xiàn)各種Objective-C
特性(如消息傳遞)所必須的數(shù)據(jù)類型核函數(shù)凑术。如圖
![](http://23.234.14.33/imagesdir/Runtime_messge.png)
當(dāng)程序想對(duì)象發(fā)送消息時(shí),運(yùn)行時(shí)系統(tǒng)會(huì)通過(guò)自定義代碼中的類方法緩存和虛函數(shù)表所意,查找類的實(shí)例方法淮逊。為了找到相應(yīng)的方法運(yùn)行時(shí)系統(tǒng)會(huì)搜索整個(gè)類的層次結(jié)構(gòu),找到方法后扶踊,它就會(huì)執(zhí)行方法的實(shí)現(xiàn)代碼泄鹏。運(yùn)行時(shí)系統(tǒng)庫(kù)含有非常多用于實(shí)現(xiàn)消息傳遞的設(shè)計(jì)機(jī)制。下面分別介紹這幾個(gè)機(jī)制:
-
通過(guò)虛函數(shù)表查找方法
運(yùn)行時(shí)系統(tǒng)定義了一種方法數(shù)據(jù)類型(objc_method
)代碼如下
struct objc_method
{
SEL method_name;
char *method_type;
IMP method_imp;
}
typedef struct objc_method Method;
因?yàn)樵趫?zhí)行程序的過(guò)程中調(diào)用方法的操作可能執(zhí)行數(shù)百萬(wàn)次秧耗,所以運(yùn)行時(shí)系統(tǒng)使用了一種快速高效的方法查詢和調(diào)用機(jī)制备籽。虛函數(shù)表是運(yùn)行時(shí)系統(tǒng)使用的動(dòng)態(tài)綁定的支持機(jī)制。Objective-C
運(yùn)行時(shí)系統(tǒng)實(shí)現(xiàn)了一種自定義的虛函數(shù)分派機(jī)制,專門用于最大限度的提高性能和靈活性车猬。
虛函數(shù)表是一個(gè)用于存儲(chǔ)IMP
(Objective-C方法的實(shí)現(xiàn)代碼)的數(shù)據(jù)數(shù)組霉猛。每個(gè)運(yùn)行時(shí)系統(tǒng)類實(shí)例(obcj_class
)都有一個(gè)紙向虛函數(shù)表的指針。
每個(gè)尅實(shí)例還擁有最近調(diào)用過(guò)方法的指針緩存珠闰。運(yùn)行時(shí)系統(tǒng)庫(kù)先搜索緩存惜浅,如果沒(méi)找到,再去虛函數(shù)表去查找伏嗜,如果在虛函數(shù)表找到了就將IMP存儲(chǔ)到緩存中備用坛悉。 -
消息分派
當(dāng)我們向?qū)ο蟀l(fā)送消息時(shí)比如[obj message ]
,編譯器會(huì)根據(jù)對(duì)象找到對(duì)應(yīng)的類承绸,然后從類(及其父類)的緩存和虛函數(shù)表(參考1)中查找裸影,并將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語(yǔ)言函數(shù)調(diào)用objc_msgSend()
()。如果沒(méi)有找到符合的方法军熏,那就執(zhí)行消息轉(zhuǎn)發(fā)空民。
消息轉(zhuǎn)發(fā)分為兩大階段。第一階段先征詢接受者所屬的類羞迷,看其能否動(dòng)態(tài)添加方法界轩,這叫做動(dòng)態(tài)方法決議(resolveInstanceMethod:)
。如果我們沒(méi)有動(dòng)態(tài)的添加方法實(shí)現(xiàn)那么熱就會(huì)進(jìn)入第二階段衔瓮。首先運(yùn)行時(shí)系統(tǒng)請(qǐng)接受著看看能否將消息轉(zhuǎn)發(fā)給其他的對(duì)象浊猾,如能則該消息有其他對(duì)象處理。這成為快速轉(zhuǎn)發(fā)(forwardingTargetForSelector:)
热鞍。如果沒(méi)有合適的對(duì)象處理消息葫慎,運(yùn)行時(shí)系統(tǒng)會(huì)把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation
中,再給接受者最后一次機(jī)會(huì)薇宠。整個(gè)過(guò)程如圖所示偷办。
示例代碼 -
訪問(wèn)類實(shí)例方法
當(dāng)我們向類發(fā)送消息時(shí),如[NSobject alloc]
澄港,運(yùn)行時(shí)系統(tǒng)是通過(guò)元類實(shí)現(xiàn)的椒涯。
元類
是一種特殊的類對(duì)象,運(yùn)行時(shí)系統(tǒng)使用其中含有的信息能夠找到并調(diào)用類方法回梧。每個(gè)類都有一個(gè)獨(dú)一無(wú)二的元類废岂,元類中存放著類的類方法列表(類的實(shí)例方法存放在類里,類方法列表存放在元類那里)狱意。
Objective-C
的對(duì)象的isa
指針指向類湖苞,類的isa
指向元類。
綜上所述運(yùn)行時(shí)系統(tǒng)通過(guò)下列方式對(duì)實(shí)例和類方法執(zhí)行消息傳遞操作详囤。- 當(dāng)向?qū)ο蟀l(fā)送消息時(shí)财骨,會(huì)通過(guò)類的緩存和虛函數(shù)表,獲得相應(yīng)的方法次慢。
- 當(dāng)向類發(fā)送消息時(shí)重窟, 會(huì)通過(guò)元類的緩存和虛函數(shù)表朗徊,獲得相應(yīng)的方法报慕。