Objective-C 編譯器與運(yùn)行時(shí)系統(tǒng)支撐著OC程序的運(yùn)行励堡。
Objective-C程序在三個(gè)層面上與runtime系統(tǒng)交互:
Objective-C源代碼:編譯器把OC代碼類兼都、方法昆著、成員變量等信息轉(zhuǎn)化為支持語言動(dòng)態(tài)特性的數(shù)據(jù)結(jié)構(gòu)與函數(shù)诫欠。比如消息傳遞機(jī)制中的核心函數(shù)
objc_msgSend
潦刃,即由OC代碼的消息傳遞語句轉(zhuǎn)換而來变隔。NSObject提供了一系列的自省(Introspection)方法喘落,也是運(yùn)行時(shí)的一部分茵宪。
Runtime函數(shù)。
消息傳遞機(jī)制
在Objective-C里瘦棋,消息(message)是到運(yùn)行時(shí)才綁定到方法實(shí)現(xiàn)的.
意思就是說, 像
[receiver message];
這樣一條語句, 編譯器會(huì)把他轉(zhuǎn)換為
objc_msgSend(receiver, selector);
這樣第一個(gè)C語言的函數(shù)調(diào)用, 參數(shù)分別是消息接受者(對(duì)象), 消息對(duì)應(yīng)的方法名稱(選擇子), 若改方法帶參數(shù), 則為
objc_msgSend(receiver, selector, arg1, arg2, ...)
該函數(shù)的動(dòng)態(tài)綁定過程是這樣的:
- 它首先沿著類的繼承體系去尋找選擇子對(duì)應(yīng)的方法實(shí)現(xiàn).
- 找到后調(diào)用具體的方法實(shí)現(xiàn), 并把對(duì)象指針以及各參數(shù)傳遞給該方法, 隨后調(diào)用它.
- 最后返回該方法的返回值.
它的函數(shù)原型:
id objc_msgSend(id self, SEL cmd, ...)
回頭看動(dòng)態(tài)綁定過程的第一步.
Objective-C里, 每個(gè)類里都維護(hù)著一張表格(dispatch table), 其中的指針正是指向該類下所定義的方法實(shí)現(xiàn), 而方法的選擇子(selector)作為查表用的"鍵".
每個(gè)類里除了該表之外, 還擁有一個(gè)指向其父類的指針.
這些類與對(duì)象的結(jié)構(gòu)是這樣的:
對(duì)象實(shí)例里有一個(gè)isa
指針, 指向它的類對(duì)象.
objc_msgSend
函數(shù)依賴著上述的繼承體系去查找并調(diào)用恰當(dāng)?shù)姆椒?
為了加速方法的查找, 每個(gè)類里除了自身定義的方法列表外, 還維護(hù)這一張快速映射表作為緩存. 多次對(duì)它查找同一selector將不再向上追溯查找, 而直接查找本身的緩存并返回對(duì)應(yīng)的方法實(shí)現(xiàn).
剛才提到要調(diào)用的方法實(shí)現(xiàn), 每個(gè)OC對(duì)象的方法都可視為一個(gè)C函數(shù), 其原型如下:
<return_type> Class_selector(id self, SEL _cmd, ...)
實(shí)際函數(shù)名可能跟上面的不一樣. 但注意的是該函數(shù)里是包括了self
和_cmd
兩個(gè)隱含參數(shù)的. 所謂"隱含", 是指在開發(fā)人員編寫的方法代碼里, 是不存在這兩個(gè)參數(shù), 但我們都可以通過這兩個(gè)變量名去訪問.
消息轉(zhuǎn)發(fā)機(jī)制
在上一節(jié)消息傳遞機(jī)制中, 對(duì)象接收到一個(gè)消息后, 去搜尋其對(duì)應(yīng)方法實(shí)現(xiàn)的函數(shù)地址. 若搜尋不到, 并不馬上拋出異常, 而是再給接受者一次機(jī)會(huì), 進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制.
消息轉(zhuǎn)發(fā)分為兩大階段. 第一階段先征詢接受者所屬的類, 看其是否能動(dòng)態(tài)添加方法, 以處理當(dāng)前這個(gè)未知的選擇子(unknown selector), 這叫做動(dòng)態(tài)方法解析(Dynamic Method Resolution); 第二階段則為"完整的消息轉(zhuǎn)發(fā)機(jī)制"(full forwarding mechanism).
動(dòng)態(tài)方法解析
對(duì)象在收到無法解讀的消息后, 首先將調(diào)用其所屬類的下列類方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector
顧名思義, 該方法作用為解析實(shí)例方法, 相應(yīng)地也有個(gè)類似的方法, 為解析類方法所用: resolveClassMethod
.
此方法在respondsToSelector:
或instancesRespondToSelector:
被調(diào)用后返回前, 也有一次機(jī)會(huì)為自己動(dòng)態(tài)添加一個(gè)方法的實(shí)現(xiàn).
動(dòng)態(tài)方法解析常用來實(shí)現(xiàn) @dynamic 屬性.
下面看一個(gè)完整的例子演示動(dòng)態(tài)方法解析.
假設(shè)要編寫一個(gè)類似"字典"的對(duì)象, 它里面可以容納其它對(duì)象, 只不過開發(fā)者要直接通過屬性來存取其中的數(shù)據(jù). 這個(gè)類的設(shè)計(jì)思路是: 有開發(fā)者來添加屬性定義, 并將其聲明為 @dynamic, 類則會(huì)自動(dòng)處理相關(guān)屬性值的存放與獲取操作. 聽起來不錯(cuò)吧? >_<
類的接口定義如下:
#import <Foundation/Foundation.h>
@interface AutoDictionary : NSObject
@property (nonatomic, strong) NSString *string;
@property (nonatomic, strong) NSNumber *number;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) id opaqueObject;
@end
這個(gè)類將裝載各種不同類型的對(duì)象, 看起來與平時(shí)普通的類沒啥區(qū)別啊? 我們看類的實(shí)現(xiàn).
#import "AutoDictionary.h"
#import <objc/runtime.h>
@interface AutoDictionary ()
@property (nonatomic, strong) NSMutableDictionary *backingStore;
@end
@implementation AutoDictionary
@dynamic string, number, date, opaqueObject;
- (instancetype)init {
if (self = [super init]) {
_backingStore = [[NSMutableDictionary alloc] init];
}
return self;
}
聲明各屬性為 @dynamic, 編譯器不會(huì)自動(dòng)為property生成存取方法和實(shí)例變量. 由我們自行實(shí)現(xiàn).
關(guān)鍵在于resolveInstanceMethod:
方法的實(shí)現(xiàn).
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selString = NSStringFromSelector(sel);
if ([selString hasPrefix:@"set"]) {
class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
} else {
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
}
return [super resolveInstanceMethod:sel];
}
眾所周知, 任何的點(diǎn)語法訪問都會(huì)轉(zhuǎn)化為名為<name>
, set<Name>
形式的存取方法來訪問, 以上使用class_addMethod
在運(yùn)行時(shí)添加存取方法, 所有屬性將共用這一對(duì)getter與setter.
class_addMethod
函數(shù)第一和第二參數(shù)分別為類對(duì)象自身與選擇子, 第三個(gè)參數(shù)為待添加方法實(shí)現(xiàn)對(duì)應(yīng)的函數(shù)指針, 第四為待添加方法的"類型編碼", 指定該添加方法的參數(shù)與返回值等.
使用class_addMethod
動(dòng)態(tài)添加方法后, 所添加的方法將一直在運(yùn)行時(shí)存在, 下一次的調(diào)用該方法將不再進(jìn)行動(dòng)態(tài)方法解析.
下面實(shí)現(xiàn)getter與setter:
// getter
id autoDictionaryGetter(id self, SEL _cmd) {
// Get the backing store from the object
AutoDictionary *typedSelf = (AutoDictionary*)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
// The key is simply the selector name
NSString *key = NSStringFromSelector(_cmd);
// Return the value
return [backingStore objectForKey:key];
}
//setter
void autoDictionarySetter(id self, SEL _cmd, id value) {
// Get the backing store from the object
AutoDictionary *typedSelf = (AutoDictionary*)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
/** The selector will be for example, "setOpaqueObject:".
* We need to remove the "set", ":" and lowercase the first
* letter of the remainder.
*/
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
// Remove the `:' at the end
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
// Remove the `set' prefix
[key deleteCharactersInRange:NSMakeRange(0, 3)];
// Lowercase the first character
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if (value) {
[backingStore setObject:value forKey:key];
} else {
[backingStore removeObjectForKey:key];
}
}
使用它們的方式很簡單:
AutoDictionary *dict = [[AutoDictionary alloc] init];
dict.date = [NSDate dateWithTimeIntervalSince1970:475372800];
NSLog(@"dict.date = %@", dict.date);
//Output: dict.date = 1985-01-24 00:00:00 +0000
而且它還是KVC兼容的哦! *(關(guān)于KVC與KVO, 可參考我之前的博客
[dict setValue:@"I'm a string!" forKey:@"string"];
NSLog(@"dict.string = %@", dict.string);
//Output: dict.string = I'm a string!
備援接受者
在完整的消息轉(zhuǎn)發(fā)來臨之前, 當(dāng)前接受者還有第二次機(jī)會(huì)處理未知的選擇子. 處理方法如下:
- (id)forwardingTargetForSelector:(SEL)aSelector
運(yùn)行時(shí)系統(tǒng)通過該方法詢問能否把無法識(shí)別的選擇子轉(zhuǎn)給其它對(duì)象處理呢?
例如, 在一個(gè)對(duì)象內(nèi)部, 可能還有其它一系列對(duì)象, 該對(duì)象可經(jīng)由此方法將能夠處理某選擇子的相關(guān)內(nèi)部對(duì)象返回. 這樣看來, 就好像是該對(duì)象親自處理了這些消息似的. 這樣可以模擬出"多重繼承"的特性.
完整的消息轉(zhuǎn)發(fā)
終于來到了這一步. 首先創(chuàng)建NSInvocation
對(duì)象, 把尚未處理的有關(guān)該消息的全部細(xì)節(jié)封裝起來, 包括選擇子, 目標(biāo)(target), 參數(shù)與返回值等. 在觸發(fā)NSInvocation
對(duì)象時(shí), 消息派發(fā)系統(tǒng)(message-dispatch system)將親自出馬, 把消息指派給目標(biāo)對(duì)象.
消息轉(zhuǎn)發(fā)方法:
- (void)forwardInvocation:(NSInvocation *)invocation
在此方法里需要做的事情是:
- 決定消息發(fā)送的目標(biāo)對(duì)象;
- 隨參數(shù)一起發(fā)送該消息.
消息通過invokeWithTarget:
發(fā)送.
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
以上代碼中最后調(diào)用超類處理該消息, 沿著繼承體系向上, 每個(gè)類都有機(jī)會(huì)處理該請(qǐng)求, 直至NSObject, 它的該方法默認(rèn)實(shí)現(xiàn)為拋出doesNotRecognizeSelector:
異常.
相對(duì)于簡單的消息發(fā)送語句 [receiver message];
, forwardInvocation:
提供了一種更加靈活的機(jī)制, 避免了冗余的方法重寫或者破壞類繼承體系, 而提供了一種類似"消息中轉(zhuǎn)派發(fā)"的機(jī)制. 另外NSInvocation也提供了對(duì)待轉(zhuǎn)發(fā)消息的修改機(jī)制, 甚至不做轉(zhuǎn)發(fā), 等等, 也提供了更多的操作性.
初探Objective-C Runtime System, 這篇博文對(duì)Runtime消息傳遞, 轉(zhuǎn)發(fā)機(jī)制等做了一些探討. 關(guān)于更多的Runtime研究與實(shí)踐, 將在日后的博客中更新.