很多人試圖解決 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、目錄結構
我們熟知的程序包含:網絡請求、數據、控制器(視圖,業務?),有時會把這幾個關系弄得繞來繞去,導致代碼臃腫,不好復用和維護。但所有情況無非就是:
1、數據怎么來?
2、數據怎么橋接?
3、數據怎么呈現?
1)、數據怎么來?
上述目錄解析:
1、網絡請求封裝成NetRequest模塊(還包括HTTPClient類,這里略)
2、數據請求總出口封裝在ZHRequestMethod類中
3、將收到的數據映射到數據模型DataModel(基于**ZHResponseBaseModel ** )
1)、數據怎么橋接?
上述目錄解析:
1、數據模型和View的橋接用到了ViewModel(基于ZHViewBaseModel)
2、 用繼承于ZHViewBaseModel的PublicViewBaseModel處理具體網絡請求,數據模型和控制器視圖的關系
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工程架構,在這個基礎上做了更多的改進。技術的積累,源于吸取,感謝同事和一起學習的朋友,大家批判指正。