本文是投稿文章,作者:kingizz(博客)

一、OC的消息機制
那麼,怎樣實現delegate方法的動態轉發呢?這需要用到Objective-C的消息機制,我們都知道,在OC中,調用一個對象的方法,實際上是給對象發了一條消息,在編譯Objective-C函數調用的語法時,會被翻譯成一個C的函數調用:objc_msgSend(),例如:
[array insertObject:foo atIndex:2]; //會被翻譯成: objc_msgSend(array, @selector(insertObject:atIndex), foo, 2);
那麼,objc_msgSend又做了哪些事呢?,以[object foo]為例:
通過object的isa指針找到它的class
在class的method_list中找到foo
如果class中沒找到foo,則繼續往他的superclass中查找
一旦找到foo這個函數,就去執行對應的方法實現(IMP)
如果一直沒有找到foo,OC的runtime將繼續下面的步驟:
二、動態方法決議與消息轉發
在Objective-C中,如果向一個對象發送一條該對象無法處理的消息(對應selector不存在),會導致程序crash, 但是,在crash之前,oc的運行時系統會先經過以下兩個步驟:
Dynamic Method Resolution
Message Forwarding
1、Dynamic Method Resolution(動態方法決議)
首先,如果調用的方法是實例方法,OC的運行時會調用- (BOOL)resolveInstanceMethod:(SEL)sel,如果是類方法,則會調用+ (BOOL)resolveClassMethod:(SEL)sel 讓我們可以在程序運行時動態的為一個selector提供實現,如果我們添加了函數的實現,並返回YES,運行時系統會重啟一次消息的發送過程,調用動態添加的方法。例如,下面的例子:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(foo)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "V@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicMethodIMP(id self, SEL _cmd){
NSLog(@"%s", __PRETTY_FUNCTION__);
}class_addMethod 方法動態的添加新的方法與對應的實現,如果調用了[Foo foo],將會轉到動態添加的dynamicMethodIMP 方法中。Objective-C的方法本質上是一個至少包含兩個參數(id self, SEL _cmd)的C函數,這樣,當重啟消息發送時,就能在類中找到@selector(foo)了。而如果方法返回NO時,將會進入下一步:消息轉發(Message Forwarding)
2、Message Forwarding(消息轉發)
消息轉發分為兩步:
首先運行時系統會調用- (id)forwardingTargetForSelector:(SEL)aSelector方法,如果這個方法中返回的不是nil或者self,運行時系統將把消息發送給返回的那個對象
如果- (id)forwardingTargetForSelector:(SEL)aSelector返回的是nil或者self,運行時系統首先會調用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector方法來獲得方法簽名,方法簽名記錄了方法的參數和返回值的信息,如果-methodSignatureForSelector 返回的是nil, 運行時系統會拋出unrecognized selector exception,程序到這裡就結束了。
整個流程可以用下面這張圖表示

三、實現多重代理
結合上面的流程分析,我麼可以發現,要實現多重代理的分發,我們需要讓Runtime系統運行到forwardInvocation這一步,並在該方法中將delegate方法分發到其他各個對象中去:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
for (id target in self.allDelegates) {
if ((signature = [target methodSignatureForSelector:aSelector])) {
break;
}
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
for (id target in self.allDelegates) {
if ([target respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:target];
}
}
}由於我們調用delegate的方法時,一般會先調用[delegate responseToSelector]方法,所以,我們還需要實現這個方法:
- (BOOL)respondsToSelector:(SEL)aSelector{
if ([super respondsToSelector:aSelector]) {
return YES;
}
for (id target in self.allDelegates) {
if ([target respondsToSelector:aSelector]) {
return YES;
}
}
return NO;
}
@end然後我們來測試一下,新建一個ScrollDelegate類,實現UIScrollViewDelegate方法:
#import "ScrollDelegate.h"
@implementation ScrollDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(@"%s", __PRETTY_FUNCTION__);
}
@end然後再新建一個ViewController,也實現UIScrollViewDelegate方法,添加一個UIScrollView在controller的view中,然後設置scrollView的delegate為multipleProxy:
#import "ViewController.h"
#import "MultipleDelegate.h"
#import "ScrollDelegate.h"
@interface ViewController ()@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@end
@implementation ViewController{
MultipleDelegate *_multipleDelegate;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height * 2);
_multipleDelegate = [MultipleDelegate new];
//添加要處理delegate方法的對象
NSArray *array = @[self, [ScrollDelegate new]];
_multipleDelegate.allDelegates = array;
self.scrollView.delegate = (id)_multipleDelegate;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSLog(@"%s", __PRETTY_FUNCTION__);
}
@end運行,滑動scrollView,看看控制台打印的信息:
2015-11-01 11:07:49.199 MultipleDelegateDemo[4732:498520] -[ViewController scrollViewDidScroll:] 2015-11-01 11:07:49.200 MultipleDelegateDemo[4732:498520] -[ScrollDelegate scrollViewDidScroll:] 2015-11-01 11:07:49.227 MultipleDelegateDemo[4732:498520] -[ViewController scrollViewDidScroll:] 2015-11-01 11:07:49.227 MultipleDelegateDemo[4732:498520] -[ScrollDelegate scrollViewDidScroll:]
很好,deegate方法已經被正確地轉發給了兩個對象了,看起來好像沒什麼不對,可是,細心的你一定會發現,這裡存在retain cycle:controller -> _multipleDelegate -> controller,那麼怎樣解決這個問題呢?
四、NSPointerArray防止循環引用
因為NSArray會對對象進行retain操作,導致循環引用的產生,所以我們可以用NSPointerArray來解決這個問題,但是需要注意對於其他的delegate對象也需要在controller中對其強引用, 最終MultipleDelegateProxy的實現:
#import "KIZMultipleDelegateProxy.h"
@interface KIZMultipleDelegateProxy ()
@property (nonatomic, strong) NSPointerArray *weakRefTargets;
@end
@implementation KIZMultipleDelegateProxy
- (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五、小記
利用這個多重代理動態轉發,我封裝了一些獨立的delegate實現的小功能,比如本文開頭提到的TableView頭部圖片拉伸效果,放在github上:https://github.com/zziking/KIZBehavior