我是如何构建网络层

摘要

Tomasz Szulc 在建立网络架构的时候并不依赖第三方库和苹果的 CoreData,这使得它很容易拓展和测试。这个设计很赞。

一次带领或参与两个项目是个不错的机会去试验 app 框架,并且可以试验脑海中或刚学到的概念。分享我最近学到的一个主题,我想你会发现我构建的网络层对你很实用。

现在,移动应用都是面向客户端的,所以几乎所有的应用或多或少都有网络层的地方。目前我见过许多网络层的实现,每种都有一些缺点。虽不说我最新构建的网络层没有缺点,但是在我的项目中它似乎运行良好。并且其测试覆盖率接近 100%。

在本文中,将介绍的网络层仅与一个后台进行通信,发送 JSON 请求,因此不是那么复杂。该层稍候将与 AWS 传输,向那发送文件,但应该很容易扩展该功能。

构思过程

这里的一些问题是我在搭建这样一个层前问我自己的:

  • 在哪里放置后台 url 相关代码?
  • 在哪里放置终端代码?
  • 在哪里放置生成请求的代码?
  • 在哪里保留所关心的请求参数代码?
  • 在哪里存储身份验证令牌?
  • 如何执行请求?
  • 何时何处执行请求?
  • 是否需要取消请求?
  • 是否需要处理后台错误、后台 bug?
  • 是否使用第三方框架?应该用什么框架?
  • 是否有核心数据需要传递?
  • 如何测试解决方案?

存储后台 url

首先,我该在哪里放置后台 url?系统的其他部分如何知道在哪里发送请求?我更喜欢创建一个 BackendConfiguration 类来存储这些信息。

易于测试,易于配置。你可以设置为 shared 静态变量,在你要的网络层的任何位置都可以访问,无需传递。

终端

这里讨论的是我试验一段时间后找到的一个即开即用的解决方案。在配置 NSURLSession 是尝试硬编码终端,尝试虚拟一些类似 Resource 对象的终端,可以容易实例化和注入,但它仍不是我所想要的。

我想到了创建 *Request 对象,包含了目标终端、使用方法(GET、PUT或其他)、请求体配置以及传递的请求头。

这是我想到的对象:

实现该协议的类可以提供构建请求所需要的基本信息。这里的 NetworkService.Method 只是一个 GETPOSTPUTDELETE 的枚举。

映射终端的实例代码可能如下所示:

为了避免到处创建头字典,我们可以定义 BackendAPIRequest 扩展。

*Request 类用所有需要的参数来创建一个成功请求。你总是需要确保至少传递所有参数,否则不能创建一个请求对象。

定义终端很容易。如果该终端需要对象 id 包含其中,也是很容易添加的,实际上也应该会存储像这样的 id 属性。

请求方法不改变,很容易就可以构建参数体和头信息。一切都容易测试。

执行请求

我是否需要使用第三方框架与后台通信?

我看到人们使用 AFNetworking(Objective-C)和 Swift 的 Alamofire。我用过很多次,但有段时间我不用它了。因为我们有 NSURLSession,而且表现良好,因此不认为还需要任何第三方框架了。IMO 这种依赖,将会让你的 app 框架更加复杂。

当前解决方案包含两个类——NetworkServiceBackendService

  • NetworkService:允许你执行 HTTP 请求,它内部包含 NSURLSession。每个网络服务一次只能执行一个请求,可以取消请求(大改进),并有成功和失败响应的回调。
  • BackendService:(不是最酷的名字,但比较恰当)是接收与后台相关请求(上面提及的 *Request 对象)的类。它内部使用 NetworkService。在我当前使用的版本,它会试图使用 NSJSONSerializer 把响应数据序列化为 json。

正如你所见的,BackendService 可以在头信息中设置身份验证令牌。BackendAuth 是一个简单的存储对象,把令牌存储到 NSUserDefaults 中。如果有需要,它还可以把令牌存储到 Keychain 中。

BackendServicerequest(_:success:failure:) 方法中,把 BackendAPIRequest 作为一个参数,并从 request 对象中提取必要信息。这很好地封装了后台服务的输入参数与输出结果。

