打造更簡潔的DataModel和ViewModel

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

一、簡介

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

二、說明

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

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

三、碼上說話

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

1、目錄結構

目錄結構.png

我們熟知的程序包含:網絡請求、數據、控制器(視圖,業務?),有時會把這幾個關系弄得繞來繞去,導致代碼臃腫,不好復用和維護。但所有情況無非就是:

1、數據怎么來?
2、數據怎么橋接?
3、數據怎么呈現?

1)、數據怎么來?

上述目錄解析:

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

1)、數據怎么橋接?

上述目錄解析:

1、數據模型和View的橋接用到了ViewModel(基于ZHViewBaseModel
2、 用繼承于ZHViewBaseModelPublicViewBaseModel處理具體網絡請求,數據模型和控制器視圖的關系

1)、數據怎么呈現?

我們的目的是什么?就是為了讓數據呈現更加簡潔,讓呈現數據的視圖控制器變的優雅。視圖呈現利用ViewModel輕松展示。

2、數據模型DataModel層

數據模型DataModel分為網絡請求返回參數模型發送參數模型
返回參數模型基類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

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

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

#import "ZHResponseBaseModel.h"

/***********對應的Json格式*******
{
 "responseCode":"0000",
 "responseMsg":"返回成功",
 "remark":"交流學習",
 "userName":"簡書Haofree",
 "taskNo":1
}
******************************/

@interface Type1ResponseModel : ZHResponseBaseModel

@property (nonatomic , copy) NSString *responseCode;        //響應結果碼

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

@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

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

發送參數模型基類ZHParamBaseModel:

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

//
//  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、(視圖-數據)橋接模型ViewModel層

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

1、將不同的返回的Json字典轉換為數據模型
2、將接收封裝數據模型通過block回調

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

#import <Foundation/Foundation.h>

//定義返回請求數據的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;

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

//將返回的Json字典轉換為數據模型
-(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 接收封裝數據請求透穿過來的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

另外就是具體處理網絡請求出口的公共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

//數據請求層,舉例三個數據請求
- (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、視圖呈現

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

//
//  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];
}

//返回的數據
- (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);
    }];
}

//發送的請求
- (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(), ^{
       
    });
}

結語

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容