前言ios
其餘編程語言所說的函數調用,在oc中被稱做爲發送消息;消息轉發的做用,開發者能夠在找不到的方法的狀況下,能夠經過動態添加方法或者是消息轉發,肯定本次發送消息是否成功,經過這樣的特性開發者能夠作不少必要的善後處理。編程
ios消息轉發的做用;app
(1)對象發送了未實現的消息,能夠經過消息轉發機制,轉移到其餘對象去處理該消息;編程語言
(2)除了協議、類別,也能夠經過消息轉發機制實現多繼承;函數
實現ios消息轉發前的準備工做;atom
(1)爲了配合下面的實例預先定義好SleepViewController,實現sleep和eat方法;代理
#import "SleepViewController.h" @interface SleepViewController () @end @implementation SleepViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ - (void)sleep { NSLog(@"excute SleepViewController sleep"); } - (void)eat { NSLog(@"ok,begin eat"); } @end
ios消息轉發的過程;code
(1)動態方法解析,好比執行[self performSelector:@selector(name)];在本類父類以及NSObject都未能查找到該方法,就是先判斷是否須要動態添加該方法,本例運用runtime動態添加了testClassName方法,執行結果會輸出"add testClassName method";orm
+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(name)) { class_addMethod(self, sel, class_getMethodImplementation([self class], @selector(testClassName:)), method_getTypeEncoding(class_getInstanceMethod([self class], @selector(testClassName:)))); return YES; }else if (sel == @selector(sleep)){ return NO; } return [super resolveInstanceMethod:sel]; } - (void)testClassName:(NSString *)string { NSLog(@"add testClassName method"); }
(2)若是(BOOL)resolveInstanceMethod:(SEL)sel或者(BOOL)resolveClassMethod:(SEL)sel返回NO,消息轉發就會進入轉發機制;好比執行[self performSelector:@selector(sleep)],這個sleep方法在SleepViewController聲明並實現,結果輸出"excute SleepViewController sleep";對象
- (id)forwardingTargetForSelector:(SEL)aSelector { NSLog(@"not found method sleep"); if (aSelector == @selector(sleep)) { return [[SleepViewController alloc] init]; }else{ NSLog(@"not found eat"); return nil; } }
(3)當重定向還不做處理的時候,這消息轉發機制就會出發,這個時候就會調用- (void)forwardInvocation:(NSInvocation *)anInvocation這個方法;可是在這個方法執行以前會先調用methodSignatureForSelector方法,因此咱們也要複寫這個方法,不然就會報異常;因此咱們要重寫這個方法。好比執行[self performSelector:@selector(eat)];既沒有動態添加該方法,也沒有重定向該方法,因此執行了一下的兩個方法,作最後的邏輯處理;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSLog(@"method name -- %@",NSStringFromSelector(aSelector)); if (aSelector == @selector(eat)) { //簽名方法; return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL sele = [anInvocation selector]; SleepViewController *slee = [[SleepViewController alloc] init]; if ([slee respondsToSelector:sele]) { //轉發到SleepViewController執行eat方法; [anInvocation invokeWithTarget:slee]; }else{ [super forwardInvocation:anInvocation]; } }
解釋:在沒有動態添加方法狀況下,第(2)(3)步,都是將處理消息的操做,轉移給了其餘的對象。區別就是在第(2)步轉發,只能轉發一個指定的對象;在第(3)步的話,能夠轉發多個對象。
好玩的東西:利用oc消息轉發,實現多重delegate代理;
你們都比較清楚,在一般狀況下,delegate只能對象之間是一對一通訊的,經過上述的消息轉發的分析,在轉發的第(3)步,能夠實現多重代理,即多個委託對象。我先上一段代碼,後面再解讀代碼邏輯;
(1)多重代理的處理類,保存多個委託對象,經過消息轉發將要執行委託函數轉發至其餘委託對象(記住本類不實現任何委託相關的邏輯);
#import "MultipleDelegateProxy.h" @interface MultipleDelegateProxy () @property (nonatomic,strong) NSPointerArray *weakRefTargets; @end @implementation MultipleDelegateProxy - (void)setDelegateTargets:(NSArray *)delegateTargets { self.weakRefTargets = [NSPointerArray weakObjectsPointerArray]; for (id delegate in delegateTargets) { [self.weakRefTargets addPointer:(__bridge void *)delegate]; } } - (BOOL)respondsToSelector:(SEL)aSelector{ if ([super respondsToSelector:aSelector]) { return YES; } for (id target in self.weakRefTargets) { if ([target respondsToSelector:aSelector]) { return YES; } } return NO; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSMethodSignature *sig = [super methodSignatureForSelector:aSelector]; if (!sig) { for (id target in self.weakRefTargets) { if ((sig = [target methodSignatureForSelector:aSelector])) { break; } } } return sig; } //轉發方法調用給全部delegate - (void)forwardInvocation:(NSInvocation *)anInvocation{ for (id target in self.weakRefTargets) { if ([target respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:target]; } } } @end
(2)使用MultipleDelegateProxy,multipleDelegate做爲scrollview的委託對象,multipleDelegate的delegateTargets添加了兩個委託對象self和scrollDelegate,在scrollview滾動的過程當中會經過MultipleDelegateProxy類中重寫的- (BOOL)respondsToSelector:(SEL)aSelector預先判斷multipleDelegate是否實現了相應的委託方法(這裏確定要返回yes的,否則就不會出現消息轉發),找不到委託方法,最後將消息轉發至self和scrollDelegate進行處理,實現多重委託。
@interface MemberCenterViewController () <UIScrollViewDelegate> { MultipleDelegateProxy *multipleDelegate; ScrollDelegate *scrollDelegate; } @property (nonatomic, strong) UIScrollView *scroll; @end @implementation MemberCenterViewController - (void)viewDidLoad { [super viewDidLoad]; self.scroll = [[UIScrollView alloc] initWithFrame:self.view.bounds]; self.scroll.backgroundColor = [UIColor lightGrayColor]; self.scroll.contentSize = CGSizeMake(375, 800); multipleDelegate = [MultipleDelegateProxy new]; scrollDelegate = [ScrollDelegate new]; multipleDelegate.delegateTargets = @[self,scrollDelegate]; _scroll.delegate = multipleDelegate; [self.view addSubview:_scroll]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { NSLog(@"%@",self); } @end
在這裏我解釋一下爲何要重寫MultipleDelegateProxy類中的- (BOOL)respondsToSelector:(SEL)aSelector方法?緣由很簡單,在設置scrollview的delegate屬性後,會判斷delegate是否實現了相應的委託方法,若沒有實現的話,是不會執行這個委託方法(可選的)的;因此咱們須要重寫,邏輯是經過真正會執行委託的兩個委託對象去判斷是否實現的委託方法。
總結
經過消息轉發機制,你能夠看到oc這門語言的獨特之處,增長了不少靈活性。可使用它來作一些特殊值的判斷處理,好比null值等,減小咱們在接收後臺返回數據時返回null對咱們處理邏輯的影響。固然消息轉發的做用不止如此,更多好玩的東西,等待着咱們去發掘。