1. 消息轉(zhuǎn)發(fā)機制
當對象接收到無法解讀的消息后,就會啟動“消息轉(zhuǎn)發(fā)”機制军拟,開發(fā)者可經(jīng)由此過程告訴對象應(yīng)該如何處理未知消息郊愧。
消息轉(zhuǎn)發(fā)分為兩大階段
- 第一階段:先征詢接收者所屬的類晓铆,看其是否能動態(tài)添加方法筷黔,以處理當前這個“未知的選擇器”,這叫做“動態(tài)方法解析”(dynamic method resolution)刊头。
- 第二階段:涉及“完整的消息轉(zhuǎn)發(fā)機制”(full forwarding mechanism)黍瞧。
運行時系統(tǒng)會請求接收者以“動態(tài)新增方法”之外的手段來處理與消息相關(guān)的方法調(diào)用,這又細分為兩小步原杂。首先印颤,請接收者看看有沒有其他對象能處理這條消息。若有穿肄,則運行期系統(tǒng)會把消息轉(zhuǎn)給那個對象年局,于是消息轉(zhuǎn)發(fā)過程結(jié)束。若沒有“備援的接收者”咸产,則啟動完整的消息轉(zhuǎn)發(fā)機制矢否,運行時系統(tǒng)會把與消息有關(guān)的全部細節(jié)都封裝到NSInvocation對象中,再給接收者最后一次機會脑溢,令其設(shè)法解決當前還沒處理的這條消息僵朗。
2. 動態(tài)方法解析
對象在收到無法解讀的消息后,首先將調(diào)用其所屬類的下列類方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector
解釋:selector是未知的選擇器屑彻,返回值為Boolean類型验庙,表示這個類是否能新增一個實例方法用以處理此選擇器。
在繼續(xù)往下執(zhí)行轉(zhuǎn)發(fā)之前社牲,本類有機會新增一個處理未知選擇器的方法粪薛,便是通過調(diào)用“resolveInstanceMethod:”或“resolveClassMethod:”方法來實現(xiàn)的。
但是搏恤,使用這種辦法有個前提:相關(guān)方法的實現(xiàn)代碼已經(jīng)寫好违寿,只等著運行的時候動態(tài)插在類里面就可以了让禀。此方案常用來實現(xiàn)@dynamic屬性。
id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);
+ (BOOL)resolveInstanceMethod:(SEL)selector{
NSString *selectorString = NSStringFromSelector(selector);// 將選擇器轉(zhuǎn)換為字符串
if(/* selector is from a @dynamic property */){ // 使用了@dynamic屬性
if([selectorString hasPrefix:@"set"]){
class_addMethod(self, selector, (IMP)autoDictionarySetter, "V@:@");
} else {
class_addMethod(self, selector, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
return [super resolveInstanceMethod:selector];
}
3. 備援接收者
在第二階段的第一小步中陨界,運行期系統(tǒng)會問未知的選擇器能不能把這條消息轉(zhuǎn)發(fā)給其他接收者來處理。與該步驟對應(yīng)的處理方法如下:
- (id)forwardingTargetForSelector:(SEL)selector
解釋:selector代表未知的選擇器痛阻,若當前接收者能找到備援對象菌瘪,則將其返回,若找不到阱当,就返回nil俏扩。
通過此方案,可以用“組合”(composition)來模擬出“多重繼承”(multiple inheritance)的某些特性弊添。
注意:開發(fā)者無法操作經(jīng)由這一步所轉(zhuǎn)發(fā)的消息录淡。若是想在發(fā)送給備援接收者之前先修改消息內(nèi)容,那就得通過完整的消息轉(zhuǎn)發(fā)機制來做了
4. 完整的消息轉(zhuǎn)發(fā)
若沒有“備援的接收者”油坝,則啟動完整的消息轉(zhuǎn)發(fā)機制嫉戚,運行時系統(tǒng)會把與尚未處理的那條消息有關(guān)的全部細節(jié)都封裝到NSInvocation對象中。在觸發(fā)NSInvocation對象時澈圈,“消息派發(fā)系統(tǒng)”(message-dispatch system)將親自出馬彬檀,把消息指派給目標對象。如下:
- (void)forwardInvocation:(NSInvocation*)invacation
此方法比較有用的實現(xiàn)方式為:在觸發(fā)消息前瞬女,先以某種方式改變消息內(nèi)容窍帝,比如追加另外一個參數(shù),或是改換選擇器诽偷,等等坤学。
5. 以完整的例子演示動態(tài)方法解析
EOCAutoDictionary.h
#import <Foundation/Foundation.h>
@interface EOCAutoDictionary : NSObject
@property(nonatomic,strong) NSString *string;
@property(nonatomic,strong) NSNumber *number;
@property(nonatomic,strong) NSDate *date;
@property(nonatomic,strong) id opaqueObject;
@end
EOCAutoDictionary.m
#import "EOCAutoDictionary.h"
#import <objc/runtime.h>
@interface EOCAutoDictionary ()
@property(nonatomic,strong) NSMutableDictionary *backingStore;
@end
@implementation EOCAutoDictionary
// @dynamic會阻止編譯器自動生成相關(guān)的存取方法,而由開發(fā)者自己創(chuàng)建存取方法
@dynamic string, number, date, opaqueObject;
- (id)init{
if (self = [super init]) {
_backingStore = [NSMutableDictionary new]; // 延遲加載
}
return self;
}
// 動態(tài)添加新方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self,
sel,
(IMP)autoDictionarySetter,
"v@:@");
}else{
class_addMethod(self,
sel,
(IMP)autoDictionaryGetter,
"@@:");
}
return YES;
}
// getter函數(shù)
id autoDictionaryGetter(id self, SEL _cmd){
// 從EOCAutoDictionary對象獲取backingStore字典
EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
// 將選擇器轉(zhuǎn)換為字符串报慕,并將其設(shè)為key
NSString *key = NSStringFromSelector(_cmd);
// 返回backingStore字典中key所對應(yīng)的值
return [backingStore objectForKey:key];
}
// setter函數(shù)
void autoDictionarySetter(id self, SEL _cmd, id value){
// 從EOCAutoDictionary對象獲取backingStore字典
EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
// 將選擇器轉(zhuǎn)換為字符串深浮,并將其拷貝為可變字符串
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
// 移除key中尾部的“:”
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
// 移除key中前面的“set”
[key deleteCharactersInRange:NSMakeRange(0, 3)];
// 取出現(xiàn)有的key中的首字母,將其小寫化并替代掉原來的首字母
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
// 根據(jù)key給backingStore存儲相關(guān)的值
if (value) {
[backingStore setObject:value forKey:key];
}else{
[backingStore removeObjectForKey:key];
}
}
@end
main函數(shù):
int main(int argc, const char * argv[]) {
@autoreleasepool {
EOCAutoDictionary *autoDict = [EOCAutoDictionary new];
// autoDict.date == [autoDict setDate]
// 由于接收者沒有相應(yīng)的方法可調(diào)用卖子,因為@dynamic特性略号,所以可以動態(tài)新增方法
autoDict.date = [NSDate dateWithTimeIntervalSince1970:3140907998];
NSLog(@"%@",autoDict.date);
}
return 0;
}
輸出結(jié)果為:
2018-08-16 15:29:25.552
2071-09-13 02:26:38 +0000
總結(jié):要想添加新屬性,只需要用@property來定義洋闽,并將其聲明為@dynamic即可玄柠。
要點
- 若對象無法響應(yīng)某個選擇器,則進入消息轉(zhuǎn)發(fā)流程诫舅。
- 通過運行期的動態(tài)方法解析功能羽利,我們可以在需要用到某個方法時再將其加入類中。
- 對象可以把其無法解讀的某些選擇器轉(zhuǎn)交給其他對象(備援接收者)來處理刊懈。
- 經(jīng)過上述兩步之后这弧,如果還是沒辦法處理選擇器娃闲,那就啟動完整的消息轉(zhuǎn)發(fā)機制。