打造更簡潔的DataModel和ViewModel

很多人試圖解決 MVC 這種架構(gòu)下 Controller 比較臃腫的問題,這里我分享一種簡潔易懂的Model層,致力于打造更為簡潔的DataModel和ViewModel層,同時也適用Controller更加簡潔。下面一起分享學(xué)習(xí)。源碼:ZHModel_Demo

一、簡介

對于iOS的APP架構(gòu),有很多說法和實踐,包括MVC、MVVM、MVCS、VIPER等等。我相信大部分開發(fā)者都熱衷于MVC這種模式,我們對于 MVC 這種設(shè)計模式真的用得好嗎?其實不是的,MVC 這種分層方式雖然清楚,但是如果使用不當(dāng),很可能讓大量代碼都集中在 Controller 之中。
ZHModel_Demo,提供簡潔的DataModel和ViewModel示例。將數(shù)據(jù)請求封裝,用ViewModel層連接控制器和數(shù)據(jù),同時用屬性映射分離出DataModel層,使數(shù)據(jù)更清晰,更重要的是將 Controller瘦身。

二、說明

不論是哪種設(shè)計方式,總的架構(gòu)都是上述那幾種,所謂劍法無窮,萬劍歸宗。但今天我們討論的是如何將DataModel和ViewModel設(shè)計得簡潔清晰,可復(fù)用。

這里MVVM 的優(yōu)點拿來借鑒。具體做法就是將 ViewController 給 View 傳遞數(shù)據(jù)這個過程,抽象成構(gòu)造 ViewModel 的過程。這樣抽象之后,View 只接受 ViewModel,而 Controller 只需要傳遞 ViewModel 這么一行代碼。而另外構(gòu)造 ViewModel 的過程,我們就可以移動到另外的類中了。在具體實踐中,我們專門創(chuàng)建構(gòu)造 ViewModel 工廠類,參見工廠模式。另外,我們也將數(shù)據(jù)模型通過屬性映射對應(yīng)到DataModel,這樣使得所有來回的數(shù)據(jù)模型清晰可見,容易同意修改和復(fù)用。同時也可以專門將數(shù)據(jù)存取都抽將到一個 Service 層,由這層來提供 ViewModel 的獲取。

三、碼上說話

下面我分享下代碼,看看DataModel和ViewModel有什么優(yōu)勢或者不足的地方

1、目錄結(jié)構(gòu)

目錄結(jié)構(gòu).png

我們熟知的程序包含:網(wǎng)絡(luò)請求、數(shù)據(jù)、控制器(視圖,業(yè)務(wù)?),有時會把這幾個關(guān)系弄得繞來繞去,導(dǎo)致代碼臃腫,不好復(fù)用和維護(hù)。但所有情況無非就是:

1、數(shù)據(jù)怎么來?
2、數(shù)據(jù)怎么橋接?
3、數(shù)據(jù)怎么呈現(xiàn)?

1)、數(shù)據(jù)怎么來?

上述目錄解析:

1、網(wǎng)絡(luò)請求封裝成NetRequest模塊(還包括HTTPClient類,這里略)
2、數(shù)據(jù)請求總出口封裝在ZHRequestMethod類中
3、將收到的數(shù)據(jù)映射到數(shù)據(jù)模型DataModel(基于**ZHResponseBaseModel ** )

1)、數(shù)據(jù)怎么橋接?

上述目錄解析:

1、數(shù)據(jù)模型和View的橋接用到了ViewModel(基于ZHViewBaseModel
2、 用繼承于ZHViewBaseModelPublicViewBaseModel處理具體網(wǎng)絡(luò)請求,數(shù)據(jù)模型和控制器視圖的關(guān)系

1)、數(shù)據(jù)怎么呈現(xiàn)?

我們的目的是什么?就是為了讓數(shù)據(jù)呈現(xiàn)更加簡潔,讓呈現(xiàn)數(shù)據(jù)的視圖控制器變的優(yōu)雅。視圖呈現(xiàn)利用ViewModel輕松展示。

2、數(shù)據(jù)模型DataModel層

