iOS組件化(三)-添加服務(wù)層以及MVVM模式

模塊間的對象傳輸

上一片分析了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如下

其中主工程和用戶模塊涉及到了復(fù)雜對象的傳輸


QQ20200527-005845.png

用戶模塊在回調(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è)操作邏輯如下


QQ20200527-103840@2x.png

為什么要多加這一層個(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)行分層,如下


組件化-分層設(shè)計(jì).png

當(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

上面的代碼交互如圖所示


MVVM-應(yīng)用.png

如果有需要,也可以在ViewModel中監(jiān)聽view層UI的變化,這樣可以進(jìn)一步把代碼封裝到ViewModel中,減少VIewController中的邏輯。

最后說一下,分層設(shè)計(jì),MVC,和MVP,MVVM這些概念,理解并不難,但是需要實(shí)踐,需要應(yīng)用到具體業(yè)務(wù)。并不是說越高級,越復(fù)雜越好。使用哪種模式,要看是否適用于項(xiàng)目,是否可擴(kuò)展,可復(fù)用,耦合程度低。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容