架构模式

本人已迁移博客至掘进,以后会在掘进平台更新最新的文章也会有更多的干货,欢迎大家关注!!!https://juejin.im/user/588993965333309

 

今天讨论这个话题是比较大的,是关于iOS的架构方面的问题,随着开发的时间的不断增加,经过的项目也会不断地增加。很多时候会看到同事说,这是哪一个sb写的代码,为什么要放到这里,等等话语。说的这些问题,本篇博客我们来讲解MVC代码存在的耦合性慢慢衍生出MVP架构模式,然后再讲述MVVM模式以及RAC的介绍,主要是带着代码去讲解,希望通过这篇博客,大家可以增加对架构模式的理解,以及可以自由的选择架构模式。

  1.  MVC架构思路
  2. MVC封装和解耦
  3. MVP架构思路
  4. MVVM架构思路

一、MVC架构思路

在MVC,MVP以及MVVM中,大家可能对MVC架构模式更熟悉!下面是大家非常熟悉的一张图。

在代码里面体验可能是这样,下面我们以一个列表展示MVC方式的显示。

 

举例:下面我们要实现下面的简单界面!

1.1 Controller

 在控制器Controller中,完成对数据的请求,以及数据源的展示!


- (void)loadData{ NSArray *temArray = @[ @{@"name":@"zxy1",@"imageUrl":@"http://hello",@"num":@"99"}, @{@"name":@"zxy2",@"imageUrl":@"http://hello",@"num":@"99"}, @{@"name":@"zxy3",@"imageUrl":@"http://hello",@"num":@"99"}, @{@"name":@"zxy4",@"imageUrl":@"http://hello",@"num":@"59"}, @{@"name":@"zxy5",@"imageUrl":@"http://hello ",@"num":@"49"}]; for (int i = 0; i<temArray.count; i++) { Model *m = [Model modelWithDictionary:temArray[i]]; [self.dataArray addObject:m]; } } #pragma mark - tableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return self.dataArray.count; }

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    MVCTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuserId forIndexPath:indexPath];

    cell.model = self.dataArray[indexPath.row];

    return cell;

}

 

在viewDidload里面完成初始化!

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadData];
    [self.view addSubview:self.tableView];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    
}

上面的cell的cell.model = self.dataArray[indexPath.row]会出发view的setModel方法,进行赋值!

1.2 View

view里面包括数据model,完成对显示层的赋值!

#import <UIKit/UIKit.h>
#import "Model.h"

@interface MVCTableViewCell : UITableViewCell
@property (nonatomic, strong) UIButton *subBtn;
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *numLabel;
@property (nonatomic, strong) UIButton *addBtn;
@property (nonatomic, assign) int num;
@property (nonatomic, strong) NSIndexPath *indexPath;
@property (nonatomic, strong) Model *model;

@end

在view通过setModel:进行赋值

- (void)setupUI{
    [self.contentView addSubview:self.nameLabel];
    [self.contentView addSubview:self.subBtn];
    [self.contentView addSubview:self.numLabel];
    [self.contentView addSubview:self.addBtn];
    self.num = 0;
}

- (void)setModel:(Model *)model{
    
    _model = model;
    self.nameLabel.text = model.name;
    self.numLabel.text  = model.num;
}

 

1.3 Model

在请求的数据中,声明属性

@interface Model : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *imageUrl;
@property (nonatomic, copy) NSString *num;

@end

 

上面就是简单的MVC的代码结构,我相信很多人都写过类似的代码!但是上面的代码对于大工程项目来说,耦合性太高,下面来讲述MVC封装和解耦!

 

二、MVC封装和解耦

  而苹果愿景中的MVC是这样的。

但在上面实际的MVC操作中,比如上面

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    MVCTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuserId forIndexPath:indexPath];
    cell.model = self.dataArray[indexPath.row];
    return cell;
}