數(shù)據(jù)模型DataModel分為網(wǎng)絡(luò)請求返回參數(shù)模型發(fā)送參數(shù)模型
返回參數(shù)模型基類ZHResponseBaseModel:

//
//  ZHResponseBaseModel.h
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface ZHResponseBaseModel : NSObject

- (void)decodeJsontoDictionary:(NSDictionary*)dic;

@end
//
//  ZHResponseBaseModel.m
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import "ZHResponseBaseModel.h"
#import <objc/runtime.h>

@implementation ZHResponseBaseModel

- (void)decodeJsontoDictionary:(NSDictionary*)dic{
    if (![dic isKindOfClass:[NSDictionary class]]) {
        return;
    }
    for (NSString *key in dic) {
        if ([self respondsToSelector:@selector(setValue:forKey:)]) {
            if (class_getProperty([self class], [key UTF8String])) {
                if (![dic[key] isKindOfClass:[NSNull class]] ) {
                    [self setValue:dic[key] forKey:key];
                }else{
                }
            }
        }
    }
}

@end

將數(shù)據(jù)請求返回的Json字典通過屬性映射到各個模型字段中,例如Demo當(dāng)中有三個對應(yīng)的返回模型,當(dāng)然這種對應(yīng)的返回數(shù)據(jù)模型和參數(shù)模型可以放大每個子業(yè)務(wù)當(dāng)中。這里以其中一個為例子:Type1ResponseModel:

//
//  Type1ResponseModel.h
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import "ZHResponseBaseModel.h"

/***********對應(yīng)的Json格式*******
{
 "responseCode":"0000",
 "responseMsg":"返回成功",
 "remark":"交流學(xué)習(xí)",
 "userName":"簡書Haofree",
 "taskNo":1
}
******************************/

@interface Type1ResponseModel : ZHResponseBaseModel

@property (nonatomic , copy) NSString *responseCode;        //響應(yīng)結(jié)果碼

@property (nonatomic , copy) NSString *responseMsg;         //響應(yīng)消息

@property (nonatomic , copy) NSString *remark;              //備注

@property (nonatomic , copy) NSString *userName;            //名字

@property (nonatomic , assign) NSInteger taskNo;            //索引號

@end

//
//  Type1ResponseModel.m
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import "Type1ResponseModel.h"

@implementation Type1ResponseModel

@end

這樣當(dāng)數(shù)據(jù)請求返回相應(yīng)的Json字典時可以清晰解析并且通過KVC屬性賦值建起返回的數(shù)據(jù)模型,當(dāng)然里時最簡單的Json,例子中還涉及到了Json中要嵌套的和數(shù)組的這種常見的格式。這種把業(yè)務(wù)字段封裝成返回數(shù)據(jù)模型方便清晰,也可以放到一個大項目的子模塊當(dāng)中去。

發(fā)送參數(shù)模型基類ZHParamBaseModel:

既然返回的數(shù)據(jù)模型可以提取出來封裝,我們業(yè)務(wù)請求當(dāng)中還有很多需要發(fā)送的參數(shù),這如果分散放在視圖控制器中將會很糟糕。同樣我們可以將每個業(yè)務(wù)請求的參數(shù)封裝在類中,這樣修改起來我們不關(guān)系視圖控制器,只關(guān)心這個發(fā)送參數(shù)類,使得數(shù)據(jù)業(yè)務(wù)和視圖控制器得到很好的分離。

//
//  ZHParamBaseModel.h
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface ZHParamBaseModel : NSObject

-(NSDictionary*)covertToDic;

@end

//
//  ZHParamBaseModel.m
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import "ZHParamBaseModel.h"
#import <objc/runtime.h>
@implementation ZHParamBaseModel

- (NSDictionary *)covertToDic{
    return [self getPropertyList:[self class]];;
}

