消息轉(zhuǎn)發(fā)以及動態(tài)解析方法
消息轉(zhuǎn)發(fā)機制基本上分為三個步驟:
- 動態(tài)方法解析
- 備用接收者
- 完整轉(zhuǎn)發(fā)
首先匠襟,對于動態(tài)方法解析,NSObject提供了以下兩個方法來處理:
+ (BOOL)resolveClassMethod:(SEL)name
+ (BOOL)resolveInstanceMethod:(SEL)name
從方法名我們可以看出泊业,resolveClassMethod:是用于動態(tài)解析一個類方法蟆湖;而resolveInstanceMethod:是用于動態(tài)解析一個實例方法肤无。
我們知道牙勘,一個Objective-C方法是其實是一個C函數(shù),它至少帶有兩個參數(shù)揩尸,即self和_cmd蛹屿。我們使用class_addMethod函數(shù),可以給類添加一個方法疲酌。我們以resolveInstanceMethod:為例蜡峰,如果要給對象動態(tài)添加一個實例方法,則可以如下處理:
void dynamicMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
備用接收者
NSObject提供了以下方法來處理:
- (id)forwardingTargetForSelector:(SEL)aSelector
該方法返回未被接收消息最先被轉(zhuǎn)發(fā)到的對象朗恳。如果一個對象實現(xiàn)了這個方法湿颅,并返回一個非空的對象(且非對象本身),則這個被返回的對象成為消息的新接收者粥诫。另外如果在非根類里面實現(xiàn)這個方法油航,如果對于給定的selector,我們沒有可用的對象可以返回怀浆,則應(yīng)該調(diào)用父類的方法實現(xiàn)谊囚,并返回其結(jié)果
完整轉(zhuǎn)發(fā)
如果備用方法搞不定,需要包裝成一個NSInvocation對象, 在這里要先返回一個方法簽名
該方法的聲明如下:
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
這個方法返回包含方法描述信息的NSMethodSignature對象执赡,如果找不到方法镰踏,則返回nil。如果我們的對象包含一個代理或者對象能夠處理它沒有直接實現(xiàn)的消息沙合,則我們需要重寫這個方法來返回一個合適的方法簽名奠伪。
對應(yīng)于實例方法,當(dāng)然還有一個處理類方法的相應(yīng)方法首懈,其聲明如下:
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
另外绊率,NSObject類提供了兩個方法來獲取一個selector對應(yīng)的方法實現(xiàn)的地址,如下所示:
- (IMP)methodForSelector:(SEL)aSelector
+ (IMP)instanceMethodForSelector:(SEL)aSelector
獲取到了方法實現(xiàn)的地址究履,我們就可以直接將IMP以函數(shù)形式來調(diào)用滤否。
對于methodForSelector:方法,如果接收者是一個對象最仑,則aSelector應(yīng)該是一個實例方法藐俺;如果接收者是一個類炊甲,則aSelector應(yīng)該是一個類方法。
對于instanceMethodForSelector:方法欲芹,其只是向類對象索取實例方法的實現(xiàn)蜜葱。如果接收者的實例無法響應(yīng)aSelector消息,則產(chǎn)生一個錯誤耀石。
最后,對于完整轉(zhuǎn)發(fā)爸黄,NSObject提供了以下方法來處理
- (void)forwardInvocation:(NSInvocation *)anInvocation
當(dāng)前面兩步都無法處理消息時滞伟,運行時系統(tǒng)便會給接收者最后一個機會,將其轉(zhuǎn)發(fā)給其它代理對象來處理炕贵。這主要是通過創(chuàng)建一個表示消息的NSInvocation對象并將這個對象當(dāng)作參數(shù)傳遞給forwardInvocation:方法梆奈。我們在forwardInvocation:方法中可以選擇將消息轉(zhuǎn)發(fā)給其它對象。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSString *selStr = NSStringFromSelector(anInvocation.selector);
if ([selStr isEqualToString:@"deptName"]) {
[anInvocation setTarget:self.companyModel];
[anInvocation setSelector:@selector(deptName:)];
BOOL hasCompanyName = YES;
//第一個和第一個參數(shù)是target和sel
[anInvocation setArgument:&hasCompanyName atIndex:2];
[anInvocation retainArguments];
[anInvocation invoke];
}else{
[super forwardInvocation:anInvocation];
}
}
在開發(fā)中經(jīng)常遇到一種錯誤称开,就是unrecognized selector sent to instance *** 但是針對未識別方法的崩潰處理亩钟。關(guān)鍵就是方法轉(zhuǎn)發(fā)
#import "NSObject+UnRecognizedSelHandler.h"
#import <objc/runtime.h>
//提示框--->UIAlertController
#define ALERT_VIEW(Title,Message,Controller) {UIAlertAction *action = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:nil];UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:Title message:Message preferredStyle:UIAlertControllerStyleAlert]; [alertVc addAction:action];[Controller presentViewController:alertVc animated:YES completion:nil];}
#import "AppDelegate.h"
static NSString *_errorFunctionName;
void dynamicMethodIMP(id self,SEL _cmd){
#ifdef DEBUG
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
UIViewController *currentRootViewController = delegate.window.rootViewController;
NSString *error = [NSString stringWithFormat:@"errorClass->:%@\n errorFuction->%@\n errorReason->UnRecognized Selector",NSStringFromClass([self class]),_errorFunctionName];
ALERT_VIEW(@"程序異常",error,currentRootViewController);
#else
//upload error
#endif
}
#pragma mark 方法調(diào)換
static inline void change_method(Class _originalClass ,SEL _originalSel,Class _newClass ,SEL _newSel){
Method methodOriginal = class_getInstanceMethod(_originalClass, _originalSel);
Method methodNew = class_getInstanceMethod(_newClass, _newSel);
method_exchangeImplementations(methodOriginal, methodNew);
}
@implementation NSObject (UnRecognizedSelHandler)
+ (void)load{
change_method([self class], @selector(methodSignatureForSelector:), [self class], @selector(SH_methodSignatureForSelector:));
change_method([self class], @selector(forwardInvocation:), [self class], @selector(SH_forwardInvocation:));
}
- (NSMethodSignature *)SH_methodSignatureForSelector:(SEL)aSelector{
if (![self respondsToSelector:aSelector]) {
_errorFunctionName = NSStringFromSelector(aSelector);
NSMethodSignature *methodSignature = [self SH_methodSignatureForSelector:aSelector];
if (class_addMethod([self class], aSelector, (IMP)dynamicMethodIMP, "v@:")) {
NSLog(@"臨時方法添加成功!");
}
if (!methodSignature) {
methodSignature = [self SH_methodSignatureForSelector:aSelector];
}
return methodSignature;
}else{
return [self SH_methodSignatureForSelector:aSelector];
}
}
- (void)SH_forwardInvocation:(NSInvocation *)anInvocation{
SEL selector = [anInvocation selector];
if ([self respondsToSelector:selector]) {
[anInvocation invokeWithTarget:self];
}else{
[self SH_forwardInvocation:anInvocation];
}
}
@end
原理就是運用oc的運行時鳖轰,將系統(tǒng)的轉(zhuǎn)發(fā)方法與自己的方法做替換清酥,從而為這個對象重新綁定一個新的方法,新的方法會在當(dāng)前頁面彈出程序的錯誤信息蕴侣。也可以在這里將錯誤的代碼信息上傳到自己的服務(wù)器中焰轻。