iOS-网络层到底该如何设计?

一、前言

镇楼小图

关于网络层,苹果对网络请求部分已经做了很好的封装,业界的AFNetworking也被广泛使用,除此以外,肯定还有其他的网络框架,但在实际的App开发中,AFNetworking已经成为了事实上各大App的标准配置。

我们一直都有讲分层架构,其中很重要的一层就是网络层,那我们到底改如何设计才能更好的辅助我们的项目呢?最近也看了一些大牛的文章,也是有所获。

二、问题简要

1.以什么方式将数据交付给业务层?

2.交付什么样的数据给业务层?

3.集约型API调用方式和离散型API调用方式

三、具体聊聊上面三个问题

1.以什么方式将数据交付给业务层?

iOS开发领域有很多对象间数据的传递方式,我看到的大多数App在网络层所采用的方案主要集中于这三种:Delegate,Notification,Block。

我当前开发的项目就是采用Block。但我今天要讲是以Delegate为主,Notification为辅。原因如下:

1.尽可能减少跨层数据交流的可能,限制耦合
2.统一回调方法,便于调试和维护
3.在跟业务层对接的部分只采用一种对接手段限制灵活性,以此来交换应用的可维护性

尽可能减少跨层数据交流的可能,限制耦合

什么叫跨层数据交流?就是某一层(或模块)跟另外的与之没有直接对接关系的层(或模块)产生了数据交换。为什么这种情况不好?严格来说应该是大部分情况都不好,有的时候跨层数据交流确实也是一种需求。之所以说不好的地方在于,它会导致代码混乱,破坏模块的封装性。我们在做分层架构的目的其中之一就在于下层对上层有一次抽象,让上层可以不必关心下层细节而执行自己的业务。所以我们尽量不选择Notification。

然后为什么尽量不要用block?

block和delegate乍看上去在作用上是很相似,但是关于它们的选型有一条严格的规范:当回调之后要做的任务在每次回调时都是一致的情况下,选择delegate,在回调之后要做的任务在每次回调时无法保证一致,选择block

在离散型调用的场景下,每一次回调都是能够保证任务一致的,因此适用delegate。这也是苹果原生的网络调用也采用delegate的原因,因为苹果也是基于离散模型去设计网络调用的。

在集约型调用的场景下,使用block是合理的,因为每次请求的类型都不一样,那么自然回调要做的任务也都会不一样,因此只能采用block。AFNetworking就是属于集约型调用,因此它采用了block来做回调。

统一回调方法,便于调试和维护

首先,Block本身无好坏对错之分,只有合适不合适。在这一节要讲的情况里,Block无法做到回调方法的统一,调试和维护的时候也很难在调用栈上显示出来。

在网络请求和网络层接受请求的地方时,使用Block没问题。但是在获得数据交给业务方时,最好还是通过Delegate去通知到业务方。因为Block所包含的回调代码跟调用逻辑放在同一个地方,会导致那部分代码变得很长,因为这里面包括了调用前和调用后的逻辑。从另一个角度说,这在一定程度上违背了single function,single task的原则,在需要调用API的地方,就只要写API调用相关的代码,在回调的地方,写回调的代码。

然而大部分App里,当我们写代码写到这边的时候,也意识到了这个问题。因此我们会在block里面写个一句话的方法接收参数,然后做转发,然后就可以把这个方法放在其他地方了,绕过了Block的回调着陆点不统一的情况。比如这样:

   [API callApiWithParam:param successed:^(Response *response){
        [self successedWithResponse:response];
    } failed:^(Request *request, NSError *error){
        [self failedWithRequest:request error:error];
    }];

这实质上跟使用Delegate的手段没有什么区别,只是绕了一下,不过还是没有解决统一回调方法的问题。Block是目前大部分第三方网络库都采用的方式,因为在发送请求的那一部分,使用Block能够比较简洁,因此在请求那一层是没有问题的,只是在交换数据之后,还是转变成delegate比较好,比如AFNetworking里面:

   [AFNetworkingAPI callApiWithParam:self.param successed:^(Response *response){
        if ([self.delegate respondsToSelector:@selector(successWithResponse:)]) {
            [self.delegate successedWithResponse:response];
        }
    } failed:^(Request *request, NSError *error){
        if ([self.delegate respondsToSelector:@selector(failedWithResponse:)]) {
            [self failedWithRequest:request error:error];
        }
    }];

这样在业务方这边回调函数就能够比较统一,便于维护。

2.交付什么样的数据给业务层?