- (NSDictionary *)getPropertyList: (Class)clazz
{
    u_int count;
    unsigned int outCount;
    Ivar* ivars = class_copyIvarList(clazz, &count);
    objc_property_t *properties = class_copyPropertyList(clazz, &outCount);
    NSMutableDictionary *propertyDic = [NSMutableDictionary dictionary];
    for (int i = 0; i < count ; i++){
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        
        id value = [self valueForKey:[NSString stringWithFormat:@"%s",propName]];
        if (value == nil || [value isKindOfClass:[NSNull class]]) {
        }else{
            [propertyDic setValue:value forKey:[NSString  stringWithCString:propName encoding:NSUTF8StringEncoding] ];
        }
    }
    free(ivars);
    return propertyDic;
}

@end

2、(視圖-數(shù)據(jù))橋接模型ViewModel層

(視圖-數(shù)據(jù))橋接模型ViewModel的基類:ZHViewBaseModel

1、將不同的返回的Json字典轉(zhuǎn)換為數(shù)據(jù)模型
2、將接收封裝數(shù)據(jù)模型通過block回調(diào)

//
//  ZHViewBaseModel.h
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import <Foundation/Foundation.h>

//定義返回請求數(shù)據(jù)的block類型
typedef void (^ReturnValueBlock) (id returnValue);
typedef void (^FailureBlock)(NSUInteger statusCode, NSString *error);

@interface ZHViewBaseModel : NSObject

@property (copy, nonatomic) ReturnValueBlock returnBlock;
@property (copy, nonatomic) FailureBlock failureBlock;

//接收封裝數(shù)據(jù)請求返回的block
-(void) setBlockWithReturnBlock: (ReturnValueBlock) returnBlock
               WithFailureBlock: (FailureBlock) failureBlock;

//將返回的Json字典轉(zhuǎn)換為數(shù)據(jù)模型
-(id)process:(NSString*)className dic:(id)response;

@end

//
//  ZHViewBaseModel.m
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import "ZHViewBaseModel.h"
#import <objc/runtime.h>


@implementation ZHViewBaseModel

#pragma 接收封裝數(shù)據(jù)請求透穿過來的block
-(void) setBlockWithReturnBlock: (ReturnValueBlock) returnBlock
               WithFailureBlock: (FailureBlock) failureBlock{
    _returnBlock = [returnBlock copy];
    _failureBlock = [failureBlock copy];
}

-(id)process:(NSString*)className dic:(id)response{
    __strong Class model = [NSClassFromString(className) alloc];
    SEL selector = NSSelectorFromString(@"decodeJsontoDictionary:");
    if ([model respondsToSelector:selector]) {
        [model performSelector:selector withObject:response];
    }
    return model;
}

@end

另外就是具體處理網(wǎng)絡(luò)請求出口的公共ViewModel類:PublicViewBaseModel

//
//  PublicViewBaseModel.h
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import "ZHViewBaseModel.h"

@interface PublicViewBaseModel : ZHViewBaseModel

//數(shù)據(jù)請求層,舉例三個數(shù)據(jù)請求
- (void)sendType1Request:(NSDictionary*)dic;

- (void)sendType2Request:(NSDictionary*)dic;

- (void)sendType3Request:(NSDictionary*)dic;

@end

//
//  PublicViewBaseModel.m
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import "PublicViewBaseModel.h"
#import "ZHRequestMethod.h"

@implementation PublicViewBaseModel

- (void)sendType1Request:(NSDictionary*)dic{
    [self requestNet:dic type:RequestInterfaceNO1];
}

- (void)sendType2Request:(NSDictionary*)dic{
    [self requestNet:dic type:RequestInterfaceNO2];
}

- (void)sendType3Request:(NSDictionary*)dic{
    [self requestNet:dic type:RequestInterfaceNO3];
}

- (void)requestNet:(NSDictionary*)dic type:(RequestInterface)type{
    __weak typeof(self) weakSelf = self;
    [ZHRequestMethod startRequest:dic type:type success:^(id responseObject){
        NSLog(@"%@", responseObject);
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            [strongSelf sucessResponse:responseObject type:type];
        }
    }failure:^(NSUInteger statusCode, NSString *error){
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            strongSelf.failureBlock(statusCode,error);
        }
    }];
}

