# 谈谈MVVM和链式网络请求架构

语言：zh-CN
原文：[谈谈MVVM和链式网络请求架构](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao41>)
中文：[中文 HTML](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao41>)
English: [English HTML](<https://allluckly.cn/en/journal/2016-04-26-mvvm-and-chained-network-request-architecture/>)
发布于: 2016-04-26T00:00:00+08:00
内容更新于: 2016-04-26T00:00:00+08:00
署名：WzxJiang

从控制器业务代码膨胀出发，用登录判断展示 Model、ViewModel 与 ViewController 的职责拆分，再介绍 ReactiveCocoa 和链式网络请求封装如何组织业务调用。

原文署名WzxJiang，Bison 编辑收录。

[原文所列来源](<http://www.jianshu.com/p/06bab2058c53>)

**阅读前** 这是 2015–2017 年间的技术记录，本次整理未重新运行验证示例。涉及的 API、依赖和工具版本请结合发表日期阅读。

> "前一段时间一直在学习iOS的架构。为什么呢？ 公司的架构一直是MVC，当我们正式上线的时候，项目已经有了超十万行代码。主要的VC一般都有2000行代码以上。 关键是，目前版本我们只做了三分之一的业务逻辑… 所以，架构重构吧。"

编辑：[Bison](<https://allluckly.cn/>)  
 投稿：[WzxJiang](<http://www.jianshu.com/p/06bab2058c53>)

## 正文

## MVVM

MVVM: Model-View-ViewModel  
 MVVM其实是MVC的进化版，它将业务逻辑从VC中解耦到ViewModel，来实现VC大’瘦身’。

用代码解释吧！

做一个简单的登录判断:

创建LoginViewModel(逻辑处理),LoginModel(只放数据),LoginViewController。 这里不用LoginView是为了让初学者能更好的把精力集中在用ViewModel解耦上。

当然要是你这些都明白，你可以直接看[Wzxhaha/RandomerFramework](<https://github.com/Wzxhaha/RandomerFramework>),这是我在做的独立项目Randomer的基本架构(SubClasses+Protocol+MVVM+RAC)以及它的登录注册模块。另外,感谢[王隆帅的这篇文章](<http://www.jianshu.com/p/3beb21d5def2>)为我打开了新世界的大门。

### 在LoginModel中加入方法

```
//.h
- (instancetype)initWithUserName:(NSString *)username password:(NSString *)password;

@property (nonatomic,copy,readonly)NSString * username;
@property (nonatomic,copy,readonly)NSString * password;
```

```
//.m
- (instancetype)initWithUserName:(NSString *)username password:(NSString *)password {
    if (self = [super init]) {
        _username = username;
        _password = password;
    }
    return self;
}
```

这个没什么好讲的，就是给Model加一个初始化方法。

### 在LoginViewModel中加入方法

```
#import "PersonModel.h"

- (instancetype)initWithPerson:(PersonModel *)person;
@property (nonatomic,assign,readonly)BOOL canLogin;
- (instancetype)initWithPerson:(PersonModel *)person {
    if (self = [super init]) {
     //在这做你绑定model后的处理
      _canLogin = [self valiCanLoginWithUserName:person.username password:person.password];
    }
    return self;
}

- (BOOL)valiCanLoginWithUserName:(NSString *)username password:(NSString *)password {
    if (username.length && password.length) {
        return YES;
    } else {
        return NO;
    }
}
```

给ViewModel添加个绑定Model的初始化方法，以及判断帐号密码是否有效的方法。

## 然后VC(或者View)就可以直接这样获得判断后的结果

```
PersonModel * person = [[PersonModel alloc]initWithUserName:@"10" password:@"10"];
PersonViewModel * viewModel = [[PersonViewModel alloc]initWithPerson:person];

NSLog(@"%d",viewModel.canLogin);
```

简单的功能的时候没什么，当你处理复杂的逻辑判断的时候，MVVM会有巨大优势。

顺便讲一下ReactiveCocoa,我之所以这么推崇MVVM,主要就是因为RAC和MVVM简直太配了！

## ReactiveCocoa

RAC具有函数式编程和响应式编程的特性，要是对编程思想不熟的可以看我的[WZXProgrammingIdeas](<https://github.com/Wzxhaha/WZXProgrammingIdeas>)

RAC最大的用处就是能监听到各个事件，RAC把这个叫做信号流，然后接受信号通过block回调，里面大量的使用了block，所以一定要用好@weakify(self)和@strongify(self)。

## 为什么说RAC和MVVM太配了？

MVVM是把方法解耦到ViewModel，但是还是要VC(V)调用的，那么判断什么时候调用的逻辑还是会复杂。

而RAC解决了这个问题,它负责监听事件,然后调用ViewModel来进行逻辑判断。

例如:

```
   [[_registerBtn rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(id x) {
        @strongify(self)
        [self.viewModel toRegisterWithType:Register];
    }];

    [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(id x) {
        @strongify(self)
        [self.viewModel loginWithUserName:self.usernameTextField.text password:self.usernameTextField.text Success:^(id response) {
        } failure:^{
            SHOW_ERROR(@"错误", @"账号或密码错误")
        } error:^(NSError *error) {
            SHOW_ERROR(@"错误", @"网络连接失败")
        }];
    }];
```

RAC监听了登录和注册按钮,使得代码简洁,而且结构十分紧凑。

Demo的话还是看这个吧[Wzxhaha/RandomerFramework](<https://github.com/Wzxhaha/RandomerFramework>)

或者简单版的[WZXRACDemo](<https://github.com/Wzxhaha/WZXRACDemo>)

## 链式网络请求框架

### 为什么封装WZXNetworking

这是一个容错性非常吓人的框架。

```
 [[WZXNetworkManager manager].setRequest(@"http://192.168.1.40:8001").RequestType(POST).HTTPHeader(nil).Parameters(nil).RequestSerialize(RequestSerializerHTTP).ResponseSerialize(ResponseSerializerJSON) startRequestWithSuccess:^(id response) {

        NSLog(@"success");
    } failure:^{

        NSLog(@"failure");
    }];
```

在这里除了.setRequest(url)和startRequestWithSuccess failure方法，其他都是非必要的。

你可以这样:

```
[[WZXNetworkManager manager].setRequest(@"http://192.168.1.40:8001") startRequestWithSuccess:^(id response) {

        NSLog(@"success");
    } failure:^{

        NSLog(@"failure");
    }];
```

链式在参数和参数的选择很多的情况或者很有可能改动的情况下展现了惊人的优势。因为，它的改动十分方便，只不过添加或者修改一个方法。

打个比方：

### 换成集中式API封装应该是这样的:

```
- (void)GET:(NSString *)url
        parameters:(id)Parameters
        success:(SuccessBlock)success
        failure:(FailureBlock)failure;
```

当你要添加一个Version属性做API版本判断的时候，你能怎么办？只能重写方法，在方法中加入一个Version参数，然后所有使用的网络请求都要改变方法。

换成分布式API封装我们则不考虑对比了..

```
GeneralAPI *apiGeGet            = [[GeneralAPI alloc] initWithRequestMethod:@"get"];
apiGeGet.apiRequestMethodType      = RequestMethodTypeGET;
apiGeGet.apiRequestSerializerType  = RequestSerializerTypeHTTP;
apiGeGet.apiResponseSerializerType = ResponseSerializerTypeHTTP;
[apiGeGet setApiCompletionHandler:^(id responseObject, NSError * error) {
    NSLog(@"responseObject is %@", responseObject);
    if (error) {
        NSLog(@"Error is %@", error.localizedDescription);
    }
}];
[apiGeGet start];
```

这样的结构是否太松散？

## 再换成WZXNetworking

我们要做的只是再添加一个方法和一个成员变量，然后在原有方法后面加一个.method()

```
- (WZXNetworkManager * (^) (id some))method {
  return ^WZXNetworkManager (id some) {
     self.XXX = some
     return self;
  }
}
```

```
[[WZXNetworkManager manager].setRequest(@"http://192.168.1.40:8001").method(some) startRequestWithSuccess:^(id response) {

        NSLog(@"success");
    } failure:^{

        NSLog(@"failure");
    }];
```

代码放这：[WZXNetworking](<https://github.com/Wzxhaha/WZXNetworking>)  
 至于链式是怎么实现的，还是看那个[WZXProgrammingIdeas](<https://github.com/Wzxhaha/WZXProgrammingIdeas>)

## 最后

在[我自己的博客](<http://wzxjiang.com>)观看效果更佳.  
 有任何疑问和建议都可以在评论告诉我.  
 如果我这有你喜欢的项目，欢迎点✨.  
 我的[GitHub](<https://github.com/Wzxhaha>)

---

好文推荐：[iOS开发之AES+Base64数据混合加密与解密](<https://allluckly.cn/aes/AES>)

