iOS开发中,经常需要在各个模块或者组件间进行消息通信,主要分为两种类别,一种是页面之间的(跨越不同的ViewController),一种是页面内部的(在同一个ViewController),两种类别由于场景不同采用的技术方案也不同,本文主要探讨的是页面内部的消息通知机制。
委托机制通过protocol声明了一系列的回调方法,发送者不需要知道接受者的具体类型,在需要回调的时候,判断delegate是否实现相应的方法然后进行调用即可。委托机制中接受者必须直接持有发送者对象才能设置delegate为自己,如果是复杂的页面对象之间层级比较多的情况,委托机制不适用。
block和委托机制比较类似,和delegate比较更适用于回调方法数量比较少的情况,同样在对象之间层级较多的时候并不适用。
消息通知在两个对象的关联性比较小的时候使用得比较普遍,发送者不需要知道接受者,接受者也不需要知晓发送者,做到了完全解耦。但是实践过程中也有以下缺点:
基于上述的几种通信方式的优缺点,并且参考了前端的Angular实现方式,闲鱼iOS客户端实现了基于组件树的定向消息通信机制。
如图所示:
应用层采用的是典型的MVVM结构,每个view都有相应的component用来做数据绑定源,component在MVVM结构中可以看做提供配置view信息的ViewModel角色,消息在组件之间单向传递,emit是子组件沿着组件树向上发送消息,broadcast是父组件向所有子组件发送消息。当某个组件接受到消息后,判断是否订阅了这个消息,如果没有的话就继续传递,如果有订阅的话可以选择是否拦截事件,如果不拦截事件就继续传递,默认是拦截的。
FMComponent表示组件,FMComponent(Messaging)是个category,用FMMessageSubject来表示订阅的事件,事件用字符串表示,发送事件可以传递data信息,和系统的notification类似。
主要代码:
@implementation FMComponent (Messaging)
- (void)emit:(NSString *)eventName data:(id)data {
if (![self.parentComponent receive:eventName data:data]) {
[self.parentComponent emit:eventName data:data];
}
}
- (void)broadCast:(NSString *)eventName data:(id)data {
NSArray *childComponents = [self childComponents];
[childComponents enumerateObjectsUsingBlock:^(FMComponent *childComponent, NSUInteger idx, BOOL *stop) {
if (![childComponent receive:eventName data:data]) {
[childComponent broadCast:eventName data:data];
}
}];
}
- (void)onEvent:(NSString *)eventName action:(void (^)(id data))actionBlock intercept:(BOOL)intercept {
__block FMMessageSubject *matchEventSubject = nil;
[self.eventSubjects enumerateObjectsUsingBlock:^(FMMessageSubject *eventSubject, NSUInteger idx, BOOL *stop) {
if ([[eventSubject name] isEqualToString:eventName]) {
matchEventSubject = eventSubject;
}
}];
if (!matchEventSubject) {
matchEventSubject = [FMMessageSubject subject];
matchEventSubject.intercept = intercept;
}
[matchEventSubject setName:eventName];
[self.eventSubjects addObject:matchEventSubject];
[matchEventSubject subscribeNext:^(id data) {
actionBlock(data);
}];
}
- (void)onEvent:(NSString *)eventName action:(void (^)(id data))actionBlock {
[self onEvent:eventName action:actionBlock intercept:YES];
}
- (BOOL)receive:(NSString *)receiveEventName data:(id)data {
__block BOOL interceptEvent = NO;
[self.eventSubjects enumerateObjectsUsingBlock:^(FMMessageSubject *eventSubject, NSUInteger idx, BOOL *stop) {
if ([[eventSubject name] isEqualToString:receiveEventName]) {
[eventSubject sendNext:data];
interceptEvent = eventSubject.intercept;
}
}];
return interceptEvent;
}
- (NSMutableArray *)eventSubjects {
NSMutableArray *eventSignals = objc_getAssociatedObject(self, _cmd);
if (eventSignals) {
return eventSignals;
}
eventSignals = [NSMutableArray array];
objc_setAssociatedObject(self, _cmd, eventSignals, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return eventSignals;
}
@end
基于组件树的定向消息通信机制使得消息的传递在模块间互相隔离,避免了全局的notification机制带来的缺点,而且在对象层级多的时候也比较方便,并且对象之间互相解耦,消息的发送者和接受者互相不需要知道对方的类型信息。此通信机制在闲鱼App的几个业务模块已经广泛使用。