cell.model = self.dataArray[indexPath.row];这句代码不是不对,而是不好,cell---Model造成了强引用,耦合性特别高。
仔细看上面MVC结构的代码,数据还是自己自定义数据,还没有加入网络请求以及业务处理,就看出来Controller就会变得非常臃肿,如下图:


对于tableView或者CollectionView的数据源代码看起来非常臃肿,下面我们针对数据源进行封装以及解耦。

针对数据源方法,我们新建立一个LMDataSource类,用于数据源方法的封装

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

//声明一个block,用于回调cell,model,下标 typedef
void (^CellConfigureBefore)(id cell, id model, NSIndexPath * indexPath); @interface LMDataSource : NSObject<UITableViewDataSource,UICollectionViewDataSource> @property (nonatomic, strong) NSMutableArray *dataArray;; //自定义 - (id)initWithIdentifier:(NSString *)identifier configureBlock:(CellConfigureBefore)before; // @property (nonatomic, strong) IBInspectable NSString *cellIdentifier; @property (nonatomic, copy) CellConfigureBefore cellConfigureBefore; - (void)addDataArray:(NSArray *)datas; - (id)modelsAtIndexPath:(NSIndexPath *)indexPath; @end

在LMDataSource.m中进行实现相应代码:

#import "LMDataSource.h"

@implementation LMDataSource

- (id)initWithIdentifier:(NSString *)identifier configureBlock:(CellConfigureBefore)before {
    if(self = [super init]) {
        _cellIdentifier = identifier;
        _cellConfigureBefore = [before copy];
    }
    return self;
}

- (void)addDataArray:(NSArray *)datas{
    if(!datas) return;
    if (self.dataArray.count>0) {
        [self.dataArray removeAllObjects];
    }
    [self.dataArray addObjectsFromArray:datas];    
}

- (id)modelsAtIndexPath:(NSIndexPath *)indexPath {
    return self.dataArray.count > indexPath.row ? self.dataArray[indexPath.row] : nil;
}

#pragma mark UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return !self.dataArray ? 0: self.dataArray.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath]; id model = [self modelsAtIndexPath:indexPath]; if(self.cellConfigureBefore) { self.cellConfigureBefore(cell, model,indexPath); } return cell; } #pragma mark UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ return !self.dataArray ? 0: self.dataArray.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:self.cellIdentifier forIndexPath:indexPath]; id model = [self modelsAtIndexPath:indexPath]; if(self.cellConfigureBefore) { self.cellConfigureBefore(cell, model,indexPath); } return cell; } - (NSMutableArray *)dataArray{ if (!_dataArray) { _dataArray = [NSMutableArray arrayWithCapacity:5]; } return _dataArray; } @end

然后我们针对请求数据进行封装到另一个类Util中

#import <Foundation/Foundation.h>
#import "Model.h"
#import <YYKit.h>

@interface Util : NSObject
@property (nonatomic, strong) NSMutableArray    *dataArray;
@end

实现方法

#import "Util.h"

@implementation Util

- (instancetype)init{
    self = [super init];
    if (self) {
        
        [self loadData];
    }
    return self;
}

- (void)loadData{

    NSArray *temArray =
    @[
      @{@"name":@"zxy1",@"imageUrl":@"http://hello",@"num":@"99"},
      @{@"name":@"zxy2",@"imageUrl":@"http://hello",@"num":@"99"},
      @{@"name":@"zxy3",@"imageUrl":@"http://hello",@"num":@"99"},
      @{@"name":@"zxy4",@"imageUrl":@"http://hello",@"num":@"59"},
      @{@"name":@"zxy5",@"imageUrl":@"http://hello ",@"num":@"49"}];
    for (int i = 0; i<temArray.count; i++) {
        Model *m = [Model modelWithDictionary:temArray[i]];
        [self.dataArray addObject:m];
    }
}

#pragma mark - lazy

- (NSMutableArray *)dataArray{
    if (!_dataArray) {
        _dataArray = [NSMutableArray arrayWithCapacity:10];
    }
    return _dataArray;
}

