MVVM 模式下iOS項目目錄結構詳細說明

原文出自【聽雲技術博客】:http://blog.tingyun.com/web/article/detail/650javascript

咱們在作項目的時候,會常常用到各類設計模式,最多見的要數 MVC (模型,視圖,控制器)了。可是,今天咱們要說的是另外一種設計模式——MVVM。 因此 MVVM 究竟是什麼?下面,咱們將結合代碼,說明 MVVM 設計模式以及項目目錄結構。html

1、MVVM 模式介紹 java

MVVM 是 Model-View-View Model 的縮寫,MVVM 聽起來好像很複雜的樣子,但它本質上就是MVC 的改進版。MVVM 就是將其中的View 的狀態和行爲抽象化,讓咱們將視圖 UI 和業務邏輯分開。固然這些事 ViewModel 已經幫咱們作了,它能夠取出 Model 的數據同時幫忙處理 View 中因爲須要展現內容而涉及的業務邏輯。在 iOS 中使用 MVVM 能夠將 ViewController 中處理 Mode 的業務邏輯所有交由 ViewModel,讓 ViewController 再也不顯的特別臃腫。git

MVVM模式是經過下面三個核心組件組成,每一個都有它本身所要處理的事情:github

  • Model -數據模型web

  • View – 用來將Model 的內容顯示出來json

  • ViewModel - 扮演「View」和「Model」之間的使者,幫忙處理 View 的業務邏輯設計模式

如圖:api

11011.png

那麼MVVM 模式有什優勢呢?數組

1. 低耦合。視圖(View)能夠獨立於Model變化和修改,一個ViewModel能夠綁定到不一樣的"View"上,當View變化的時候Model能夠不變,當Model變化的時候View也能夠不變。

2. 可重用性。你能夠把一些視圖邏輯放在一個ViewModel裏面,讓不少view重用這段視圖邏輯。

3. 獨立開發。開發人員能夠專一於業務邏輯和數據的開發(ViewModel),設計人員能夠專一於頁面設計。

4. 可測試。界面素來是比較難於測試的,而使用MVVM的一大好處是咱們能夠很容易對 ViewModel 進行單元測試

2、項目目錄結構

我認爲一個合理的項目目錄結構首先應該是讓人一目瞭然的,讓人一眼看上去就能大概瞭解目錄的職責,瞭解每一個文件夾下的內容是作什麼的,並且容易應對新的改變方便後續添加功能或者擴展。iOS 項目目錄結構不必定適合全部人的想法,關鍵是看你但願用一個結構解決什麼問題。對於我本身來說,我認爲一個良好的項目目錄結構,要達到如下兩個目的:

1)使項目更適合於團隊開發,可以下降耦合、便於任務的劃分和代碼的整合管理。

2)使項目可以積累出更多可複用的代碼和架構。

這個結構會在不斷遇到問題解決問題的過程當中權衡、進化,在這個過程最重要的是可以保持:

1)主幹簡潔。主幹上防止過分劃分,過分劃分會讓代碼放在這個目錄下也能夠,放在另外一個目錄下好像也行,容易混亂。

2)分支開放。不對過於細節的分支作嚴格規範,能夠發揮你們的靈活性和創造性。

下面咱們來看,我構建的項目目錄結構,以下圖所示。

0015.png

Define —— 用於存放咱們設置的一些宏(#define)。

Model —— 用於存放模型類(數據模型)。

NetworkManager —— 用於存放網絡請求類

Resources —— 用於存放資源 例如xib,storyboard,圖片,plist,音頻,視頻

Util —— 用於存放咱們定義的分類和擴展或者工具類

Vendors —— 用於存放第三方框架或者第三方SDK文件

View —— 用於存放視圖類

ViewControllers —— 用於存放視圖控制器類

ViewModel —— 用於存放視圖模型類,及處理 View 和 Model 之間的業務邏輯。

總體項目的運行流程是:

ViewController->向ViewModel請求數據->ViewModel->向網絡請求數據->須要數據解析類型負責解析

項目編寫的順序是,須要先完成最底層的依賴,而後層層向上。

咱們經過一個小 Demo,來說解咱們的目錄結構。

第一,咱們須要在 Model 中寫好數據模型,根據接口提供的數據,進行解析。在這個 Demo 中,解析咱們用到了第三方框架MJExtension。

0016.png

在這兒咱們須要注意的是, Model(模型)類的命名規則。我認爲,咱們須要一層一層的命名,體現出包含關係。這樣更清晰的表示了數據模型。

如:

@class CarHomeResultModel,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<CarHomeResultNewslistModel *> *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 <Foundation/Foundation.h>
#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 文件中調用改類方法,用斷點調試的方式,能夠看到請求和解析是否成功。

0017.png

第三,咱們在作完網絡請求後,就要根據產品所給的 UI 界面的設計稿,要開始作 ViewModel 層了,這一層主要是 View 顯示的一些業務邏輯。

0018.png

代碼以下:

在 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

目前說的就這麼多,若是有疑問,歡迎你們來討論。

相關文章
相關標籤/搜索