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

模块间的对象传输

上一片分析了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这些概念,理解并不难,但是需要实践,需要应用到具体业务。并不是说越高级,越复杂越好。使用哪种模式,要看是否适用于项目,是否可扩展,可复用,耦合程度低。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342