模塊間的對象傳輸
上一片分析了MGJRouter的源碼,不難發(fā)現(xiàn),用MGJRouter這種以URL形式進(jìn)行模塊間的調(diào)用存在一個(gè)天然缺陷:
模塊間傳遞復(fù)雜對象的時(shí)候比較困難
因?yàn)槟K間以URL方式調(diào)用,對象在模塊間不能直接傳遞,只能通過以參數(shù)的形式傳遞,MGJRouter里面的參數(shù)可以以字典的形式存在。假設(shè)兩個(gè)模塊間需要傳遞用戶信息,內(nèi)容如下
SFUserInfo.h
@interface SFUserInfo : NSObject
@property (nonatomic, strong) NSString *userName;
@property (nonatomic, assign) int age;
@end
在兩個(gè)模塊間傳遞,發(fā)送對象時(shí)就要把對象里面的值轉(zhuǎn)化成以下內(nèi)容,放到字典里面去。讀取時(shí)候把字典里面的字段解析出來,這是一種最簡單的方式
{
"userName":"張三",
"age":10
}
但是兩個(gè)模塊間發(fā)送和處理如果以字典的形式去處理無疑是低效的。對于這種模塊間傳輸復(fù)雜的對象的問題,筆者有幾種解決方式
- 1 將所有需要傳輸?shù)膶ο蠓诺揭粋€(gè)基礎(chǔ)model組件中,所有模塊去引用這個(gè)組件
- 2 添加基于協(xié)議的模塊調(diào)用,模塊間對外協(xié)議中暴露model,交互的時(shí)候直接引入model
- 3 拷貝需要傳輸?shù)膍odel到需要交互的模塊中,并以模塊為前綴去命名。假設(shè)A模塊需要傳SFUserInfo給模塊B,可以在A模塊新建一個(gè)A_SFUserInfo類,在B模塊新建B_ SFUserInfo,A_ SFUserInfo信息和B_ SFUserInfo內(nèi)容一致。在A模塊傳遞時(shí)候?qū)_ SFUserInfo對象放入字典中,在模塊B從字典中獲取內(nèi)容,并強(qiáng)轉(zhuǎn)為B_ SFUserInfo。
但是對于這三種方式,都會(huì)存在一定的問題
- 1隨著模塊中互相調(diào)用增加,基礎(chǔ)model組件修改頻繁,其他模塊引入的時(shí)候需要頻繁更新版本。并且對于其他模塊,引入了很多無關(guān)的類
- 2 可以直接受用modle,但是破壞了項(xiàng)目獨(dú)立編譯的特性
- 3 當(dāng)一個(gè)模塊的modle修改時(shí),其他模塊的修改不及時(shí),導(dǎo)致兩邊的model信息不一致。容易出現(xiàn)問題。而且出現(xiàn)代碼冗余。適合于
在基于自身的項(xiàng)目中,我采用了第三種方式。對于model信息不一致的問題,可以通過腳本去檢測,如果類信息不一致,則報(bào)警告。然后再去修改model,下面給出一個(gè)Demo做示范
** 注意:下方截圖和代碼均來自demo **
Demo如下
- SFMainProject 主工程
- SFUserModule 用戶模塊
其中主工程和用戶模塊涉及到了復(fù)雜對象的傳輸

用戶模塊在回調(diào)用戶信息model給主模塊的時(shí)候
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) {
}];
這樣雖然解決模塊間的復(fù)雜model傳輸問題,但這并不是一個(gè)優(yōu)秀的方案,需要基于自身的項(xiàng)目去選擇合適的方案。
彈到了模塊與模塊間的數(shù)據(jù)交互,再順便談一下模塊內(nèi)的數(shù)據(jù)交互。
app的分層設(shè)計(jì)
在上面的demo中,在路由中并不是直接跳轉(zhuǎn)頁面,而是多加了一層Service層,由service層去做頁面跳轉(zhuǎn)。
[MGJRouter registerURLPattern:@"sf_user://SFUserInfoViewController" toHandler:^(NSDictionary *routerParameters) {
NSDictionary *userInfo = [routerParameters objectForKey:MGJRouterParameterUserInfo];
[UserService gotoUserInfoWithController:[userInfo objectForKey:@"vc"] withBLock:[userInfo objectForKey:@"block"]];
}];
整個(gè)操作邏輯如下

