RAC搭配MVVM,使用过程中,虽然所有的逻辑处理都放在VM中了,但是当页面之间数据有交互的时候最先想到的还是利用VC属性去传递,我……自我检讨。
业务需求是A页面PUSH到B页面,在B页面做的修改更新到A页面,再次进入B页面也要把A页面的数据带进去。这些数据包括并不在UI上显示的数据,比如数据ID,只用来做数据请求,这样的数据放在VM中最合适,不应该放在VC中,于是我觉得,应该绑定两个VM,这样一来要么需要B页面持有A页面的VM,要么使用router来获取。
第一种方法显然不合适,第二种方法虽然解了耦合,但是明显增加了router的工作,本身VC就要做router,现在VM也要做router就……容易乱,而且这样的方法也不利于双向绑定。
于是写了一个类专门给这样的两个VM属性进行双向绑定,绑定的数据为两个VM属性名称相同的内容,其中一个有变化,另一个跟着变化。
使用方法:在第一个VM中[[SRouterManager shareManager] s_bindViewModel:self.viewModel forkey:keyBindViewModelPublishProductCategory];
第二个VM中:[[SRouterManager shareManager] s_bindSecondViewModel:self.viewModel forkey:keyBindViewModelPublishProductCategory];
保证key相同就行,需要在页面销毁的时候remove掉[[SRouterManager shareManager] s_removeBindObjectForKey:keyBindViewModelPublishProductCategory];
- 以下为具体实现,付上router管理VC的方法:
.h
//
// SRouterManager.h
// Snatch
//
// Created by DawnWang on 2020/3/11.
// Copyright © 2020 Dawn Wang. All rights reserved.
//
#import
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString *routerScheme;
@interface SRouterManager : NSObject
+ (instancetype)shareManager;
+ (void)releaseManager;
/// push进入一个新的页面
/// @param classString 页面类名称
/// @param params 页面参数
+ (void)s_pushVCClassString:(NSString *)classString withParams:(NSDictionary *__nullable)params;
/// push进入一个新的页面
/// @param viewController 当前VC
/// @param classString 页面类名称
/// @param params 页面参数
+ (void)s_viewController:(UIViewController *__nullable)viewController pushVCClassString:(NSString *)classString withParams:(NSDictionary *__nullable)params;
/// present进入一个新的页面,默认添加srootnavigationcontroller
/// @param classString 页面类名
/// @param params 页面参数
+ (void)s_presentVCClassString:(NSString *)classString withParams:(NSDictionary *__nullable)params;
/// present进入一个新的页面,默认添加srootnavigationcontroller
/// @param viewController 当前VC
/// @param classString 页面类名称
/// @param params 页面参数
+ (void)s_viewController:(UIViewController *__nullable)viewController presentVCClassString:(NSString *)classString withParams:(NSDictionary *__nullable)params;
/// 绑定viewmodel
/// @param viewModel 第一个viewmodel
/// @param key 关键字
- (void)s_bindViewModel:(id)viewModel forkey:(NSString *)key;
/// 绑定viewmodel
/// @param viewModel 第二个viewmodel
/// @param key 需要跟第一个关键字相同,两个model才会绑定在一起,绑定的内容为两个viewmodel属性名相同的property属性
- (void)s_bindSecondViewModel:(id)viewModel forkey:(NSString *)key;
/// 删除某一个绑定
/// @param key key值
- (void)s_removeBindObjectForKey:(NSString *)key;
@end
NS_ASSUME_NONNULL_END
.m
//
// SRouterManager.m
// Snatch
//
// Created by DawnWang on 2020/3/11.
// Copyright © 2020 Dawn Wang. All rights reserved.
//
#import "SRouterManager.h"
#import
#import
#import "NSObject+TopVC.h"
#import "SRootNavigationViewController.h"
NSString *routerScheme = @"Snatch://";
static NSString *const firstViewModelKey = @"firstViewModelKey";
static NSString *const secondViewModelKey = @"secondViewModelKey";
@interface SRouterManager()
@property (nonatomic, strong) JLRoutes *router;
@property (nonatomic, strong) NSMutableDictionary *viewModelsDic;
@property (nonatomic, weak) UIViewController *topVC;
@property (nonatomic, weak) NSDictionary *commenParameters;
@end
@implementation SRouterManager
static SRouterManager *manager = nil;
static dispatch_once_t onceToken;
+ (instancetype)shareManager {
dispatch_once(&onceToken, ^{
manager = [[SRouterManager alloc] init];
[manager s_registerCommenVCs];
});
return manager;
}
+ (void)releaseManager {
manager = nil;
onceToken = 0;
}
#pragma mark - privit method
- (void)s_registerCommenVCs {
[[JLRoutes routesForScheme:routerScheme] addRoute:@"push/:viewController" handler:^BOOL(NSDictionary * _Nonnull parameters) {
NSString *vcString = parameters[@"viewController"];
UIViewController *pushedVC = [[NSClassFromString(vcString) alloc] init];
[parameters enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([[self.commenParameters allKeys] containsObject:key]) {
[pushedVC setValue:obj forKey:key];
}
}];
UIViewController *topVC = self.topVC?:[self s_topViewController];
[topVC.navigationController pushViewController:pushedVC animated:YES];
return YES;
}];
[[JLRoutes routesForScheme:routerScheme] addRoute:@"present/:viewController" handler:^BOOL(NSDictionary * _Nonnull parameters) {
NSString *vcString = parameters[@"viewController"];
UIViewController *presnetVC = [[NSClassFromString(vcString) alloc] init];
[self.commenParameters enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[presnetVC setValue:obj forKey:key];
}];
SRootNavigationViewController *nav = [[SRootNavigationViewController alloc] initWithRootViewController:presnetVC];
UIViewController *topVC = self.topVC?:[self s_topViewController];
[topVC presentViewController:nav animated:YES completion:nil];
return YES;
}];
}
#pragma mark - vc router
+ (void)s_pushVCClassString:(NSString *)classString withParams:(NSDictionary *__nullable)params{
[SRouterManager s_viewController:nil pushVCClassString:classString withParams:params];
}
+ (void)s_viewController:(UIViewController *__nullable)viewController pushVCClassString:(NSString *)classString withParams:(NSDictionary *__nullable)params{
[[SRouterManager shareManager] setTopVC:viewController];
[[SRouterManager shareManager] setCommenParameters:params];
[[JLRoutes routesForScheme:routerScheme] routeURL:[NSURL URLWithString:[NSString stringWithFormat:@"push/%@",classString]] withParameters:params];
}
+ (void)s_presentVCClassString:(NSString *)classString withParams:(NSDictionary *__nullable)params {
[SRouterManager s_viewController:nil presentVCClassString:classString withParams:params];
}
+ (void)s_viewController:(UIViewController *__nullable)viewController presentVCClassString:(NSString *)classString withParams:(NSDictionary *__nullable)params {
[[SRouterManager shareManager] setTopVC:viewController];
[[SRouterManager shareManager] setCommenParameters:params];
[[JLRoutes routesForScheme:routerScheme] routeURL:[NSURL URLWithString:[NSString stringWithFormat:@"present/%@",classString]] withParameters:params];
}
#pragma mark - viewModel router
/*
@{
连接两个model的key:@{第一个model对象:viewModel,第二个model对象的数组:@[viewModel1,viewModel2,viewModel3]}
}
*/
- (void)s_bindViewModel:(id)viewModel forkey:(NSString *)key {
NSMutableDictionary *dic = self.viewModelsDic[key];
if (dic) {
[dic setObject:viewModel forKey:firstViewModelKey];
}else{
dic = [[NSMutableDictionary alloc] init];
[dic setObject:viewModel forKey:firstViewModelKey];
self.viewModelsDic[key] = dic;
}
}
- (void)s_bindSecondViewModel:(id)viewModel forkey:(NSString *)key {
NSMutableDictionary *dic = self.viewModelsDic[key];
// NSAssert(dic, @"请先设置第一个需要绑定的viewmodel");
NSMutableArray *secondViewModels = dic[secondViewModelKey];
if (secondViewModels) {
__block BOOL hasOne = NO;
__block NSInteger hasIndex = 0;
//此处是为了清空已经失效的VM对象
[secondViewModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([NSStringFromClass([obj class]) isEqualToString:NSStringFromClass([viewModel class])]) {
hasOne = YES;
hasIndex = idx;
*stop = YES;
}
}];
if (hasOne) {
[secondViewModels removeObjectAtIndex:hasIndex];
}
[secondViewModels addObject:viewModel];
[self s_beginBindWithKey:key];
}else{
secondViewModels = [[NSMutableArray alloc] init];
[secondViewModels addObject:viewModel];
self.viewModelsDic[key][secondViewModelKey] = secondViewModels;
[self s_beginBindWithKey:key];
}
}
- (void)s_beginBindWithKey:(NSString *)key {
NSMutableDictionary *dic = self.viewModelsDic[key];
if (dic) {
id firstViewModel = dic[firstViewModelKey];
if (!firstViewModel) {
return;
}
NSArray *secondViewModels = dic[secondViewModelKey];
NSAssert(firstViewModel && secondViewModels, @"需要绑定的viewmodel都为不能为空");
//获取第一个viewmodel的属性名称数组
unsigned firstPropertyCount;
objc_property_t *firstPropertyList = class_copyPropertyList([firstViewModel class], &firstPropertyCount);
NSMutableArray *fistViewModelPropertyNames = [NSMutableArray array];
for (int i = 0; i < firstPropertyCount; i++) {
objc_property_t property = firstPropertyList[i];
NSString *propertyName = [NSString stringWithFormat:@"%s",property_getName(property)];
[fistViewModelPropertyNames addObject:propertyName];
}
[secondViewModels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//获取第二个viewmodel的属性名称数组
unsigned propertyCount;
objc_property_t *propertyList = class_copyPropertyList([obj class], &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = propertyList[i];
NSString *propertyName = [NSString stringWithFormat:@"%s",property_getName(property)];
if ([fistViewModelPropertyNames containsObject:propertyName]) {
//如果跟第一个viewmodel的属性名称相同就绑定一下
[[RACKVOChannel alloc] initWithTarget:obj keyPath:propertyName nilValue:nil][@"followingTerminal"] = [[RACKVOChannel alloc] initWithTarget:firstViewModel keyPath:propertyName nilValue:nil][@"followingTerminal"];
}
}
}];
}
}
- (void)s_removeBindObjectForKey:(NSString *)key {
NSAssert(key, @"key不能为空");
[self.viewModelsDic removeObjectForKey:key];
}
#pragma mark - getter && setter
- (NSMutableDictionary *)viewModelsDic {
if (!_viewModelsDic) {
_viewModelsDic = [[NSMutableDictionary alloc] init];
}
return _viewModelsDic;
}
@end
虽然不太确定这样写是否符合MVVM的理念,但是我利用这种方法做到了模块间完全解耦。目前没有发现这样写有什么不利于项目的地方,如果有路过的大神指点一下,非常感谢!