@end

上面已经将数据源方法和请求数据封装了,所以在Controller就会变成

#import "MVCViewController.h"
#import "LMDataSource.h"
#import "MVCTableViewCell.h"
#import "Util.h"
#import "Model.h"
#import <YYKit.h>

static NSString *const reuserId = @"reuserId";

@interface MVCViewController ()<UITableViewDelegate,UITableViewDataSource>
@property (nonatomic, strong) UITableView       *tableView;
@property (nonatomic, strong) NSMutableArray    *dataArray;
@property (nonatomic, strong) LMDataSource      *dataSource;
@property (nonatomic, strong) Util           *pt;

@end

@implementation MVCViewController

/**
 1: 耦合性强
 2: cell服用  UI <---> model (双向绑定)
 3: VC 好重 : adapter
 */


- (void)viewDidLoad {
    [super viewDidLoad];
    
//    [self loadData];
    self.dataSource = [[LMDataSource alloc] initWithIdentifier:reuserId configureBlock:^(MVCTableViewCell *cell, Model *model, NSIndexPath *indexPath) {
        cell.model = model;
    //cell.numLabel.text = model.num;
    //cell.nameLabel.text = model.name;
}];
self.pt
= [[Util alloc] init]; [self.dataSource addDataArray:self.pt.dataArray]; self.view.backgroundColor = [UIColor whiteColor]; [self.view addSubview:self.tableView]; self.tableView.dataSource = self.dataSource; // self.tableView.delegate = self; // self.tableView.dataSource = self; // } //- (void)loadData{ // // NSArray *temArray = // @[ // @{@"name":@"zxy1",@"imageUrl":@"http://hello",@"num":@"99"}, // @{@"name":@"zxy2",@"imageUrl":@"http://hello",@"num":@"99"}, // @{@"name":@"zxy3",@"imageUrl":@"http://hello",@"num":@"99"}, // @{@"name":@"zxy4",@"imageUrl":@"http://hello",@"num":@"59"}, // @{@"name":@"zxy5",@"imageUrl":@"http://hello ",@"num":@"49"}]; // for (int i = 0; i<temArray.count; i++) { // Model *m = [Model modelWithDictionary:temArray[i]]; // [self.dataArray addObject:m]; // } //} #pragma mark - tableViewDataSource //- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ // return self.dataArray.count; //} // // //- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ // MVCTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuserId forIndexPath:indexPath]; // cell.model = self.dataArray[indexPath.row]; // return cell; //} // // //#pragma mark - tableViewDelegate // //- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ // [tableView deselectRowAtIndexPath:indexPath animated:YES]; //}

发现原本Controller很繁杂的代码,现在就会变得非常少。但是发现View和Model还是没有彻底解耦。

下面我们讲述另外一种架构模式。

 

三、MVP架构思路

3.1 MVP概括(面向协议(接口)编程)

MVP(Model-View-Presenter),是MVC架构模式的变形。因为UI是经常变化的,同样的数据可能有很多种显示方式,业务层也是比较容易变化的,我们希望UI的逻辑及业务逻辑和数据隔离出来,而MVP就是个很好地选择。

Presenter代替了Controller,但是比Controller担任更多的任务。Presenter处理事件,执行相应的逻辑而这些逻辑映射到Model的Command用来操作Model,处理UI如何工作的代码都会放到了Utiler上。

Model和View使用Observer观察者模式进行通信;而Presenter和View是使用Mediator模式进行沟通;Presenter使用Command模式来操作Model。

 

对待处理流程中:

MVC中,用户的请求会先到达控制器Controller,控制器Controller从Model里面获取数据,然后选中合适的View,将数据呈现在View上;但是在MVP中,用户的请求首先会到达View中,View进行传递请求到指定的Presenter,Presenter从Model获取后,再把处理结果通过接口传到View上。

下面是MVP(Model-View-Utiler)交互图

在上面的MVC 中,数据库以及网络都会交给UIViewController,这样不便于团队开发,耦合性很高;MVP的诞生也是为了解决这个问题,将模块独立,数据是数据,UI是UI,完全隔离,做UI开发的专心做UI开发,数据库维护的专门维护数据库,我们就通过P层(中介将UI层和数据层关联),类似于下面:

 3.2 下面将讲述项目MVP架构

3.2.1 Controller

在控制器里面完成主要view的加载。

#import <UIKit/UIKit.h>
@interface MineController : UIViewController

@end


#import "MineController.h"
#import "MineMainView.h"
@interface MineController ()
@property (nonatomic,strong)MineMainView *mainView;
@end

@implementation MineController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.mainView];
}
#pragma mark - lazy load
- (MineMainView *)mainView{
    if (!_mainView) {
        _mainView = [[MineMainView alloc] initWithFrame:CGRectZero];
    }
    return _mainView;
}
@end

