类与类之间的通信我们有很多种方式,iOS中有代理,通知,block,单例类等等,每种方式都有其适用的场景
需求举例:
假设委托者皇上发起一个委托事件 要吃饭,这个事件的参数是今天要吃红烧肉,水煮鱼,肉末茄子,最终做饭这件事会被代理者实施,厨师甲做红烧肉,厨师乙做水煮鱼,厨师丙做肉末茄子
在iOS开发中面对上面这个需求,我们肯定能想到用通知模式来实现这个逻辑。其实更好的做法是使用多播代理模式
- 用通知的方式实现:用大喇叭广播:“皇上要吃饭了,并且要吃红烧肉,水煮鱼,肉末茄子”,虽然厨师甲乙丙听到之后就会开始去做给皇上做菜,但是这广播出去全城的人都知道了,这种消息传递方式会造成消息外露,不受控制;
- 用多播代理的方式实现:皇上通过吃饭总管告诉厨师甲乙丙它要吃饭了,甲乙丙收到消息后就去给皇上做菜了,这种消息传递很精准,并且不会导致消息外露。
一. 为什么不用通知
通知是一种零耦合的类之间通信方式,它的优点就是能够完全解耦,然而除了这个优点,通知也有不少值得吐槽的地方:
- 通知的接收范围为全局,这可能会暴露你原本想隐藏的实现细节,比如你封装的SDK中发出的通知,通知参数中包含敏感信息等;
- 通知的匹配完全依赖字符串,容易出现问题,当项目中大量使用通知以后难以维护,极端情况会出现通知名重复的问题;
相对于代理方式,通知不能像代理一样使用协议来约束代理者的方法实现; - 通知携带的参数不能直观的表达出来,依靠字典操作也增加的出错的可能性,通知不能像代理方法那样有返回值;
通知参数传递对于基本类型需要装箱和拆箱操作,不能传递nil参数; - 通知有时候会打破高内聚低耦合中的高内聚的原则,对于原本就有单向依赖的2个类来说,他们是有内聚耦合关系的,使用通知反而将这种内聚关系打散了,并且不利于方法调试;
二. 多播代理的思想
在C#语言中就有这样一个概念叫做多播委托,它直接是针对对象的某个委托事件的代理,委托对象内部保存了所有代理实现(指针),构成一个委托链,当这个委托事件触发的时候这个委托链上的所有实现方法都将被调用。iOS中的多代理概念雷同,其实就是委托对象中保持多个代理对象的引用,当触发事件的时候,让所有的代理对象调用相应的代理方法即可。
三. OC中构造多播代理
1.存储多个代理
遵循iOS常规代理的实现,我们需要一 个能够保存多个对象弱引用的结构,iOS中可以用多种方式实现,这里我推荐使用NSHashTable这个容器类,它可以指定加入到其中的对象为弱引用,并且当其中的对象被释放以后,该对象将会被自动从容器中移除掉
#pragma mark 发送消息
-(void)sendSocketMessage:(NSData *)data socketid:(NSString *)socketid delegate:(id<TCPManagerDelegate>)delegate
{
//保存代理
if (!self.delegates) {
self.delegates = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
}
[self.delegates addObject:delegate];
}
2.遍历多代理,执行代理方法
当NSHashTable中的对象释放以后,会被从中自动移除(经测试hashTable的count并没有变),我们遍历的时候就不会遍历到该nil对象
#pragma mark 服务器消息返回来了
-(void)responsemessage:(NSData *)data
{
//遍历代理并且执行代理方法
for (id<TCPManagerDelegate> delegate in self.delegates)
{
if (delegate && [delegate respondsToSelector:@selector(messagerecive:)])
{
[delegate messagerecive:nil];
}
}
}
3.设置(添加)代理
对于多代理我们只能用添加的方式,不能用直接赋值的方式
[[TCPManager sharemanager] sendSocketMessage:nil socketid:nil delegate:self];
在在这个类中实现出代理方法:
#pragma mark TCPManagerDelegate
-(void)messagerecive:(NSData *)message
{
NSLog(@"%s=======%@", __func__, [self class]);
}
4.总结
上面其实还不是真正的多播代理,只是实现出来了,如何使用代理方式,饭后回调回来数据,我们回到文章开头的需求,我们要吃饭(vc说我要吃饭),这是需要三个人来做饭(),我们就要实现出这三个人:
- (void)viewDidLoad {
[super viewDidLoad];
[self setupViews];
// [[TCPManager sharemanager] sendSocketMessage:nil socketid:nil delegate:self];
//因为里面采用的hashTable用的事弱引用,所以这里的三个对象不能够是局部变量,因为局部变量,释放之后,弱引用就断了,不能回调了
self.pp1 = [[LWDPeople alloc] initWithName:@"a"];
self.pp2 = [[LWDPeople alloc] initWithName:@"b"];
self.pp3 = [[LWDPeople alloc] initWithName:@"c"];
//给这三个人都添加上委托 -- 这里添加委托可以放到他们的init方法里面
//如果还想让一条狗也知道,也去帮忙做事的话,继续添加委托就行了
self.dog1 = [BigDOG new];
}
在LWDPeople.m中
-(instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self)
{
_name = name;
[[TCPManager sharemanager] sendSocketMessage:nil socketid:nil delegate:self];
}
return self;
}
#pragma mark TCPManagerDelegate
- (void)messagerecive:(NSString *)message
{
//多播代理回来了--注意看三次的self是不是同一个
NSLog(@"111111======%@===%@", self, _name);
}
在BigDOG.m中
-(instancetype)init
{
self = [super init];
if (self)
{
[[TCPManager sharemanager] sendSocketMessage:nil socketid:nil delegate:self];
}
return self;
}
#pragma mark TCPManagerDelegate
-(void)messagerecive:(NSString *)message
{
NSLog(@"大狗快去帮忙");
}
这样就实现了多播代理,谁需要接收代理的消息,谁就把这个代理添加进去。
总结就是,使用数组把添加的代理都保存进去,发消息的时候,遍历数组,把里面的对象全部拿出来执行一遍,来实现多播代理
上面只是介绍了多播代理实现的基本思路,如果有多个多播代理的话,上面的扩展性很差,以后会继续优化