美文网首页iosMVVM+RAC
RAC+MVVM,不同VC(VM)间数据双向绑定

RAC+MVVM,不同VC(VM)间数据双向绑定

作者: Dawn_wdf | 来源:发表于2020-03-25 18:14 被阅读0次

    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 <Foundation/Foundation.h>
    
    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 <objc/runtime.h>
    
    #import <JLRoutes/JLRoutes.h>
    #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<NSString *,id> * _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<NSString *,id> * _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的理念,但是我利用这种方法做到了模块间完全解耦。目前没有发现这样写有什么不利于项目的地方,如果有路过的大神指点一下,非常感谢!

    相关文章

      网友评论

        本文标题:RAC+MVVM,不同VC(VM)间数据双向绑定

        本文链接:https://www.haomeiwen.com/subject/lhmxuhtx.html