美文网首页
iOS组件化(三)-添加服务层以及MVVM模式

iOS组件化(三)-添加服务层以及MVVM模式

作者: samstring | 来源:发表于2020-05-26 23:43 被阅读0次

    模块间的对象传输

    上一片分析了MGJRouter的源码,不难发现,用MGJRouter这种以URL形式进行模块间的调用存在一个天然缺陷:

    模块间传递复杂对象的时候比较困难

    因为模块间以URL方式调用,对象在模块间不能直接传递,只能通过以参数的形式传递,MGJRouter里面的参数可以以字典的形式存在。假设两个模块间需要传递用户信息,内容如下
    SFUserInfo.h

    @interface SFUserInfo : NSObject
    @property (nonatomic, strong) NSString *userName;
    @property (nonatomic, assign) int age;
    @end
    

    在两个模块间传递,发送对象时就要把对象里面的值转化成以下内容,放到字典里面去。读取时候把字典里面的字段解析出来,这是一种最简单的方式

    {
    "userName":"张三",
    "age":10
    }
    

    但是两个模块间发送和处理如果以字典的形式去处理无疑是低效的。对于这种模块间传输复杂的对象的问题,笔者有几种解决方式

    • 1 将所有需要传输的对象放到一个基础model组件中,所有模块去引用这个组件
    • 2 添加基于协议的模块调用,模块间对外协议中暴露model,交互的时候直接引入model
    • 3 拷贝需要传输的model到需要交互的模块中,并以模块为前缀去命名。假设A模块需要传SFUserInfo给模块B,可以在A模块新建一个A_SFUserInfo类,在B模块新建B_ SFUserInfo,A_ SFUserInfo信息和B_ SFUserInfo内容一致。在A模块传递时候将A_ SFUserInfo对象放入字典中,在模块B从字典中获取内容,并强转为B_ SFUserInfo。

    但是对于这三种方式,都会存在一定的问题

    • 1随着模块中互相调用增加,基础model组件修改频繁,其他模块引入的时候需要频繁更新版本。并且对于其他模块,引入了很多无关的类
    • 2 可以直接受用modle,但是破坏了项目独立编译的特性
    • 3 当一个模块的modle修改时,其他模块的修改不及时,导致两边的model信息不一致。容易出现问题。而且出现代码冗余。适合于

    在基于自身的项目中,我采用了第三种方式。对于model信息不一致的问题,可以通过脚本去检测,如果类信息不一致,则报警告。然后再去修改model,下面给出一个Demo做示范

    ** 注意:下方截图和代码均来自demo **

    Demo如下

    其中主工程和用户模块涉及到了复杂对象的传输


    QQ20200527-005845.png

    用户模块在回调用户信息model给主模块的时候

     SFUserInfo *userInfo = [SFUserInfo new];
       userInfo.userName = [self.viewModel getUserInfo].userName;
       if (self.userBLock) {
           self.userBLock(userInfo);
       }
       [self.navigationController popViewControllerAnimated:YES];
    

    主模块接收

     
       UserModuleExample_UserBLock block = ^(SFExampleUserInfo *userInfo){
           NSLog(@"demo获取用户名-%@",userInfo.userName);
       };
       [userInfo  setObject:block forKey:@"block"];
       
       [MGJRouter openURL:@"sf_user://SFUserInfoViewController" withUserInfo:userInfo completion:^(id result) {
           
       }];
    

    这样虽然解决模块间的复杂model传输问题,但这并不是一个优秀的方案,需要基于自身的项目去选择合适的方案。

    弹到了模块与模块间的数据交互,再顺便谈一下模块内的数据交互。

    app的分层设计

    在上面的demo中,在路由中并不是直接跳转页面,而是多加了一层Service层,由service层去做页面跳转。

      [MGJRouter registerURLPattern:@"sf_user://SFUserInfoViewController" toHandler:^(NSDictionary *routerParameters) {
            NSDictionary *userInfo = [routerParameters objectForKey:MGJRouterParameterUserInfo];
            [UserService gotoUserInfoWithController:[userInfo objectForKey:@"vc"] withBLock:[userInfo objectForKey:@"block"]];
          }];
        
    

    整个操作逻辑如下


    QQ20200527-103840@2x.png

    为什么要多加这一层个service层呢?有以下几点

    • 封装模块,将操作逻辑交由service处理。在serivie层提供对外的URL,方法以及参数,其他模块在调用这个模块的时候,只需要查看模块对应的service头文件便可确定调用时候所需要的地址。
    • 如果后期要添加基于协议的路由交互方式,可以在sercie层添加。
    • 减少模块对MGJRouter的依赖。

    上面这一层服务是指模块对外访问,在模块内设计也可进行分层,如下


    组件化-分层设计.png

    当然分层这种设计是见仁见智的,有优点也有缺点,是否分层,怎样分层,都需要基于团队情况和项目情况而定。

    app的UI层设计模式

    对于UI层,我们也可以进行设计。我们常说的MVC,和MVP,MVVM等模式在我看来,更多的是针对上面所说的分层设计的UI层,令UI层尽可能的实现复用和解耦合。

    关于MVC,MVP,MVVM的概念解释,网上有很多,在这不再阐述,这里推荐一篇文章
    https://www.jianshu.com/p/ff6de219f988

    MVVM对于MVP,其中一个很大的差异是双向数据绑定。当model或是view有所改变的时候,对应的view和model会自动的去变更。

    实现这种双向数据绑定的方法有很多,例如有系统自带的KVO,ReactiveCocoa,和Facebook的开源库KVOController等。

    对于系统的KVO,使用起来过于麻烦,容易出错。而ReactiveCocoa使用方便,但是实现相对来说较为复杂,功能较多,对团队的学习成本比较高。而KVOController使用相对来说简单,实现也简单,所以本文采用KVOController去实现MVVM。

    在VIew或是VC初始化VIewModel。由ViewModel持有对象model,同时ViewModel持有一个VIiew或VC对象的弱引用。由VIewModel对View+ViewController和model的改变进行监听,如果有改变,则执行回调。

    SFUserViewModel.h

    #import <Foundation/Foundation.h>
    #import "SFUserInfoViewController.h"
    #import "SFUserInfo.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface SFUserViewModel : NSObject
    @property (nonatomic, weak)SFUserInfoViewController *vc;
    
    -(void)initWtihVC:(SFUserInfoViewController *)vc;
    //改变用户名称
    -(void)changeUserName;
    //获取用户信息
    -(SFUserInfo *)getUserInfo;
    @end
    

    SFUserViewModel.m

    #import "SFUserViewModel.h"
    #import <KVOController/KVOController.h>
    @interface SFUserViewModel ()
    
    @property (nonatomic, strong) SFUserInfo *userInfo;
    @property (nonatomic, weak) FBKVOController *kvoController;
    @end
    
    @implementation SFUserViewModel
    
    -(void)initWtihVC:(SFUserInfoViewController *)vc{
        self.vc = vc;
        self.userInfo = [SFUserInfo new];
        self.kvoController = [FBKVOController controllerWithObserver:self];
        [self.kvoController observe:self keyPath:@"userInfo.userName" options:(NSKeyValueObservingOptionNew) block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
            self.vc.nameLabel.text = [change objectForKey:@"new"];
        }];
        [self getDataFromnet];
    }
    
    -(void)getDataFromnet{
        //模拟从网络获取用户信息
        
        self.userInfo.userName = [NSString stringWithFormat:@"用户名:%@",@"张三"];
        self.userInfo.age = 10;
    }
    
    -(void)changeUserName{
        self.userInfo.userName = [NSString stringWithFormat:@"用户名:%@",[self randomNoNumber:8]];;
    }
    
    -(SFUserInfo *)getUserInfo{
        return self.userInfo;
    }
    
    // 随机生成字符串(由大小写字母组成)
    -(NSString *)randomNoNumber: (int)len {
        
        char ch[len];
        for (int index=0; index<len; index++) {
            
            int num = arc4random_uniform(58)+65;
            if (num>90 && num<97) { num = num%90+65; }
            ch[index] = num;
        }
        
        return [[NSString alloc] initWithBytes:ch length:len encoding:NSUTF8StringEncoding];
    }
    @end
    

    上面的代码交互如图所示


    MVVM-应用.png

    如果有需要,也可以在ViewModel中监听view层UI的变化,这样可以进一步把代码封装到ViewModel中,减少VIewController中的逻辑。

    最后说一下,分层设计,MVC,和MVP,MVVM这些概念,理解并不难,但是需要实践,需要应用到具体业务。并不是说越高级,越复杂越好。使用哪种模式,要看是否适用于项目,是否可扩展,可复用,耦合程度低。

    相关文章

      网友评论

          本文标题:iOS组件化(三)-添加服务层以及MVVM模式

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