传统的代理模式只能实现一对多的代理,即每个对象同一时刻只能有一个代理,这就可能导致在某些使用场景下失效,如果对象A在处理某些事情的情况下要将结果同时通知给B和C该怎么办?这时候代理就没法用了,而通知机制虽然能够解决,但它是APP生命周期的管理,如果项目中大量使用通知,必然会不好维护且也不够优雅。这时候可以考虑使用OC强大的运行时机制,利用消息转发实现多代理。

PS: 如果还有不了解消息转发的童鞋可以先看看这篇文章:初步了解消息转发 ,我就不继续复制粘贴造轮子了。

先看效果

图1
图1

图中GreenView为一个scrollview,OrangeView和BlueView为普通的UIView,他们都是ViewController的属性,现在我要实现的目标是,当 滚动scrollview的时候它的代理(OrangeVIew,BlueView和ViewController)都会同时实现各自的代理方法scrollViewWillBeginDraggingscrollViewWillEndDragging

思路

经过上面提供链接里面的内容我们可以大体了解到消息转发具体是怎么回事,过程如下图:

消息转发示意图
消息转发示意图

因此我们可以利用图示消息转发的过程实现多代理。

首先

    _myScrolView.contentSize = self.view.bounds.size;
    NSArray *array = @[self,self.orange,self.blue];
    _delegateTargets = [[DelegateShareManager alloc] init];
    _delegateTargets.delegateTargets = array;
    _myScrolView.delegate = (id)_delegateTargets;

我们需要一个代理分发者,我们叫他DelegateShareManager,他的作用是管理scrollview的所有代理者,_delegateTargets是他的一个实例,在这里个实例内部有一个array拥有着那些代理们。最后让_myScrolView.delegate = (id)_delegateTargets,这样原本的scrollview的单一代理就可以通过 这种方式变为多个代理。

然后

DelegateShareManager内部通过下面代码将scrollview的需要唤起的代理信息发送出去

- (void)setDelegateTargets:(NSArray *)delegateTargets{
    self.weakTargets = [NSPointerArray weakObjectsPointerArray];
    for (id delegate in delegateTargets) {
        [self.weakTargets addPointer:(__bridge void*)delegate];
    }
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return nil;
}


- (BOOL)respondsToSelector:(SEL)aSelector{
    if ([super respondsToSelector:aSelector]) {
        return YES;
    }
    for (id target in self.weakTargets) {
        if ([target respondsToSelector:aSelector]) {
            return YES;
        }
    }
    return NO;
}


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
    if (!sig) {
        for (id target in self.weakTargets) {
            if ((sig = [target methodSignatureForSelector:aSelector])) {
                break;
            }
        }
    }
    return sig;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    for (id target in self.weakTargets) {
        if ([target respondsToSelector:anInvocation.selector]) {
            [anInvocation invokeWithTarget:target];
        }
    }
}

上面代码可以理解为,通过查找weakTargets里面的单一代理元素(如orangeView),将消息转发出去

最后

在OrangeView,ViewController和BlueView里面分别具体实现代理方法scrollViewWillBeginDraggingscrollViewWillEndDragging即能实现多代理效果。

演示

拖动绿色的scrollview,可以看到控制台输出

控制台输出
控制台输出

Demo

这里有个小demo:链接,如果觉得对你有帮助,给个star吧