这是对前文基于 message forwarding 的轻量依赖注入容器实现 的一个实践。git
利用一个封装了 VC 与 VM 的类实例,经过 message forwarding 将 tableView.dataSource 和 tableView.delegate 转发给 VC 或 VM,从而避免这样的代码github
// VC.m
#pragma mark - UITableViewDataSource && UITableViewDelegate
// ...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.viewModel tableView:tableView numberOfRowsInSection:section];
}
复制代码
考虑到 tableView.dataSource 和 tableView.delegate 多多少少涉及到 view,一般会这么作swift
- (void)viewDidLoad
{
// ...
// VC 做为 tableView.dataSource 和 tableView.delegate
self.tableView.dataSource = self;
self.tableView.delegate = self;
// ...
}
// 再经过胶水代码调用 VM 的对应实现
#pragma mark - UITableViewDataSource && UITableViewDelegate
// ...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.viewModel tableView:tableView numberOfRowsInSection:section];
}
// ...
复制代码
抹一点点胶水没问题,但 app 里大部分页面都是 tableView 时,抹起来就很烦了。app
VC 负责实现 view 相关的方法,VM 负责实现数据相关的方法,两者由 proxy 封装起来。post
tableView 的每一次方法调用都经过 proxy,传递给 VC 或 VM 中实现了该方法的一方。单元测试
// vc.m
- (void)viewDidLoad
{
// ...
self.proxy = [[XXXTableViewProxy alloc] initWithVC:self vm:self.viewModel];
self.tableView.dataSource = proxy;
self.tableView.delegate = proxy;
// ...
}
#pragma mark - UITableViewDataSource && UITableViewDelegate
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[XXXCell reuseID]
forIndexPath:indexPath];
id model = [self.viewModel modelForRowAtIndexPath:indexPath];
[cell configWithModel:model];
return cell;
}
// vm.m
#pragma mark - UITableViewDataSource && UITableViewDelegate
- (NSInteger)tableView:(UITableView *)_ numberOfRowsInSection:(NSInteger)section
{
return self.dataSource.count;
}
复制代码
简直是天堂。测试
有了 NSFPrioritizedDelegate,一切都很简单。spa
// NSFTableViewDelegateProxy.h
/**
将 UITableView.dataSource/delegate 的方法调用转发给传入的 viewModel 或 VC
*/
@interface NSFTableViewDelegateProxy : NSFPrioritizedDelegate<UITableViewDataSource, UITableViewDelegate>
- (instancetype)initWithViewController:(UIViewController<NSFAllOptionalTableViewDataSource, UITableViewDelegate> *)viewController
viewModel:(id<NSFAllOptionalTableViewDataSource, UITableViewDelegate>)viewModel NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDelegates:(NSArray<id<NSObject>> *)delegates
weakRef:(BOOL)weakRef NS_UNAVAILABLE;
@end
// NSFTableViewDelegateProxy.m
// ...
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wprotocol"
@implementation NSFTableViewDelegateProxy
#pragma clang diagnostic pop
- (instancetype)initWithViewController:(UIViewController<NSFAllOptionalTableViewDataSource, UITableViewDelegate> *)viewController
viewModel:(id<NSFAllOptionalTableViewDataSource, UITableViewDelegate>)viewModel
{
if (self = [super initWithDelegates:@[viewController, viewModel] weakRef:YES])
{
_viewController = viewController;
_viewModel = viewModel;
self.cellForRowAtIndexPath = @selector(tableView:cellForRowAtIndexPath:);
self.didSelectRowAtIndexPath = @selector(tableView:didSelectRowAtIndexPath:);
}
return self;
}
#pragma mark - Rule
- (id<NSObject>)delegateRules:(SEL)selector
{
if (selector == self.cellForRowAtIndexPath)
{
return self.viewController;
}
else if (selector == self.didSelectRowAtIndexPath)
{
if ([self.viewController respondsToSelector:selector])
{
return self.viewController;
}
}
else if ([self.viewController respondsToSelector:selector])
{
return self.viewController;
}
else if ([self.viewModel respondsToSelector:selector])
{
return self.viewModel;
}
return nil;
}
复制代码
delegateRules 中,code
用起来是这样的get
- (void)viewDidLoad
{
// ...
self.proxy = [[NSFTableViewDelegateProxy alloc] initWithViewController:self viewModel:self.viewModel];
self.tableView.dataSource = self.proxy;
self.tableView.delegate = self.proxy;
// ...
}
复制代码
然后在 VC 和 VM 中各自实现须要的方法便可 😂😂。
完整实现见 NSFTableViewDelegateProxy,对应的单元测试见 NSFTableViewDelegateProxySpec。