NetworkService、BackendServiceBackendAuth` 都是易于测试与维护。

排列请求

这里涉及几个问题。我们想要通过什么方式执行网络请求?如果我们想一次执行多个请求怎么办?我们如何获得请求的成功或失败的通用通知?

决定使用 NSOperationQueueNSOperation 来执行网络请求。

所以,我继承 NSOperation,并重写 asynchronous 属性,返回 true

接下来,因为我想使用 BackendService 执行网络调用,所以我继承 NetworkOperation,并创建 ServiceOperation

该类内部创建 BackendService,所以不需要在子类中创建它。

以下是登录操作:

start 方法,服务执行在操作构造函数内部创建的请求。request(_:success:failure:) 传递 handleSuccesshandleFailure 方法做为回调。IMO 让代码变得更清晰,并且是可读的。

操作传递给 NetworkQueue 对象,它是单例,并且可以排列每个操作。现在我尽可能保持简单:

在一个地方执行操作有什么优点呢?

  • 轻松取消所有的网络操作。
  • 在网络连接较弱的情况下,不需要提供给使用 app 的用户提供基本体验。例如,当用于适用连接较弱时,你会取消所有正在下载图片。
  • 你可以构建一个优先级队列,优先执行一些请求以快速获得应答。

使用 Core Data

这是我不得不推迟发布这个分录的一面。在以前版本的网络层中,操作会返回 Core Data 对象。响应接收时,就会被解析、转换为 Core Data 对象。但这种解决方案远非理想。

  • 操作必须知道 Core Data 是什么。因为我让模型和网络层都分离到独立的框架,而网络框架必须得知道模型框架。
  • 每个操作都需要添加额外的 NSManagedObjectContext 参数来获取操作的上下文。
  • 每次收到响应并调用成功 block 时,它首先尝试在上下文查找对象,或者命中磁盘,从磁盘获取对象。这是 IMO 一个很大的缺点。你不总是需要创建 Core Data 对象。

所以我想到了把 Core Data 完全从网络层中脱离出来。我创建了中间层,用于解析响应结果,创建对象。

  • 使用这种方式解析、创建对象是快速的,并且不需要命中磁盘。
  • 同样也不需要传递 NSManagedObjectContext 给操作。
  • 大多情况下,当操作添加到队列中,你可以通过在 success block 解析并引用可能保存在创建操作的位置的 Core Data 对象,来更新核心数据对象。

 映射响应

映射响应的想法是用于分离解析逻辑和 JSON 映射器逻辑的。

我们可以把两种类型的解析器区分开。第一种类型只返回单个特定类型的对象。第二种是解析一组这样的项的解析器。

首先,我们为所有项目定义一个公共协议:

这里的几个对象是映射器产物:

让我们定义一个错误类型,它在解析错误时抛出。

  • Invalid:当传递的 json 为 nil 且不该为 nil 时,或当传递的是一组对象而不是期望的单个对象的 json。
  • MissingAttribute:当 json 中的 key 丢失,或当传递的值不该为 nil 时,值为 nil 时,对错误的解释。

一个 ResponseMapper 可能长这样

它需要一个来自后台响应的 obj,我这里是一个 JSON,parse 方法消费 obj 并返回符合 ParsedItemA 对象。

我们现在有了这个通用映射器后,我们可以创建具体的映射器。让我们开看看用于解析注册操作的响应映射器。

ResponseMapperProtocol 是由具体映射器实现的协议,因此他们共享相同的方法来解析响应。

然后,这样的映射器在操作的成功 block 中使用,并且你可以对非字典的,如特定类型的具体对象进行操作。容易使用这样的对象后,其他的也是很容易进行测试的。

如果一切解析正确,它需要一个映射函数并返回多个项的数组。如果只返回一项且不能解析,或者最坏情况,返回该映射器的数组为空,仅依赖于你考虑的解析范围,那么它可能会抛出错误。映射器期望的 obj(来自后台的响应)是一组 JSON 元素的数组。

这里呈现的是网络层架构图。

示例项目

你可以在我的 github上找到实例项目。该项目使用的是假 url 的后端,所有没有成功完成的任务请求。我只提供了一个可用视图,尽可能地展示网络层框架的样子。

总结

我发现这种网络层非常实用、简单易用。

  • 它的最大优点是,你可以轻松添加用于其他地方的操作,而无需理会 Core Data。
  • 你无需费劲保持代码覆盖率接近 100%,无需考虑超级复杂的情况,因为没有这种情况。
  • 它的核心是在类似复杂的应用中易于重用。

打赏支持我翻译更多好文章,谢谢!

打赏译者

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

1 2 收藏 评论

关于作者:BEASTQ

iOS 摇滚小怪兽 个人主页 · 我的文章 · 10 ·   

可能感兴趣的话题



直接登录
跳到底部
返回顶部