声明一个协议MineViewProtocol,完成成功或者失败赋值

#import <Foundation/Foundation.h>
#import "MineModel.h"
@protocol MineViewProtocol <NSObject>
- (void)onGetMineInfoSuccess:(MineModel *)model;

- (void)onGetMineInfoFail:(NSInteger) errorCode des:(NSString *)des;
@end

 

3.2.2 Model

Model和MVC的Model一样,声明属性。

#import <Foundation/Foundation.h>

@interface MineModel : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *address;
@property (nonatomic,copy) NSString *birthday;
@end


#import "MineModel.h"
@implementation MineModel

@end

 

3.2.3 View

View进行UI的显示,拿到数据之后,可以实现接口完成对UI界面的赋值。

#import <UIKit/UIKit.h>
/**
 如果MineMainView是一个共用页面,并且需要其他数据资源,可以将presenter写在view里面
 */
@interface MineMainView : UIView

@end


#import "MineMainView.h"
#import "MinePresenter.h"

@interface MineMainView()<MineViewProtocol>
@property (nonatomic,strong)MinePresenter *presenter;
@end

@implementation MineMainView
- (instancetype)init
{
    self = [super init];
    if (self) {
        [self.presenter getMineInfoWithURLString:@"mine" param:@{}];
    }
    return self;
}

