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

来源:http://www.cnblogs.com/TingyunAPM/archive/2016/06/01/5549699.html
-Advertisement-
Play Games

➠更多技術乾貨請戳:聽雲博客 我們在做項目的時候,會經常用到各種設計模式,最常見的要數 MVC (模型,視圖,控制器)了。但是,今天我們要說的是另一種設計模式——MVVM。 所以 MVVM 到底是什麼?下麵,我們將結合代碼,說明 MVVM 設計模式以及項目目錄結構。 一、MVVM 模式介紹 MVVM ...


更多技術乾貨請戳:聽雲博客

我們在做項目的時候,會經常用到各種設計模式,最常見的要數 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 的業務邏輯

如圖:

11011.png 

那麼MVVM 模式有什優點呢?

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

2. 可重用性。你可以把一些視圖邏輯放在一個ViewModel裡面,讓很多view重用這段視圖邏輯。

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

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

二、項目目錄結構

我認為一個合理的項目目錄結構首先應該是讓人一目瞭然的,讓人一眼看上去就能大概瞭解目錄的職責,瞭解每個文件夾下的內容是做什麼的,而且容易應對新的改變方便後續添加功能或者擴展。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

目前說的就這麼多,如果有疑問,歡迎大家來討論。

 

原文鏈接:http://blog.tingyun.com/web/article/detail/650


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 效果圖 1. 導入 .aar 2. fragment_line_chart.xml ...
  • 最近看視頻瞭解了一下應用程式的啟動原理,這裡就做一個博客和大家分享一下,相互討論,如果有什麼補充或不同的意見可以提出來! 1、程式入口 眾所周知,一個應用程式的入口一般是一個 main 函數,iOS也不例外,在工程的 Supporting Files 文件夾中你可以找到main.m,他就是程式的入口 ...
  • cordova plugin add org.apache.cordova.vibration cordova plugin add https://github.com/katzer/cordova-plugin-local-notifications cordova plugin add cor ...
  • MVP是從MVC演變過來的。M即Model層負責提供數據,V即View層負責顯示,P指Presenter層負責邏輯處理。在MVP中View不直接使用Model,它們通過Presenter來通信,它們之間的交互都在Presenter內部進行。實現了View和Model的耦合。<! more 在MVP中 ...
  • 當原生控制項不符合需求,並且進行拓展或組合也無濟於事的時候,就需要自己來繪製一個控制項了。<! more 全新的View 通過源碼,我們可以看到所有的控制項都是通過繼承View類來實現的。所以,我們要創建全新的控制項,也是要通過繼承View來實現。 案例 案例同樣來自《Android群英傳》。 繪製圓弧文本 ...
  • 1. 註意 key 一定要在activity 前面 ...
  • 序列化是什麼 序列化是指 把Java對象轉換為位元組序列並存儲到一個存儲媒介的過程 。反之, 把位元組序列恢復為Java對象的過程 則稱之為反序列化。<! more 為什麼要序列化 Java對象存在的一個前提是JVM有在運行,因此,如果JVM沒有運行或者在其他機器的JVM上是不可能獲取到指定的Java對 ...
  • 1.在values建立attrs.xml,寫出你需要的屬性: 2.創建view類並實現所需要的業務,具體看代碼,代碼中寫的很詳細: 3.在xml中引用: 當然,在實際操作中OnTouch事件一般是放在activity或者Fragment裡面的,只需在代碼中複製出去即可 最後十分感謝鴻洋大神,讓我們學 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...