在Objective-C中,方法會在運行時轉換成一個消息函數的的調用,即objc_msgSend。其基本形式是objc_msgSend(receiver, selector, arg1, arg2, ...)。第一個參數是消息接受者,第二個參數是一個SEL類型的數據,其余的為消息的參數。
等等,如果您對基本方法基本的概念還不太清晰的話,請參閱一下另一篇文章。如果有了基本的概念性基礎後,我們來進行流程的解析。
定義一個男人類,裡面沒有多余的代碼。
@interface Man : NSObject @end @implementation Man @end
1. 如果selector是一些內存管理相關的消息(如retain,release,retainCount等),則直接返回接收者。
2. 如果沒有接受者則返回為空(nil,0,或者{0})
3. 接下來接受者會在對象結構體中緩存的方法鏈表中尋找selector,如果尋找到就進行方法的調用。如果沒有找到,則繼續在所有的方法鏈表中尋找,如果尋找到了,則進行方法的調用,並且把這個selector存在緩存鏈表中。假如在對象的結構體中沒有尋找到這個selector,則會一級一級的在父類的方法鏈表中尋找,直到NSObject停止。
這些流程和大部分的語言相似,但是Objective-C並不止如此,它還會進行更多的檢測和操作,從而進一步挽救大家“脆弱”的程序。
4. 挽救第一步:利用resolveInstanceMethod:進行自救<喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPqOto62jrTxzdHJvbmc+tq/MrLe9t6i94s72PC9zdHJvbmc+PC9wPgo8cD682cno1eK49sTQyMu7ucvjx9rAzaOs19S8urvh1/a3uaOsy/nS1Lao0uXSu7j21/a3ubXEt723qKGj1NpNYW4uaNbQvNPI68jnz8K3vbeoPC9wPgo8cD48L3A+CjxwcmUgY2xhc3M9"brush:java;">- (NSString *)makeSomeFood:(NSString *)food; //做飯 但是在Man.h中不去實現它,因為他不知道今天到底有沒有肉,如果有肉就做肉吃,沒有就做大白菜吃。在Man.m中加入如下代碼
5. 挽救第二步:利用forwardingTargetForSelector:進行求助---備用接受者 假設這個男人想生一個孩子,他必然得求助他老婆啦,當然前提有願意替他生孩子的老婆。 定義Wife類 當調用giveBirthToBaby:時,會執行第四步去檢查自己能不能自力更生解決問題,當沒法解決的時候會讓_mWife去實現相應的方法,從表面上看時Man去實現了這個方法。 這裡需要注意一點的是:由於是讓新對象去執行這個方法,一個必要的流程也是如果這個新對象也沒有實現方法,就會去父類中找實現方法,如果找到了就調用並存儲到緩存鏈表中,沒有找到則沒法實現會拋出異常,繼續接下來的拯救方法。 6. 挽救第三步:利用forwardInVocation:進行外包---完整消息轉發假設這個男人現在想要制造一個大飛機,雖然是土豪,但是最多也只能是外包給朋友去解決了。 定義能解決問題的土豪朋友類: 如果這一步也沒有進行消息的處理,NSObject 的forwardInVocation:就會調用doesNotRecognizeSelector:方法,程序就崩潰啦。 最後上圖:static id makeVegetable (id self, SEL _cmd, id value)
{
NSString *intentedFood = (NSString *)value;
return [NSString stringWithFormat:@"本來打算做%@吃,現在只有做大白菜吃了",intentedFood];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSString *selStr = NSStringFromSelector(sel);
if ([selStr isEqualToString:@"makeSomeFood:"]) {
class_addMethod([Man class], sel, (IMP)makeVegetable, "v@:@");
}
return [super resolveInstanceMethod:sel];
}
resolveInstanceMethod:方法中先判斷方法名稱,如果是makeSomeFood:方法,則在運行時動態的添加一個makeVegetable方法去進行方法處理並放回結果。class_addMethod的第一個參數是類名,第二個參數是方法名稱,第三個是函數實現地址,第四個參數是類型編碼參數。
@interface Wife : NSObject
@end
@implementation Wife
- (void)giveBirthToBaby
{
NSLog(@"生孩子啦");
}
@end
//Man.h中添加方法
@property (nonatomic, retain) Wife *mWife;
- (void)giveBirthToBaby; //生孩子
//Man.m中實現
- (id)forwardingTargetForSelector:(SEL)aSelector{
if ([_mWife respondsToSelector:aSelector]) {
return _mWife;
}
return [super forwardingTargetForSelector:aSelector];
}
//初始化的時候
Man *people = [[Man alloc] init];
people.mWife = Wife.new;
[people giveBirthToBaby];
@interface RichFriend : NSObject
@end
@implementation RichFriend
- (void)makeSomeAirplane:(NSString *)airPlaine
{
NSLog(@"%@",[NSString stringWithFormat:@"制造%@成功",airPlaine]);
}
@end
//Man.h中添加方法
- (void)makeSomeAirplane:(NSString *)airPlaine; //造飛機
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
if (!sig) {
if ([RichFriend instancesRespondToSelector:aSelector]) {
sig = [RichFriend instanceMethodSignatureForSelector:aSelector];
}
}
return sig;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
if ([RichFriend instancesRespondToSelector:anInvocation.selector])
{
[anInvocation invokeWithTarget:RichFriend.new];
} else {
[super forwardInvocation:anInvocation];
}
}
//初始化
Man *people = [[Man alloc] init];
[people makeSomeAirplane:@"A380"];
如果第四和第五部都沒法解決問題,那麼只能是最後一步進行NSInVocation進行轉發,NSInvocation是一個包含receiver,selector,parameter 等的一個封裝類,把所有相關的信息進行打包處理。首先需要通過methodSignatureForSelector:方法獲得selector的方法簽名建立NSInvocation對象,這個對象可以通過invokeWithTarget:方法驅動新的接受者去進行消息的處理。