原文:How do I build a Network Layer 作者:Tomasz Szulc(@tomkowz),资深 iOS 软件工程师 译者:孙薇,欢迎技术投稿、约稿、给文章纠错,请发送邮件至 [email protected]。 版权声明:本文为作者授权 CSDN 翻译,未经允许,请勿转载。
【导语】本文作者 Tomasz Szulc 曾同时带领着两个项目的研发工作,由此为他提供了一次很好的对于应用架构进行深度尝试的机会,本文即是他根据实践经验所总结的网络层构建方法,大家或许有兴趣一读。
如今的移动应用大多是“客户端-服务器”模式,某个应用中很可能就包含或大或小的网络层结构。迄今为止笔者见过的许多实现均有一些缺陷,最新构建的这个或许仍有缺陷,但在手边的这两个项目中效果都很不错,而且测试覆盖率几乎达到100%。本文只讨论与单个后台通讯、发送 JSON 编码请求的网络层,这个网络层会与 AWS 通讯,发送一些文件,整体结构并不复杂,不过相应功能的扩展也应当十分简单。
思维流程
在构建相应网络层之前,我先提出一些问题:
将包含有后台 URL 相关内容的代码放在哪里?
将包含端点相关的代码放在哪里?
将包含如何构建请求信息的代码放在哪里?
将与为请求准备参数的相关代码放在哪里?
应当将身份验证 token 存在哪里?
如何执行请求?
何时、在何处执行请求?
是否关注取消请求的问题?
是否需要关注错误的后台响应或者一些后台 Bug?
是否需要使用第三方的框架?应当使用什么框架?
是否存在相关的Core Data?
如何测试解决方案?
存储后端 URL
首先,我们要了解后端 URL 应当放在哪里?系统的其它部分怎么知道向哪里发送请求?这里我们更偏好创建存储这类信息的 BackendConfiguration 类。
import Foundation
public final class BackendConfiguration {
let baseURL: NSURL
public init(baseURL: NSURL) {
self.baseURL = baseURL
}
public static var shared: BackendConfiguration!
}
这种类易于测试,也易于配置,设定共享静态变量之后,我们就能从网络层的任意位置对其进行访问,不需将这个变量发送到其它位置。
let backendURL = NSURL(string: "https://szulctomasz.com")!
BackendConfiguration.shared = BackendConfiguration(baseURL: backendURL)
端点
在找到解决方案前,笔者在这个问题上做了颇有一阵子的实验,在配置 NSURLSession 时曾尝试对端点执行硬编码的方式,并尝试了一些了解端点、便于实例化与注入的虚拟资源类对象,但并未找到需要的方案。然后得出了设想:创建知道要接入哪个端点,使用什么方法,该是 GET、POST、PUT 还是其它什么的 *Request 对象,它要了解如何配置请求主体,以及要 pass 什么头文件。
于是我得出了这样的代码:
protocol BackendAPIRequest {
var endpoint: String { get }
var method: NetworkService.Method { get }
var parameters: [String: AnyObject]? { get }
var headers: [String: String]? { get }
}
实现这个协议的类能够提供构建请求所需的基本信息,NetworkService.Method 只是个带有 GET、POST、PUT、DELETE案例的enum函数。
映射一个端点的请求示例如下:
final class SignUpRequest: BackendAPIRequest {
private let firstName: String
private let lastName: String
private let email: String
private let password: String
init(firstName: String, lastName: String, email: String, password: String) {
self.firstName = firstName
self.lastName = lastName
self.email = email
self.password = password
}
var endpoint: String {
return "/users"
}
var method: NetworkService.Method {
return .POST
}
var parameters: [String: AnyObject]? {
return [
"first_name": firstName,
"last_name": lastName,
"email": email,
"password": password
]
}
var headers: [String: String]? {
return ["Content-Type": "application/json"]
}
}
为了避免给每个 header 创建 dictionary,我们可以为 BackendAPIRequest 定义扩展。
extension BackendAPIRequest {
func defaultJSONHeaders() -
|