美文网首页
iOS-网络层到底该如何设计?

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

作者: Leewins | 来源:发表于2018-11-04 22:52 被阅读115次

    一、前言

    镇楼小图

    关于网络层,苹果对网络请求部分已经做了很好的封装,业界的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 <NSObject>
    - (ViewModel *)reformDataWithManager:(APIManager *)manager;
    @end
    
    
    在Controller里是这样:
    
    @property (nonatomic, strong) id<ReformerProtocol> XXXReformer;
    @property (nonatomic, strong) id<ReformerProtocol> 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<ReformerProtocol>)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-网络层到底该如何设计?

          本文链接:https://www.haomeiwen.com/subject/gtedxqtx.html