#pragma mark - lazy load
- (MinePresenter *)presenter{
    if (!_presenter) {
        _presenter = [[MinePresenter alloc] initWithView:self];
    }
    return _presenter;
}
#pragma MineViewProtocol
- (void)onGetMineInfoSuccess:(MineModel *)model{ //这里做一些与UI相关的事情,完成对UI的赋值,避免了view与model的强依赖 } - (void)onGetMineInfoFail:(NSInteger) errorCode des:(NSString *)des{ } @end

 

3.2.4 presenter

#import "HttpPresenter.h"
#import "MineViewProtocol.h"

@interface MinePresenter : HttpPresenter <id<MineViewProtocol>>
//用于请求数据
- (void)getMineInfoWithURLString:(NSString *)URLString param:(NSDictionary *)param;
@end

在相应的实现中

#import "MinePresenter.h"
#import "MineModel.h"
#import "HKHttpResponse.h"
@implementation MinePresenter
- (void)getMineInfoWithURLString:(NSString *)URLString param:(NSDictionary *)param{
    [self.httpClient get:URLString parameters:param];
}
#pragma mark - HttpResponseHandle

//用来回调,触发view代理,完成赋值
- (void)onSuccess:(id )responseObject{
    HKHttpResponse * responseObj = (HKHttpResponse *)responseObject;
    MineModel *model = [MineModel yy_modelWithJSON:responseObj.content];
    if ([_view respondsToSelector:@selector(onGetMineInfoSuccess:)]) {
        [_view onGetMineInfoSuccess:model];
    }
}

- (void)onFail:(id)clientInfo errCode:(NSInteger)errCode errInfo:(NSString *)errInfo{
    if ([_view respondsToSelector:@selector(onGetMineInfoFail:des:)]) {
        [_view onGetMineInfoFail:errCode des:errInfo];
    }
}
@end

上面就是简单的MVP的项目使用和简单的架构结构。如果嵌套层次比较多,页面比较复杂,传值比较频繁,不推荐用MVP架构方式,而需求非常清晰,嵌套层次比较少可以尝试用MVP。

 

四、MVVM架构思路(双向绑定)

 MVVM架构中,组件就会随之变成Model-View-ViewModel,如下图:

ViewModel是MVC新引入的视图模型,用于显示逻辑,验证逻辑和网络请求存放的地方。但需要注意的是视图本身不应该放在VM中,换句话说就是VM代码中不要引入UIKit.h,这样解决了VC臃肿的问题,然后VM包含所有的展示逻辑但不会引用V,所以这样是可以通过编程充分测试。

我们来一起看一下在具体代码中的使用:

下面我们一一讲解各个文件夹的使用:

4.1 IOAMeAddressApi

对于Api文件夹,我们首先看一下此类包含的内容:请求参数的配置

#import "IOARequest.h"
#import "IOAMeAddressModel.h"

// 新增地址
@interface IOAMeAddAddressRequest: IOARequest
@property (nonatomic, strong) IOAMeAddressModel *addressModel;
@end

// 设置默认收货地址
@interface IOAMeSetDefaultAddressRequest: IOARequest
@property (nonatomic, copy) NSString *addressId;
@end

// 收货地址列表
@interface IOAMeAddressListRequest: IOARequest

@end

// 删除收货地址
@interface IOAMeDeleteAddressRequest: IOAMeSetDefaultAddressRequest
//@property (nonatomic, strong) NSString *addressId;
@end

然后IOAMeAddressApi.m中

#import "IOAMeAddressApi.h"

// 新增地址
@implementation IOAMeAddAddressRequest
- (id)requestArgument {
    NSMutableDictionary *dic = [IOAApiManager getParametersWithService:@"App.Users...."];
    self.addressModel.province = self.addressModel.pId;
    self.addressModel.city = self.addressModel.cId;
    self.addressModel.district = self.addressModel.aId;
    
    if (self.addressModel.city == nil) {
        self.addressModel.city = @"";
    }
    if (self.addressModel.district == nil) {
        self.addressModel.district = @"";
    }
    NSDictionary *temp = [self.addressModel yy_modelToJSONObject];
    [dic addEntriesFromDictionary:temp];
    return dic;
}
@end

// 设置默认收货地址
@implementation IOAMeSetDefaultAddressRequest
- (id)requestArgument {
    NSMutableDictionary *dic = [IOAApiManager getParametersWithService:@"App.Users......"];
    if (self.addressId) {
        [dic setObject:self.addressId forKey:@"address_id"];
    }
    return dic;
}
@end


// 收货地址列表
@implementation IOAMeAddressListRequest
- (id)requestArgument {
    NSMutableDictionary *dic = [IOAApiManager getParametersWithService:@"App.Users......"];
    return dic;
}
@end

// 删除收货地址
@implementation IOAMeDeleteAddressRequest
- (id)requestArgument {
    NSMutableDictionary *dic = [IOAApiManager getParametersWithService:@"App.Users....."];
    if (self.addressId) {
        [dic setObject:self.addressId forKey:@"address_id"];
    }
    return dic;
}
@end

这个类IOAMeAddressApi用于ViewModel中发送请求的请求体参数的配置,没有太多的难度,仅仅为了防止ViewModel臃肿。

 

4.2 IOAMeAddressModel

Model用于请求数据属性的声明(IOAMeAddressModel继承于IOAUserAddressBaseModel)

/*
 address_id    地址ID
 consignee    收货人
 country    国家
 province    省份
 city    城市
 district    地区
 twon    乡镇
 address    地址
 zipcode    邮政编码
 name    姓名
 mobile    手机
 telephone    电话号码
 address_alias    地址别名
 is_default    默认收货地址
 */
@interface IOAMeAddressModel : IOAUserAddressBaseModel

@property (nonatomic, copy) NSString *consignee;
@property (nonatomic, copy) NSString *zipcode;
@property (nonatomic, copy) NSString *address_id;
@property (nonatomic, copy) NSString *twon;
@property (nonatomic, copy) NSString *address_alias;
@property (nonatomic, copy) NSString *is_default;
@property (nonatomic, copy) NSString *telephone;
@property (nonatomic, copy) NSString *country;
@end

在上面声明了属性,如果有需要特殊处理的属性,看一下实现方法

#import "IOAMeAddressModel.h"

@implementation IOAMeAddressModel

+ (NSDictionary *)modelCustomPropertyMapper {
    return @{
             @"pId" : @"p_id",
             @"cId" : @"c_id",
             @"aId" : @"d_id"
             };
}

// 根据code获取地址
- (NSString *)getAddress {
    NSMutableString *address = [NSMutableString string];
    if (self.province.length) {
        [address appendString:self.province];
    }
    if (self.city.length && ![self.city isEqualToString:@"0"]) {
        [address appendString:@"-"];
        [address appendString:self.city];
    }
    if (self.district.length && ![self.district isEqualToString:@"0"]) {
        [address appendString:@"-"];
        [address appendString:self.district];
    }

    return address;
}

@end

上面红色方法modelCustomPropertyMapper是yyModel中的,用于解决json解析文件中关键字和定义声明的属性不一致的问题。

 

4.3 ViewModel

ViewModel在这个项目中用于完成对数据的请求和解析。就以新增收获地址接口来说:

@interface IOAMeAddressViewModel : NSObject

// 新增修改收货地址
- (void)requestForAddAddress:(IOAMeAddressModel *)addressModel callback:(void (^)(IOAResponse *response))callback;

@end

看一下实现方法

#import "IOAMeAddressViewModel.h"

@implementation IOAMeAddressViewModel
// 新增收货地址
- (void)requestForAddAddress:(IOAMeAddressModel *)addressModel callback:(void (^)(IOAResponse *response))callback {
    IOAMeAddAddressRequest *api = [[IOAMeAddAddressRequest alloc] init];
    api.addressModel = addressModel;
    [api startWithCompletionBlockWithSuccess:^(IOARequest *request) {
        id dic = request.responseObject[@"data"];
        IOAResponse *response = [IOAResponse responseWithRequest:request];

        if (!dic) {
            if (callback) {
                callback(response);
            }
            return;
        }
        if ([dic isKindOfClass:[NSNumber class]] || [dic isKindOfClass:[NSString class]]) {
            if (![dic boolValue]) {
                if (callback) {
                    callback(response);
                }
                return;
            }
        }
        response.success = YES;
        if (callback) {
            callback(response);
        }
    } failure:^(IOARequest *request) {
        IOAResponse *response = [IOAResponse responseWithRequest:request];

        if (callback) {
            callback(response);
        }
    }];
}

上面startWithCompletionBlockWithSuccess方法是自己对网络第三方框架YTKNetwork的封装,大家想用本项目封装网络框架可以在下面地方下载。https://github.com/zxy1829760/YTKNetwork-

4.4 ViewController

ViewController完成对整个布局的初始化以及触发请求。

NSString *kCellIdentifier = @"IOAReceiveAddressTableViewCell";

@interface IOAReceiveAddressViewController () <IOACellDelegate>
@property (nonatomic, strong) UIButton *addAddress;

//@property (nonatomic, assign) NSUInteger select;
@property (nonatomic, strong) IOAMeAddressViewModel *viewModel;
@end

@implementation IOAReceiveAddressViewController

- (void)dealloc {
    
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"管理收货地址";
    self.view.backgroundColor = [UIColor whiteColor];

    [self.dataSource removeAllObjects];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}


- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self requestForAddressList];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
}

