如何在iOS - OC中实现多代理模式
OC 如何实现多代理模式
为什么要使用多代理模式
虽然标题是如何实现多代理模式,但是知道为什么需要实现多代理模式同样重要。
众所周知,OC常见的消息传递方式有很多,各有各的优势,在不同的场景下要选择不同的实现方式。例如:
1对1试剂,高耦合
一对多通知,松散耦合
街区
KVO
.
不同的实现方法有不同的应用场景,也有各自的优缺点。普通代理模式只能应用于1对1场景,对于1对多场景,只能强制选择使用通知。
但是通知也有自己的缺点:
它不会在编译期间检查观察者是否可以正确处理通知;
释放通知的观察者时,需要移除通知中心的观察者;
调试时,通知传输的过程难以控制和跟踪;
发送和接收通知时,需要提前知道通知名称。如果通知名称不一致,将会出现不同步的情况。
通知发出后,你无法从观察者那里得到任何反馈。没有办法处理需要返回值的场景。
如果代理模式可以支持多个响应对象,那么上述问题就不会再发生了。
如何实现多代理模式
单代理模式
最常见的代理模式之一如下:
协议报告代表:
@协议报告委托n对象
//提交报告
-(无效)报告;
@end
命令的发送者科曼达类
#import 'ReportDelegate.h '
@接口ComandA :对象
@property(弱、非原子)id ReportDelegate委托;
/**
发送提交报告的订单。
*/
-(void)send order;
@end
@实现ComandA
-(无效)发送订单
{
if(self . delegate[self . delegate response stoselector : @ selector(report)]
{
[self.delegate报告];
}
}
@end
类ExecutorB,命令的执行者
#import 'ReportDelegate.h '
@接口执行器:对象报告委托
@end
@实现执行器b
-(无效)报告
{
NSLog(@ '我想交报告');
}
@end
现在一个ComandA对象A可以命令一个ExecutorB对象B提交一个报告。ComandA只定义了单个成员@ property(弱的、非原子的)id报告委托委托;
最初的多代理模式
现在,如果您将委托更改为id ReportDelegate委托的数组委托。最好遍历sendOrder方法中的委托数组来调用每个委托执行代理方法。如下所示:
@接口ComandA :对象
@property(强,非原子)NSPointerArray *
delegates;
/**
发送上交报告的命令
*/
- (void)sendOrder;
@end
@implementation ComandA2
- (void)sendOrder
{
for (NSUInteger i = 0; i self.delegates.count; i += 1) {
id delegate = (__bridge id)[self.delegates pointerAtIndex:i];
if(delegate [delegate respondsToSelector:@selector(report)])
{
[delegate report];
}
}
}
@end
多代理就这样实现了,现在一个ComandA
对象A可以命令多个ExecutorB
对象B上交报告,只要提前将多个ExecutorB
对象加入到delegates
数组中即可。 之所以选择NSPointerArray
,是因为NSPointerArray
不增加成员的引用计数,相当于弱引用,在释放一个delegate
前,就算不将其从delegates
数组中移除也不会有问题。
一切看起来非常完美,对的,只是看起来非常完美。再深入的思考或实践一下,就会发现这个方式运用起来多么麻烦,哪怕更多的优化也不可避免。有兴趣的可以下载这个。
pod 'MultiDelegateOC', '0.0.1'
代理协议中的每个方法都要主动遍历调用每个代理对象。我们自己新建的类还好,如果我们需要将第三方库的类变为多代理,想想那么多的代理方法需要改动。倘若第三方库的类新增了部分代理方法,我们也要相应的添加。
如果不想修改第三方库的代码,怎么办,难道要在外面再封装一层吗想想以后的维护工作就让人头疼。
进阶的多代理
于是寻找进阶的多代理方式已不得不做,幸好万能的github有很多大牛,我们只需要站在他们的肩膀上就好了。
最初的多代理模式之所以有上述的问题,是因为我们让ComandA
直接管理delegates
数组,这样必然会对原有代码进行改动。
如果我们新建一个类MultiDelegateOC
代替ComandA
管理delegates
数组,只需要将ComandA
的@property (weak, nonatomic) id ReportDelegate delegate
设置为MultiDelegateOC
对象不就好了。
这样原来的ComandA
不需要任何改动就能继续使用多代理了。我们只需要在MultiDelegateOC
内部实现遍历调用就好了。
如果我们理解OC的方法执行——消息转发机制就很容易实现了。我们只需要截获MultiDelegateOC
的方法执行,将其变为遍历执行就可以了。
这里需要重写NSObject
的两个方法methodSignatureForSelector:
和forwardInvocation:
。
methodSignatureForSelector:
原型:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行。如果当前类没有实现这个函数导致返回值为nil,程序就会crash--未实现的函数。
forwardInvocation:
原型:
- (void)forwardInvocation:(NSInvocation *)anInvocation
函数的真正执行者,在这个方法中,我们可以从NSInvocation对象中截获selector,参数,可以设置selector的调用者,真正的遍历delegates
数组去执行就完全没有问题了。
//重写respondsToSelector方法,让`ComandA`类真实判断。
- (BOOL)respondsToSelector:(SEL)selector
{
if ([super respondsToSelector:selector])
{
return YES;
}
for (id delegate in self.delegates)
{
if (delegate [delegate respondsToSelector:selector])
{
return YES;
}
}
return NO;
}
//防止崩溃,生成函数签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (signature)
{
return signature;
}
[self.delegates compact];
if (self.silentWhenEmpty self.delegates.count == 0)
{
// return any method signature, it doesn't really matter
return [self methodSignatureForSelector:@selector(description)];
}
for (id delegate in self.delegates)
{
if (!delegate)
{
continue;
}
signature = [delegate methodSignatureForSelector:selector];
if (signature)
{
break;
}
}
return signature;
}
//遍历`delegates`数组调用代理方法
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL selector = [invocation selector];
BOOL responded = NO;
NSArray *copiedDelegates = [self.delegates copy];
for (id delegate in copiedDelegates)
{
if (delegate [delegate respondsToSelector:selector])
{
[invocation invokeWithTarget:delegate];
responded = YES;
}
}
if (!responded !self.silentWhenEmpty)
{
[self doesNotRecognizeSelector:selector];
}
}
一个进阶版的多代理模式就完成,现在我们只需要主动生成一个MultiDelegateOC
对象管理多代理就可以了。
有兴趣的可以下载这个。
pod 'MultiDelegateOC', '0.0.2'
不过现在还不是很完美,如果代理协议中有返回值的情况,我们并没有处理。再给- (void)forwardInvocation:
方法添点料就好了:
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL selector = [invocation selector];
BOOL responded = NO;
NSArray *copiedDelegates = [self.delegates copy];
void *returnValue = NULL;
for (id delegate in copiedDelegates)
{
if (delegate [delegate respondsToSelector:selector])
{
[invocation invokeWithTarget:delegate];
if(invocation.methodSignature.methodReturnLength != 0)
{
void *value = nil;
[invocation getReturnValue:value];
if(value)
{
returnValue = value;
}
}
responded = YES;
}
}
if(returnValue)
{
[invocation setReturnValue:returnValue];
}
if (!responded !self.silentWhenEmpty)
{
[self doesNotRecognizeSelector:selector];
}
}
如果多个代理对象都有返回值,最终返回将是最后加入的代理的返回值。当然NSPointerArray可以调整成员的顺序,你也可以自己设置判断条件来选择返回值。
总结
以上是我自己在实现多代理模式的历程,很多方法都是使用网络上大神的成熟经验,在不断的使用实践踩坑中,逐步完善出来。
包括最初的多代理模式,我也使用了很长时间,后来实在觉得太麻烦。逼不得已从网上找到第二种方式,觉得挺好用。
后来测试发现总会出现莫名其妙的异常,一时之间找不到原因,不得已又切换到了第一种方式。
后来闲的时候灵光一闪,发现第二种方式有异常的情况都是在代理方法有返回值的情况下出现。知道问题原因,解决起来就简单多了。
现在我一直使用第二种代理模式,至今没有出现过问题。
多代理模式我一般是与单例配合使用。使用多代理的地方还不少,比如高德地图SDK。
Demo地址
MultiDelegateOC Demo
Pod引用
pod 'MultiDelegateOC'
高德地图Demo
作者:FlameGrace
链接:https://www.jianshu.com/p/fed580fa45eb
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/136284.html