转载

iOS页面内消息通信机制

iOS开发中,经常需要在各个模块或者组件间进行消息通信,主要分为两种类别,一种是页面之间的(跨越不同的ViewController),一种是页面内部的(在同一个ViewController),两种类别由于场景不同采用的技术方案也不同,本文主要探讨的是页面内部的消息通知机制。

现有常用的几种页面内消息通信方式

delegate

委托机制通过protocol声明了一系列的回调方法,发送者不需要知道接受者的具体类型,在需要回调的时候,判断delegate是否实现相应的方法然后进行调用即可。委托机制中接受者必须直接持有发送者对象才能设置delegate为自己,如果是复杂的页面对象之间层级比较多的情况,委托机制不适用。

block

block和委托机制比较类似,和delegate比较更适用于回调方法数量比较少的情况,同样在对象之间层级较多的时候并不适用。

notification

消息通知在两个对象的关联性比较小的时候使用得比较普遍,发送者不需要知道接受者,接受者也不需要知晓发送者,做到了完全解耦。但是实践过程中也有以下缺点:

  • 消息通知是全局的,如果不做好命名规范容易发生通知的冲突
  • 必须在dealloc中移除监听
  • 当对象发生了内存泄露的时候,仍在接受通知,这时候就会发生一些意想不到的bug,这些bug也很难调试

基于组件树的定向消息通信

基于上述的几种通信方式的优缺点,并且参考了前端的Angular实现方式,闲鱼iOS客户端实现了基于组件树的定向消息通信机制。

如图所示:

iOS页面内消息通信机制

应用层采用的是典型的MVVM结构,每个view都有相应的component用来做数据绑定源,component在MVVM结构中可以看做提供配置view信息的ViewModel角色,消息在组件之间单向传递,emit是子组件沿着组件树向上发送消息,broadcast是父组件向所有子组件发送消息。当某个组件接受到消息后,判断是否订阅了这个消息,如果没有的话就继续传递,如果有订阅的话可以选择是否拦截事件,如果不拦截事件就继续传递,默认是拦截的。

实现方式

iOS页面内消息通信机制

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的几个业务模块已经广泛使用。

原文  http://scorpiolin.github.io/2016/03/16/iOS-inner-messaging/
正文到此结束
Loading...