#pragma mark - IOAProtocol
- (void)addSubViews {
    [super addSubViews];
    [self.view addSubview:self.addAddress];
}

- (void)prepare {
    CGFloat height = 44;
    
    [self.addAddress mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self.view);
        make.bottom.equalTo(self.view).offset(-BottomHeightOffset);
//        make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
//        make.bottom.equalTo(self.view);
        make.height.mas_equalTo(height);
    }];
    
    [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self.view);
        make.bottom.equalTo(self.addAddress.mas_top);
        make.top.equalTo(self.view).offset(TopHeightOffset);
//        make.top.equalTo(self.view);
//        make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
    }];
}

#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.dataSource.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    IOAReceiveAddressTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath];
    cell.cellDelegate = self;
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    [cell update:self.dataSource[indexPath.section]];
    
    return cell;
}


- (void)registerClasses {
    [self.tableView registerClass:[IOAReceiveAddressTableViewCell class] forCellReuseIdentifier:kCellIdentifier];
}

- (void)clickedEmptyPage:(id)object {
    [self requestForAddressList];
}

#pragma mark - IOARefreshAndLoadMoreDelegate
- (void)refresh:(id<IOARefreshAndLoadMoreDelegate>)collection {
    [self requestForAddressList];
}

#pragma mark - CellDelegate
- (void)clickOn:(id)sender {
    IOAInfoTableViewCell *cell = sender;
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];

    IOAMeAddressModel *model = self.dataSource[indexPath.section];
    if (cell.clickType == kInfoCellClickDelete) {
        [self requestForDeleteAddress:model.address_id withIndexPaht:indexPath];
    }
    else if (cell.clickType == kInfoCellClickEdit) {
        IOAAddReceiveAddressViewController *vc = [IOAAddReceiveAddressViewController new];
        vc.isEdit = YES;
        vc.addressModel = model;
        [self.navigationController pushViewController:vc animated:YES];
    }
    else {
//        IOAMeAddressModel *model = self.dataSource[indexPath.section];
        if ([model.is_default boolValue]) {
            [self.tableView reloadData];
            return;
        }
        model.is_default = [NSString stringWithFormat:@"%d", 1];
        [self requestForSetDefaultAddress:model];
    }
}

