`

MVVM 模式下iOS项目目录结构详细说明

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 的业务逻辑

如图:

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

目前说的就这么多,如果有疑问,欢迎大家来讨论。

0
0
分享到:
评论

相关推荐

    iOS MVVM设计模式

    在MVVM模式下,ViewModel通常负责网络请求。对AFNetworking进行二次封装,可以创建一个网络服务层,提供更简洁、易用的API,如GET、POST请求接口,以及处理网络错误、缓存策略等。这有助于保持代码的整洁,同时也...

    IOS MVVM小Demo

    在这个名为"IOS MVVM小Demo"的项目中,我们可以深入探讨MVVM模式在iOS应用开发中的应用。 **MVVM模式概述** MVVM模式由三个主要部分组成:模型(Model)、视图(View)和视图模型(ViewModel)。模型负责数据的存储...

    ios-初始MVVM项目架构.zip

    MVVM模式以其清晰的职责划分、良好的测试特性以及易于解耦的优势,深受开发者喜爱。本文将详细介绍如何理解和使用这种架构。 一、MVVM架构基础 MVVM架构源于微软的WPF和Silverlight开发,后来被广泛应用到iOS和...

    iOS OC - MVVM开发模式

    MVVM模式在iOS开发中带来了许多好处,如代码的可读性、可测试性和可维护性。通过合理的架构设计,开发者可以更高效地完成任务,同时保持代码的整洁和模块化。实践MVVM需要理解其核心原理,并选择合适的工具和库,...

    iOS计算器源码MVVM设计模式.zip

    总之,"iOS计算器源码MVVM设计模式"项目提供了一个很好的学习平台,帮助开发者掌握MVVM模式,提升iOS应用开发能力。通过分析源码,你可以深入了解如何在实际项目中运用设计模式,优化代码结构,以及如何实现一个功能...

    iOS MVVM开发模式 配合 RAC 信号绑定框架让开发更高效有趣.zip

    MVVM模式强调解耦视图(View)和业务逻辑(ViewModel),使开发者能够更加专注于数据的处理,而无需关心视图的细节。结合ReactiveCocoa(RAC)这种响应式编程框架,可以进一步提升开发效率,实现数据流的自动管理。 ...

    iOSMVVM设计

    MVVM模式源自微软的WPF和XAML平台,但在iOS和Android开发中也逐渐流行起来,特别是在Swift和Kotlin这样的现代编程语言中。 **MVVM模式的构成** 1. **Model(模型)**:模型层是应用程序的核心部分,负责处理业务...

    iOS mvvm demo

    4. **代码组织**:体验MVVM模式如何使代码结构更清晰,便于维护和扩展。 总之,"iOS mvvm demo"是一个理想的起点,让开发者了解并实践MVVM设计模式,提升iOS应用的开发效率和代码质量。通过深入研究这个示例,你...

    ios-MVVM框架.zip

    在iOS开发中,MVVM(Model-View-ViewModel)是一种设计模式,被广泛应用于构建可维护性和可测试性更强的应用程序。与传统的MVC(Model-View-...在实际项目中,可以根据需求选择适合的工具和实践来落地MVVM模式。

    mvvm 设计模式的 model

    在"MVVM-master"这个压缩包文件中,很可能包含了一个关于MVVM模式的示例项目,通过阅读和分析项目源码,开发者可以深入理解MVVM模式在实际开发中的应用,学习如何在iOS项目中有效地使用MVVM来提升开发效率和代码质量...

    iOS 组件化开发项目架构设计,结合 MVVM 设计模式 + RAC 数据绑定 + Pod 组件管理.zip

    - 为了更好地利用此架构,开发者需要熟悉MVVM模式的实现,理解RAC的信号处理机制,以及如何编写和管理Pod组件。 - 在实际开发中,要注重组件的边界划分,确保各组件间的通信简洁高效,避免不必要的耦合。 综上所...

    ios-一个简单的MVVM框架.zip

    这个"ios-一个简单的MVVM框架.zip"文件可能包含了一个演示或实现MVVM模式的基本结构,帮助开发者更好地理解和应用这一模式。 MVVM模式的核心理念是将业务逻辑、数据和界面视图进行分离,以提高代码的可测试性和可...

    ios项目基本架构

    以下是对“iOS项目基本架构”这一主题的详细阐述。 1. **Xcode工程结构**:Xcode是iOS开发的主要工具,其工程文件包含了项目的所有资源和源代码。一个标准的Xcode工程包括了`Project.pbxproj`(项目配置信息)、`...

    MVVM和MVP模式demo

    1.mvvm文件可能包含了使用MVVM模式编写的代码示例,展示了如何创建ViewModel,以及如何通过数据绑定实现View与ViewModel的通信。2.MVP文件则可能展示了MVP模式的实现,包括Presenter如何与View和Model交互。 学习并...

    ios-MVVM KVO 购物车 (一处计算总价钱).zip

    在这个"ios-MVVM KVO 购物车 (一处计算总价钱).zip"项目中,开发者使用MVVM模式来实现一个购物车功能,其中包括了键值观察(Key-ValueObserving, KVO)技术来实时更新购物车总价,并处理了键盘交互以及单元格的...

    MVVM模式简单demo

    4. **数据绑定**:在MVVM模式下,视图和ViewModel之间的通信通常通过数据绑定完成。例如,使用KVO(Key-Value Observing)或者ReactiveCocoa等库,使得当ViewModel中的数据发生变化时,视图能自动更新。 5. **分层...

    Application of Protocol-Oriented MVVM Architecture in iOS Development.pdf

    论文详细说明了如何构建一个遵循POP原则的MVVM架构,包括如何利用协议来定义数据模型和服务接口,以及如何通过协议扩展来简化代码并提供更灵活的结构。 通过分析,作者强调了在移动应用开发中采用灵活、可维护架构...

    ios-MVVM-Demo-Swift.zip

    这个"ios-MVVM-Demo-Swift.zip"压缩包提供了一个Swift实现的MVVM模式示例,帮助开发者更好地理解和实践这一模式。 MVVM模式的核心思想是将业务逻辑和界面展示分离,它主要由三个部分组成: 1. **Model(模型)**:...

    ios-mvvm架构简单实用.zip

    这个名为"ios-mvvm架构简单实用.zip"的压缩包文件,显然是一个关于如何使用MVVM模式来实现一个电影列表页面的示例项目,名为"mvvmDemo"。 MVVM架构的核心概念是将视图(View)、模型(Model)和视图模型(ViewModel...

    什么是 MVVM 模式1

    总的来说,MVVM模式通过ViewModel的引入,优化了MVC模式中的Controller角色,提升了代码的可读性和可维护性,尤其适合需要大量UI交互和动态数据更新的应用场景。在iOS、Android以及React-Native等平台上,MVVM已经...

Global site tag (gtag.js) - Google Analytics