Domino
Domino 是 Objective-C 中多级对象之间的通信机制。
为什么使用 Domino?
当实现复杂 UI 时,通常会形成多级关系,如下所示
随着 ContentViewController 商务的发展,ContentViewController/ContainerViewController/RootViewController 的代理将越来越大。ContainerViewController/RootViewController 可能不会关心那么多事件,但仍需要将这些事件传给上级。Domino就是为了解决这个问题的。
Domino 在对象之间创建一个类似响应链的事件链。
适用场景
- 父 VC 包含子 VC。子 VC 和其他子 VC 依次包含。
- 还有一些事件由与 VC 分离的某些管理者标记,需要将这些事件传给 VC。
- 需要知道更多对象中的委托(如 UIScrollDelegate)中的事件。
与代理相比
- 避免实现转发了消息的方法。
- 委托只能进行一对一通信。但某些事件需要通知多个对象。
- 忽略命名空间。(也就是说,事件可以直接跨组件传递)
与 Notification 的比较
-
当有多个实例时,Notification 不是一个好的选择。
-
只有上级才能接收到事件,这意味着管理比 notification 更严格。
Domino 入门
系统响应链中存在默认事件链。如果所有相关对象都在同一个响应链上,您就不需要做任何配置。
如果不属于同一个响应链,您可以按照以下方式将对象挂载到链中
[dddManager.domino mountAtDomino:contentVC.domino]
您也可以通过使用 unmountFromPredomino:
从前一个节点卸载。链不会引起保留循环,因此不需要在对象销毁时调用 unmountFromPredomino
。
Domino 事件包含 SimpleEvent
和 SelectorEvent
。**事件仅从下到上传递,无论何种事件**。
SimpleEvent
使用 NSString
作为事件名,使用 NSDictionary
存储参数,就像 NSNotification
一样
SelectorEvent
更类似于委托。SelectorEvent 由协议定义。每个方法都是一种 SelectorEvent。通过这种方式调用方法和传递参数更简单、更正式。因此,在大多数情况下,建议使用 SelectorEvent。
对于返回值的 selector 事件,一旦有任何对象处理了事件,事件就不会传递到下一个节点。其机制与责任链相同。
对于不返回任何值的 selector 事件,事件会像通知一样通知所有合法的订阅者。
首先,您最好创建一个新文件来声明事件。
// ContentViewControllerEvents.h
/// SimpleEvent
extern NSString * const ContentViewControllerStatisticsEvent;
/// SelectorEvent
@protocol ContentViewControllerEvents <NSObject>
@optional
- (void)contentDidLoadWithArg1:(NSString *)arg1 arg2:(NSInteger)arg2;
- (void)contentDidLoadWithArg:(NSInteger)arg;
- (NSString *)fetchChannelId;
@end
发布事件
#import "Domino.h"
#import "ContentViewControllerEvents.h"
@DominoSelectorEvents(ContentViewControllerEvents); // declare events would be posted
@interface ContentViewController ()
@end
@implementation ContentViewController
- (void)viewDidLoad {
[super viewDidLoad];
// ...
// post SelectorEvent
[self.domino.trigger contentDidLoadWithArg:12];
// post NormalEvent
[self.domino.trigger postEvent:ContentViewControllerStatisticsEvent params:@{@"msg":@"did load"}];
}
@end
订阅事件
#import "Domino.h"
#import "ContentViewControllerEvents.h"
@interface ContainerViewController () // <ContentViewControllerEvents> NOT neccessary
@end
@implementation ContainerViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.domino.tracker subscribeSelectorEvent:@selector(contentDidLoadWithArg:) target:self];
[self.domino.tracker subscribeEvent:ContentViewControllerStatisticsEvent handler:^(NSDictionary *params) {
NSLog(@"[%@]%@",ContentViewControllerStatisticsEvent, params);
}];
}
- (void)contentDidLoadWithArg:(NSInteger)arg {
NSLog(@"ContainerViewController - contentDidLoad %td",arg);
}
@end
重构事件参数
@interface ContainerViewController ()<DominoInterceptor> // !!! declare IS neccessary !!!
@end
@implementation ContainerViewController
- (void)reformDominoParams:(DominoSelectorEventParams *)params forSelectorEvent:(SEL)selector {
if (selector == @selector(contentDidLoadWithArg1:arg2:)) {
NSLog(@"ContainerViewController - reform @selector(contentDidLoadWithArg1:arg2:)");
params[0] = @"hook!!!"; // index from 0
params[1] = @(333); // Don't worry about type
}
}
@end
当您需要拦截某些事件链时,您可以使用 BOOL DominoProtocolContainSelector(Protocol *protocol, SEL selector);
来确定一个选择器是否属于该协议。这允许在必要时拦截所有非公开事件。
线程
所有线程都安全,除了 DominoSelectorEventParams
。由于大多数场景是UI事件,因此默认情况下,所有订阅者都会在主线程上被调用。
当然,还有两种其他模式。
typedef NS_ENUM(NSInteger, DominoTriggerMode) {
DominoTriggerModeMainThread, // default
DominoTriggerModeBackground,
DominoTriggerModeCurrentThread,
};
您可以通过 [Domino setTriggerMode:DominoTriggerModeBackground]
来更改模式。模式不影响具有返回值的选择器事件,并且此类事件的订阅者始终在发布此事件的用户所在的线程上调用。
安装
如果您使用 Cocoapods,请在 Podfile 中添加 pod 'Domino', '~> 1.0.1'
。
您还可以将两个文件 Domino.h/m
添加到项目中。没有其他要求。
许可
Domino 采用 MIT 许可发布。有关详细信息,请参阅 LICENSE。