#pragma mark - Actions
- (void)clickAddAdress:(UIButton *)sender {
    IOAAddReceiveAddressViewController *vc = [IOAAddReceiveAddressViewController new];
    [self.navigationController pushViewController:vc animated:YES];
}

#pragma mark - Api
- (void)requestForAddressList {
    [self dismissEmptyPage];
    [self startProgress];
    [self.viewModel requestForAddressList:^(IOAResponse *response) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self stopProgress];
            NSArray<IOAMeAddressModel *> *addressList = response.responseObject;
            if (addressList) {
                [self.dataSource removeAllObjects];
                [self.dataSource addObjectsFromArray:addressList];
                [self.tableView reloadData];
                
                if (self.dataSource.count == 0) {
                    [self showNoDatasPage];
                    return ;
                }
            }
            else {
                if ([response isNoNetwork] && self.dataSource.count == 0) {
                    [self showNoNetworkPage];
                    return;
                }
                if ([response isExpiryToken]) {
                    [self showLoginViewController];
                    return;
                }
                if ([response alertOrNot]) {
                    if (response.responseMessage.length > 0) {
                        [self.view makeToast:response.responseMessage];
                        return;
                    }
                    [self.view makeToast:@"获取失败"];
                }
            }
        });
    }];
}

上面就是MVVM代码在实际项目中的使用。本篇博客会不断更新,因为框架的东西很难学完,慢慢优化。

下一篇我们将讲述MVVM+RAC的结合使用,以及了解RAC底层实现逻辑。

 

posted @ 2019-02-20 19:22  国孩  阅读(1153)  评论(0编辑  收藏  举报