其实我们的理想情况是希望API的数据下发之后就能够直接被View所展示。但是,这怎么可能呢?UI可能一天一变,你能让API也天天变?,另外,这种做法使得View和API联系紧密,也是我们不希望发生的。

这里我们引入了reformer(名字而已,叫什么都好)这个对象用于封装数据转化的逻辑,这个对象是一个独立对象,事实上,它是作为Adaptor模式存在的。我们可以这么理解:想象一下我们洗澡时候使用的莲蓬头,水管里出来的水是API下发的原始数据。reformer就是莲蓬头上的不同水流挡板,需要什么模式,就拨到什么模式。(其实它就是个专业处理数据的)

先定义一个protocol:

@protocol ReformerProtocol 
- (ViewModel *)reformDataWithManager:(APIManager *)manager;
@end


在Controller里是这样:

@property (nonatomic, strong) id XXXReformer;
@property (nonatomic, strong) id YYYReformer;

#pragma mark - APIManagerDelegate
- (void)apiManagerDidSuccess:(APIManager *)manager {
    XXXViewModel *reformedXXXData = [manager fetchDataWithReformer:self.XXXReformer];
    [self.XXXView configWithData:reformedXXXData];

    YYYViewModel *reformedYYYData = [manager fetchDataWithReformer:self.YYYReformer];
    [self.YYYView configWithData:reformedYYYData];
}


在APIManager里面,fetchDataWithReformer是这样:
- (ViewModel *)fetchDataWithReformer:(id)reformer {
    if (reformer == nil) {
        return self.rawData;
    } else {
        return [reformer reformDataWithManager:self];
    }
}

为了保持数据可读性,我们这里再引入一个ViewModel,其实就是相关View的模型。一个控件对应一个属性。

PropertyListReformer.m

- (PropertListCellModel *)reformData:(NSDictionary *)originData fromManager:(APIManager *)manager {
    ... ...
    ... ...

    PropertListCellModel *resultData = nil;

    if ([manager isKindOfClass:[FirstListAPIManager class]]) {
        resultData = ...
    }

    if ([manager isKindOfClass:[SecondListAPIManager class]]) {
        resultData = ...
    }

    if ([manager isKindOfClass:[ThirdListAPIManager class]]) {
        resultData = ...
    }

    return resultData;
}
PropertListCell.m

- (void)configWithData:(PropertListCellModel *)model {
    self.imageView.image = model.image;
    self.idLabel.text = model.id
    self.nameLabel.text = model.name;
    self.titleLabel.text = model.title;
}
3.集约型API调用方式和离散型API调用方式

集约型API调用其实就是所有API的调用只有一个类,然后这个类接收API名字,API参数,以及回调着陆点(可以是target-action,或者block,或者delegate等各种模式的着陆点)作为参数。然后执行类似startRequest这样的方法,它就会去根据这些参数起飞去调用API了,然后获得API数据之后再根据指定的着陆点去着陆。

集约型API调用方式:

[APIRequest startRequestWithParams:params success:^(Response *response){
  ...
} fail:^(Error *error){
  ...
}];

离散型API调用是这样的,一个API对应于一个APIManager,然后这个APIManager只需要提供参数就能起飞,API名字、着陆方式都已经集成入APIManager中。

离散型API调用方式:

@property (nonatomic, strong) ItemListAPIManager *itemListAPIManager;

// getter
- (ItemListAPIManager *)itemListAPIManager {
    if (!_itemListAPIManager) {
        _itemListAPIManager = [[ItemListAPIManager alloc] init];
        _itemListAPIManager.delegate = self;
    }
    return _itemListAPIManager;
}

// 使用的时候就这么写:
[self.itemListAPIManager loadDataWithParams:params];

- (void)successWithResponse:(Response *)response { ... }
- (void) failedWithResponse:(Error *) error { ... }

四、总结

1.使用delegate来做数据对接,仅在必要时采用Notification来做跨层访问
2.交付NSDictionary给业务层,使用Const字符串作为Key来保持可读性
3.提供reformer机制来处理网络层反馈的数据,这个机制很重要,好处极多
4.网络层上部分使用离散型设计,下部分使用集约型设计

五、写在最后

注:本文主体思想是学习了Casa Taloyum 的一篇关于网络框架的文章,很有收获,于是有了一些学习感悟,也感谢大牛的文章。并附上原文如下:
iOS应用架构谈 网络层设计方案

你可能感兴趣的:(iOS-网络层到底该如何设计?)