這是對前文基於 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。