為什么要多加這一層個(gè)service層呢?有以下幾點(diǎn)
- 封裝模塊,將操作邏輯交由service處理。在serivie層提供對外的URL,方法以及參數(shù),其他模塊在調(diào)用這個(gè)模塊的時(shí)候,只需要查看模塊對應(yīng)的service頭文件便可確定調(diào)用時(shí)候所需要的地址。
- 如果后期要添加基于協(xié)議的路由交互方式,可以在sercie層添加。
- 減少模塊對MGJRouter的依賴。
上面這一層服務(wù)是指模塊對外訪問,在模塊內(nèi)設(shè)計(jì)也可進(jìn)行分層,如下

當(dāng)然分層這種設(shè)計(jì)是見仁見智的,有優(yōu)點(diǎn)也有缺點(diǎn),是否分層,怎樣分層,都需要基于團(tuán)隊(duì)情況和項(xiàng)目情況而定。
app的UI層設(shè)計(jì)模式
對于UI層,我們也可以進(jìn)行設(shè)計(jì)。我們常說的MVC,和MVP,MVVM等模式在我看來,更多的是針對上面所說的分層設(shè)計(jì)的UI層,令UI層盡可能的實(shí)現(xiàn)復(fù)用和解耦合。
關(guān)于MVC,MVP,MVVM的概念解釋,網(wǎng)上有很多,在這不再闡述,這里推薦一篇文章
http://www.itdecent.cn/p/ff6de219f988
MVVM對于MVP,其中一個(gè)很大的差異是雙向數(shù)據(jù)綁定。當(dāng)model或是view有所改變的時(shí)候,對應(yīng)的view和model會(huì)自動(dòng)的去變更。
實(shí)現(xiàn)這種雙向數(shù)據(jù)綁定的方法有很多,例如有系統(tǒng)自帶的KVO,ReactiveCocoa,和Facebook的開源庫KVOController等。
對于系統(tǒng)的KVO,使用起來過于麻煩,容易出錯(cuò)。而ReactiveCocoa使用方便,但是實(shí)現(xiàn)相對來說較為復(fù)雜,功能較多,對團(tuán)隊(duì)的學(xué)習(xí)成本比較高。而KVOController使用相對來說簡單,實(shí)現(xiàn)也簡單,所以本文采用KVOController去實(shí)現(xiàn)MVVM。
在VIew或是VC初始化VIewModel。由ViewModel持有對象model,同時(shí)ViewModel持有一個(gè)VIiew或VC對象的弱引用。由VIewModel對View+ViewController和model的改變進(jìn)行監(jiān)聽,如果有改變,則執(zhí)行回調(diào)。
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{
//模擬從網(wǎng)絡(luò)獲取用戶信息
self.userInfo.userName = [NSString stringWithFormat:@"用戶名:%@",@"張三"];
self.userInfo.age = 10;
}
-(void)changeUserName{
self.userInfo.userName = [NSString stringWithFormat:@"用戶名:%@",[self randomNoNumber:8]];;
}
-(SFUserInfo *)getUserInfo{
return self.userInfo;
}
// 隨機(jī)生成字符串(由大小寫字母組成)
-(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中監(jiān)聽view層UI的變化,這樣可以進(jìn)一步把代碼封裝到ViewModel中,減少VIewController中的邏輯。
最后說一下,分層設(shè)計(jì),MVC,和MVP,MVVM這些概念,理解并不難,但是需要實(shí)踐,需要應(yīng)用到具體業(yè)務(wù)。并不是說越高級,越復(fù)雜越好。使用哪種模式,要看是否適用于項(xiàng)目,是否可擴(kuò)展,可復(fù)用,耦合程度低。