MVVM 模式下iOS專案目錄結構詳細說明
原文出自【聽雲技術部落格】:http://blog.tingyun.com/web/article/detail/650
我們在做專案的時候,會經常用到各種設計模式,最常見的要數 MVC (模型,檢視,控制器)了。但是,今天我們要說的是另一種設計模式——MVVM。 所以 MVVM 到底是什麼?下面,我們將結合程式碼,說明 MVVM 設計模式以及專案目錄結構。
一、MVVM 模式介紹
MVVM 是 Model-View-View Model 的縮寫,MVVM 聽起來好像很複雜的樣子,但它本質上就是MVC 的改進版。MVVM 就是將其中的View 的狀態和行為抽象化,讓我們將檢視 UI 和業務邏輯分開。當然這些事 ViewModel 已經幫我們做了,它可以取出 Model 的資料同時幫忙處理 View 中由於需要展示內容而涉及的業務邏輯。在 iOS 中使用 MVVM 可以將 ViewController 中處理 Mode 的業務邏輯全部交由 ViewModel,讓 ViewController 不再顯的特別臃腫。
MVVM模式是通過下面三個核心元件組成,每個都有它自己所要處理的事情:
Model -資料模型
View – 用來將Model 的內容顯示出來
ViewModel - 扮演“View”和“Model”之間的使者,幫忙處理 View 的業務邏輯
如圖:
那麼MVVM 模式有什優點呢?
1. 低耦合。檢視(View)可以獨立於Model變化和修改,一個ViewModel可以繫結到不同的"View"上,當View變化的時候Model可以不變,當Model變化的時候View也可以不變。
2. 可重用性。你可以把一些檢視邏輯放在一個ViewModel裡面,讓很多view重用這段檢視邏輯。
3. 獨立開發。開發人員可以專注於業務邏輯和資料的開發(ViewModel),設計人員可以專注於頁面設計。
4. 可測試。介面素來是比較難於測試的,而使用MVVM的一大好處是我們可以很容易對 ViewModel 進行單元測試
二、專案目錄結構
我認為一個合理的專案目錄結構首先應該是讓人一目瞭然的,讓人一眼看上去就能大概瞭解目錄的職責,瞭解每個資料夾下的內容是做什麼的,而且容易應對新的改變方便後續新增功能或者擴充套件。iOS 專案目錄結構不一定適合所有人的想法,關鍵是看你希望用一個結構解決什麼問題。對於我自己來講,我認為一個良好的專案目錄結構,要達到以下兩個目的:
1)使專案更適合於團隊開發,能夠降低耦合、便於任務的劃分和程式碼的整合管理。
2)使專案能夠積累出更多可複用的程式碼和架構。
這個結構會在不斷遇到問題解決問題的過程中權衡、進化,在這個過程最重要的是能夠保持:
1)主幹簡潔。主幹上防止過度劃分,過度劃分會讓程式碼放在這個目錄下也可以,放在另一個目錄下好像也行,容易混亂。
2)分支開放。不對過於細節的分支做嚴格規範,可以發揮大家的靈活性和創造性。
下面我們來看,我構建的專案目錄結構,如下圖所示。
Define —— 用於存放我們設定的一些巨集(#define)。
Model —— 用於存放模型類(資料模型)。
NetworkManager —— 用於存放網路請求類
Resources —— 用於存放資源 例如xib,storyboard,圖片,plist,音訊,視訊
Util —— 用於存放我們定義的分類和擴充套件或者工具類
Vendors —— 用於存放第三方框架或者第三方SDK檔案
View —— 用於存放檢視類
ViewControllers —— 用於存放檢視控制器類
ViewModel —— 用於存放檢視模型類,及處理 View 和 Model 之間的業務邏輯。
整體專案的執行流程是:
ViewController->向ViewModel請求資料->ViewModel->向網路請求資料->需要資料解析型別負責解析
專案編寫的順序是,需要先完成最底層的依賴,然後層層向上。
我們通過一個小 Demo,來講解我們的目錄結構。
第一,我們需要在 Model 中寫好資料模型,根據介面提供的資料,進行解析。在這個 Demo 中,解析我們用到了第三方框架MJExtension。
在這兒我們需要注意的是, Model(模型)類的命名規則。我認為,我們需要一層一層的命名,體現出包含關係。這樣更清晰的表示了資料模型。
如:
@classCarHomeResultModel,CarHomeResultHeadlineinfoModel,CarHomeResultTopnewsinfoModel,CarHomeResultNewslistModel;
@interface CarHomeModel : BaseModel
@property (nonatomic, strong) CarHomeResultModel *result;
@property (nonatomic, assign) NSInteger returncode;
@property (nonatomic, copy) NSString *message;
@end
@interface CarHomeResultModel : NSObject
@property (nonatomic, assign) BOOL isloadmore;
@property (nonatomic, assign) NSInteger rowcount;
@property (nonatomic, strong) CarHomeResultHeadlineinfoModel *headlineinfo;
@property (nonatomic, strong) NSArray *focusimg;
@property (nonatomic, strong) NSArray *newslist;
@property (nonatomic, strong) CarHomeResultTopnewsinfoModel *topnewsinfo;
@end
我們可以看到,在.h 檔案中,CarHomeModel 和 CarHomeResultModel。可以明確看出包含關係。
然後我們還需要注意,在.m檔案中,需要對,陣列和一些關鍵詞做特殊處理才能解析。
如:
#import "CarHomeModel.h"
@implementation CarHomeResultModel
+(NSDictionary *)objectClassInArray{
return @{@"newslist" : [CarHomeResultNewslistModel class]};
}
@end
@implementation CarHomeResultNewslistModel
+(NSDictionary *)replacedKeyFromPropertyName
{
return @{@"ID":@"id"};
}
@end
對陣列,需要指定解析的類的型別,對關鍵字需要將改變前和改變後的詞相對應。
第二,在寫完 Model 之後,我們需要進行網路請求了。在 NetManager 目錄下面我們主要做所有的網路請求工作。為了方便,簡單的封裝了 AFNetworking。閒話不多說,直接上程式碼。在 BaseNetManager.h 檔案中。我們公開了兩個類方法
#import #define kCompletionHandle completionHandle:(void(^)(id model, NSError
*error))completionHandle
@interface BaseNetManager : NSObject
/** 對AFHTTPSessionManager的GET請求方法進行了封裝 */
+(id)GET:(NSString *)path parameters:(NSDictionary *)params
completionHandler:(void(^)(id responseObj, NSError *error))complete;
/** 對AFHTTPSessionManager的POST請求方法進行了封裝 */
+(id)POST:(NSString *)path parameters:(NSDictionary *)params
completionHandler:(void(^)(id
responseObj, NSError *error))complete;
@end
我們在 BaseNetManager.m 中對其實現。
#import "BaseNetManager.h"
static AFHTTPSessionManager *manager = nil;
@implementation BaseNetManager
+(AFHTTPSessionManager *)sharedAFManager{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",
@"application/json", @"text/json", @"text/javascript", @"text/plain", nil];
});
return manager;
}
+(id)GET:(NSString *)path parameters:(NSDictionary *)params completionHandler:(void(^)(id
responseObj, NSError *error))complete{
return [[self sharedAFManager] GET:path parameters:params success:^void(NSURLSessionDataTask
*task, id responseObject) {
complete(responseObject, nil);
} failure:^void(NSURLSessionDataTask * task, NSError * error) {
complete(nil, error);
}];
}
+(id)POST:(NSString *)path parameters:(NSDictionary *)params completionHandler:(void(^)(id
responseObj, NSError *error))complete{
return [[self sharedAFManager] POST:path parameters:params
success:^void(NSURLSessionDataTask * task, id responseObject) {
complete(responseObject, nil);
} failure:^void(NSURLSessionDataTask * task, NSError * error) {
[self handleError:error];
complete(nil, error);
}];
}
@end
封裝好網路請求的基類之後,我們就可以使用它們了。
在我們繼承的CarHomeManager.h 中,我們用 block 回撥的方式, 公開一個類方法,用來網路請求。
#import "BaseNetManager.h"
//http://app.api.autohome.com.cn/autov5.0.0/news/newslist-pm1-c0-nt3-p4-s30-l0.json
@interface CarHomeManager : BaseNetManager
+(id)getCarHomeWithLastTime:(NSString *)lasttime kCompletionHandle;
@end
在CarHomeManager.m 檔案中實現
#import "CarHomeManager.h"
#import "CarHomeModel.h"
#define Kpath @"http://app.api.autohome.com.cn/autov5.0.0/news/newslist-pm1-c0-nt3-p4-s30-
l%@.json"
@implementation CarHomeManager
//http://app.api.autohome.com.cn/autov5.0.0/news/newslist-pm1-c0-nt3-p4-s30-l0.json
+(id)getCarHomeWithLastTime:(NSString *)lasttime completionHandle:(void (^)(id, NSError
*))completionHandle{
NSString *path = [NSString stringWithFormat:Kpath,lasttime];
return [self GET:path parameters:nil completionHandler:^(id responseObj, NSError *error) {
completionHandle([CarHomeModel objectWithKeyValues:responseObj],error);
}];
}
@end
這樣封裝的好處有很多,當我們的介面有改變時,我們只需要改變上面的巨集定義的請求 URL地址就可以了。而且,方便我們測試網路請求已經資料解析到底成不成功。例如,我們在 AppDelegate.m 檔案中呼叫改類方法,用斷點除錯的方式,可以看到請求和解析是否成功。
第三,我們在做完網路請求後,就要根據產品所給的 UI 介面的設計稿,要開始做 ViewModel 層了,這一層主要是 View 顯示的一些業務邏輯。
程式碼如下:
在 CarHomeViewModel.h中
#import "BaseViewModel.h"
#import "CarHomeModel.h"
@interface CarHomeViewModel : BaseViewModel
//多少行
@property (nonatomic)NSInteger newlistNumber;
//每行的圖片地址
-(NSURL *)newlistIconURLWithForRow:(NSInteger )row;
//每行的標題名
-(NSString *)titleWithForRow:(NSInteger )row;
//每行的評論數
-(NSString *)replycountWithForRow:(NSInteger )row;
//每行的時間
-(NSString *)intacttimeWithForRow:(NSInteger )row;
@property (nonatomic,strong)NSString *lasttime;
@property (nonatomic,strong) CarHomeResultNewslistModel *newlistModel;
@end
CarHomeViewModel.m 中實現
#import "CarHomeViewModel.h"
#import "CarHomeManager.h"
#import "CarHomeModel.h"
@implementation CarHomeViewModel
-(NSInteger)newlistNumber
{
return self.dataArr.count;
}
-(void)getDataFromNetCompleteHandle:(CompletionHandle)completionHandle
{
//[self cancelTask];
[CarHomeManager getCarHomeWithLastTime:self.lasttime completionHandle:^(CarHomeModel
*model, NSError *error) {
if (!error) {
if ([_lasttime isEqualToString:@"0"]) {
[self.dataArr removeAllObjects];
}
[self.dataArr addObjectsFromArray:model.result.newslist];
}
completionHandle(error);
}];
}
-(void)getMoreDataCompletionHandle:(CompletionHandle)completionHandle
{
self.lasttime = self.newlistModel.lasttime;
return [self getDataFromNetCompleteHandle:completionHandle];
}
-(CarHomeResultNewslistModel *)newslistModelForRowInResultModel:(NSInteger)row
{
self.newlistModel = self.dataArr[row];
return self.dataArr[row];
}
-(void)refreshDataCompletionHandle:(CompletionHandle)completionHandle
{
self.lasttime = @"0";
return [self getDataFromNetCompleteHandle:completionHandle];
}
-(NSURL *)newlistIconURLWithForRow:(NSInteger )row;
{
return [NSURL URLWithString:[self newslistModelForRowInResultModel:row].smallpic];
}
-(NSString *)titleWithForRow:(NSInteger )row;
{
return [self newslistModelForRowInResultModel:row].title;
}
-(NSString *)replycountWithForRow:(NSInteger )row;
{
return [NSString stringWithFormat:@"%ld",[self
newslistModelForRowInResultModel:row].replycount];
}
-(NSString *)intacttimeWithForRow:(NSInteger )row;
{
return [self newslistModelForRowInResultModel:row].intacttime;
}
將 View 的業務邏輯分離到 VewModel 之後,可複用性大大提高了。
第四,將 ViewModel 寫完之後,我們就開始考慮 ViewController 和介面的佈局等。
關於檢視部分就不多說了。下面附上 Demo 地址:
https://github.com/mengruirui/MVVMDemo
目前說的就這麼多,如果有疑問,歡迎大家來討論
相關文章
- Angular專案目錄結構詳解Angular
- vue專案目錄結構Vue
- C++ 專案目錄結構C++
- Android Studio專案目錄結構簡介Android
- ionic3 angular專案目錄結構解析Angular
- DedeCMS模板目錄的檔案目錄結構
- nginx 詳解 - 詳細配置說明Nginx
- nginx 詳解 – 詳細配置說明Nginx
- Linux sed命令詳細說明Linux
- Linux 目錄結構及詳細操作Linux
- 初探AngularJS6.x---目錄結構說明AngularJS
- winscp操作說明,winscp操作說明的詳細解讀
- iOS MVC、MVVM、MVP架構模式淺淺析iOSMVCMVVMMVP架構模式
- VNC安裝配置詳細說明VNC
- Flask-Limit使用詳細說明FlaskMIT
- 檔案的邏輯結構、檔案目錄
- 阿里雲物聯網平臺專用工具詳細說明阿里
- DKhadoop框架結構說明Hadoop框架
- 微機結構說明
- flowable 表結構說明
- [譯]iOS架構模式——解密MVC、MVP、MVVM和VIPERiOS架構模式解密MVCMVPMVVM
- jpa 方法 命名規則 詳細說明
- linux檔案目錄結構彙總!Linux學習Linux
- iOS沙盒檔案目錄介紹iOS
- 【PG結構】Postgresql資料庫資料目錄說明SQL資料庫
- vue-cli 目錄結構詳細講解Vue
- Oracle 官方文件 結構說明Oracle
- 加強堆結構說明
- 鴻蒙前端開發1-檔案目錄結構鴻蒙前端
- Linux學習之linux檔案目錄結構彙總Linux
- JQuery Datatables Columns API 引數詳細說明jQueryAPI
- 寬頻路由器的詳細說明路由器
- 細說IOS工程架構(持續更新)iOS架構
- 自動化檔案目錄結構生成工具——filemap.jsJS
- Mac使用tree命令生成檔案目錄結構,超簡單!Mac
- Angular4學習(初始檔案目錄結構解讀)Angular
- Go基礎學習記錄 – 編寫Web應用程式 – 重新調整專案目錄結構(一)GoWeb
- Python Matplotlib add_subplot 和 subplots_adjust詳解及程式碼詳細說明 配圖片說明Python
- zblog模板文章釋出時間格式詳細說明