- (void)sucessResponse:(id)responseObject type:(RequestInterface)type{
    NSString *className = @"";
    switch (type) {
        case RequestInterfaceNO1:
            className = @"Type1ResponseModel";
            break;
        case RequestInterfaceNO2:
            className = @"Type2ResponseModel";
            break;
        case RequestInterfaceNO3:
            className = @"Type3ResponseModel";
            break;
        default:
            break;
    }
    id result = [self process:className dic:responseObject];
    if (self.returnBlock) {
        self.returnBlock(result);
    }
}

@end

1、視圖呈現(xiàn)

例子中一個視圖控制器有三個數(shù)據(jù)請求和對應(yīng)的數(shù)據(jù),返回數(shù)據(jù)一個地方處理即可,簡潔,需要用到數(shù)據(jù)的,直接從數(shù)據(jù)模型中取值。

//
//  ViewController.m
//  ZHModel_Demo
//
//  Created by haofree on 2017/3/3.
//  Copyright ? 2017年 haofree. All rights reserved.
//

#import "ViewController.h"
#import "PublicViewBaseModel.h"
#import "Type1ResponseModel.h"
#import "Type2ResponseModel.h"
#import "Type3ResponseModel.h"

@interface ViewController ()
@property (nonatomic , strong) PublicViewBaseModel *viewModel;
@property (nonatomic , strong) Type1ResponseModel  *responseModel_1;
@property (nonatomic , strong) Type2ResponseModel  *responseModel_2;
@property (nonatomic , strong) Type3ResponseModel  *responseModel_3;
@property (nonatomic , strong) Type3ResponseSubListModel  *responseSubListModel_3;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.viewModel = [[PublicViewBaseModel alloc] init];
    self.responseModel_1 = [[Type1ResponseModel alloc] init];
    self.responseModel_2 = [[Type2ResponseModel alloc] init];
    self.responseModel_3 = [[Type3ResponseModel alloc] init];
    self.responseSubListModel_3 = [[Type3ResponseSubListModel alloc] init];
    [self getResponseDataModel];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

//返回的數(shù)據(jù)
- (void) getResponseDataModel{
    __weak __typeof(self)wekself = self;
    [self.viewModel setBlockWithReturnBlock:^(id returnValue) {
        __strong typeof(wekself)strongSelf = wekself;
        if ([returnValue isKindOfClass:[Type1ResponseModel class]]) {
            strongSelf.responseModel_1 = (Type1ResponseModel*)returnValue;
            NSLog(@"responseModel_1:%@",strongSelf.responseModel_1);
        }else if ([returnValue isKindOfClass:[Type2ResponseModel class]]){
            strongSelf.responseModel_2 = (Type2ResponseModel*)returnValue;
            NSLog(@"responseModel_2:%@",strongSelf.responseModel_2);
            
        }else if ([returnValue isKindOfClass:[Type3ResponseModel class]]){
            strongSelf.responseModel_3 = (Type3ResponseModel*)returnValue;
            strongSelf.responseSubListModel_3 = strongSelf.responseModel_3.subTaskList[0];
            NSLog(@"responseModel_3:%@",strongSelf.responseModel_3);
        }else{
            NSLog(@"無法識別的模型類");
        }

    } WithFailureBlock:^(NSUInteger statusCode, NSString *error) {
        NSLog(@"錯誤碼:%lu,錯誤信息:%@",(unsigned long)statusCode,error);
    }];
}

//發(fā)送的請求
- (IBAction)requestBtn1:(id)sender{
    [self.viewModel sendType1Request:@{}];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       
    });
}

結(jié)語

文章分享用于交流學(xué)習(xí),一直處于學(xué)習(xí)積累過程中,文中最開始的思路來自于李澤魯---青玉伏案大神的MVVM工程架構(gòu),在這個基礎(chǔ)上做了更多的改進(jìn)。技術(shù)的積累,源于吸取,感謝同事和一起學(xué)習(xí)的朋友,大家批判指正。

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

相關(guān)閱讀更多精彩內(nèi)容

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