iOS如何实现多代理模式–OC

技术iOS如何实现多代理模式–OC iOS如何实现多代理模式–OCOC 如何实现多代理模式
为什么要使用多代理模式
标题虽然是如何实现多代理模式,但是知道为什么需要实现多代理模式同样重要。
众所周知

如何在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

(1)

相关推荐

  • elasticsearch推荐书籍(elasticsearch统计有多少字数)

    技术将唐诗三百首写入 Elasticsearch 会发生什么本篇文章给大家分享的是有关将唐诗三百首写入 Elasticsearch 会发生什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,

    攻略 2021年12月16日
  • Does RSA Private key always contain the Public key, or is it just .NET

    技术Does RSA Private key always contain the Public key, or is it just .NET Does RSA Private key always

    礼包 2021年12月9日
  • php获取文件mime类型的几种常用方法

    技术php获取文件mime类型的几种常用方法 php获取文件mime类型的几种常用方法说明
    这些是在文件上传必备的一些验证函数,文件上传这一块要是不做好安全防护,肯定被人登录后台拿你源码的
    MIME类型

    礼包 2021年12月14日
  • 周记一个星期发生的事,周记是写一周发生的事情吗

    技术周记一个星期发生的事,周记是写一周发生的事情吗这一周我过得非常开心周记一个星期发生的事,因为我干了许许多多有趣的事情,还学了很多新知识,掌握了很多新本领。星期三晚上,我们在风雨球场看了一场精彩的英语歌曲大赛。小学部的

    生活 2021年10月24日
  • mysql存储过程中游标怎么用

    技术mysql存储过程中游标怎么用这篇文章将为大家详细讲解有关mysql存储过程中游标怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。DELIMITER $$USE `cms`$$

    攻略 2021年11月2日
  • C/C++指针知识点有哪些

    技术C/C++指针知识点有哪些本篇内容介绍了“C/C++指针知识点有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!基

    攻略 2021年11月30日