模块间的对象传输
上一片分析了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如下
- SFMainProject 主工程
- SFUserModule 用户模块
其中主工程和用户模块涉及到了复杂对象的传输
用户模块在回调用户信息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"]];
}];
整个操作逻辑如下
为什么要多加这一层个service层呢?有以下几点
- 封装模块,将操作逻辑交由service处理。在serivie层提供对外的URL,方法以及参数,其他模块在调用这个模块的时候,只需要查看模块对应的service头文件便可确定调用时候所需要的地址。
- 如果后期要添加基于协议的路由交互方式,可以在sercie层添加。
- 减少模块对MGJRouter的依赖。
上面这一层服务是指模块对外访问,在模块内设计也可进行分层,如下
当然分层这种设计是见仁见智的,有优点也有缺点,是否分层,怎样分层,都需要基于团队情况和项目情况而定。
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
上面的代码交互如图所示
如果有需要,也可以在ViewModel中监听view层UI的变化,这样可以进一步把代码封装到ViewModel中,减少VIewController中的逻辑。
最后说一下,分层设计,MVC,和MVP,MVVM这些概念,理解并不难,但是需要实践,需要应用到具体业务。并不是说越高级,越复杂越好。使用哪种模式,要看是否适用于项目,是否可扩展,可复用,耦合程度低。