(原文地址:https://medium.freecodecamp.org/swift-networking-with-siesta-5b5e7089bd8f)

今天我跟大家分享一下我的 iOS 网络库新欢,名字叫做 Siesta。“她有啥特殊的?为啥我不直接用 Almofire?”你也许会问。事实上,你仍然可以把 Alamofire 和 Siesta 一起使用!它是客户端之上的网络抽象层。

和 Moya 不同,Siesta 不会隐藏 HTTP。这种中间状态,是我使用 Siesta 构建 REST API 的理由。

通过资源为中心而不是请求为中心的设计,Siesta 提供一个全局的符合 RESTful 的可被观察的模型。

这意味着什么?一些非必要的网络和反序列化操作被大量减少,视图控制器和网络请求之间的关系被解耦。此外,它的响应解析十分透明,开箱即用。

这篇教程里,我将展示给你如何通过使用 Siesta,让你的网络处理代码变得更加 Swiftly。

初始化

从 Cocoapods 安装:

pod 'Siesta', '~> 1.0'
复制代码

为了演示本教程,我将编写一个简单的 CRUD 应用程序配合 REST API 和 我部署到 HeroKu 上基于 JWT 的验证。

首先,创建一个名为 AwesomeAPI.swift 的文件。

定义基本的 API 配置:


import Siestalet baseURL = "https://jwt-api-siesta.herokuapp.com"let AwesomeAPI = _AwesomeAPI()class _AwesomeAPI {// MARK: - Configurationprivate let service = Service(baseURL: baseURL,standardTransformers: [.text, .image])fileprivate init() {// –––––– Global configuration ––––––#if DEBUGLogCategory.enabled = [.network]#endif}// MARK: - Resource Accessorsfunc ping() -> Resource {return service.resource("/ping")}
}复制代码

我们在此定义了全局使用的单例 API 对象。我们配置服务的地址,还有standardTransforms (定义类型的转换标准),它提供了对文本类型、图片类型响应的解析。然后我们打开了 debug 模式,在调试 API 时这很有用。最后,我们定义了 resource accessor(资源访问)。一个访问我们API 的方法返回一个我们在 ViewController 中使用的资源对象。

从资源对象中访问网络并读取数据,我们需要在 ViewController 中创建一个观察者:


import Siestaclass ViewController: UIViewController {override func viewDidLoad() {super.viewDidLoad()AwesomeAPI.ping().addObserver(self)}override func viewWillAppear(_ animated: Bool) {super.viewWillAppear(animated)AwesomeAPI.ping().loadIfNeeded()}
}extension ViewController: ResourceObserver {func resourceChanged(_ resource: Resource, event: ResourceEvent) {if let text = resource.latestData?.text {print(text)}}
}复制代码

我们给ping返回的资源添加了一个观察者,并定义好了代理,当资源的状态改变时,代理会被调用。当收到新数据和被资源被添加时,资源的状态都会改变。

Siesta 支持对请求初始化和配置进行解耦,所以在请求资源的时候,不用担心过多关于请求具体的细节。

比如,你无需担心loadIfNeeded被调用的太频繁,Siesta 允许你在指定时间内忽略重复的请求。默认时间是30秒,值可配置。

现在如果你运行程序,你可能将看到类似这样的输出:

Siesta:network        │ GET https://jwt-api-siesta.herokuapp.com/ping
Siesta:network        │ Response:  200 ← GET https://jwt-api-siesta.herokuapp.com/ping
pong复制代码

转换器

让我们再做点有意思的。定义一些转换器可以实现自动解析原始 JSON 数据到一个模型对象。

/status 返回:

{"text": "ok"
}
复制代码

我们使用 JSONDecoder 在后台对 JSON 进行解析,这是一个在 Swift 4 的新加入的。

首先,我们添加转换器:

fileprivate init() {...let jsonDecoder = JSONDecoder()// –––––– Mapping from specific paths to models ––––––service.configureTransformer("/status") {try jsonDecoder.decode([String: String].self, from: $0.content)}
}// MARK: - Resource Accessors
func status() -> Resource {return service.resource("/status")
}
复制代码

[String: String] 意味着我们期待在我们的 JSON 响应对象中,返回一个 string-to-string 映射的字典。

然后我们对 ViewController 中观察方法进行更新。

func resourceChanged(_ resource: Resource, event: ResourceEvent) {if let status: [String: String] = resource.typedContent() {print("\(status)")}
}
复制代码

你可能注意到了,解析一个 JSON 我们使用 typedContent(),它返回一个可选值,解包后使用。注意我们需要明确提供数据类型([String: String]),这里的数据类型不能被推倒出来。同样的,对 /ping 的调用修改如下:

if let text: String = resource.typedContent() {print(text)
}
复制代码

验证

在我们的 API 中,我们有两个需要验证权限的接口:incomesexpenses。他们需要认证权限,所以我们需要先获得 JWT token。我们来增加认证方法。这里没有采用增加一个方法去返回带有认证信息的资源,而是把验证信息增加到每个请求中。

首先,增加一个属性,它将存储JWT token用于验证。

private var authToken: String? {didSet {service.invalidateConfiguration()guard let token = authToken else { return }let jwt = try? JWTDecode.decode(jwt: token)tokenExpiryDate = jwt?.expiresAt}
}
复制代码

这个属性被赋值的时候,我们将当前的配置作废掉,这样做是必须的,当下一次资源(resource)被获取的时候,请求的头会被刷新。刚刚配置的最新的 token 会被放到 HTTP 头中。

还需要考虑将 token 存储到钥匙串而不是 NSUserDefaults 或者其他不安全的存储方式。我们这里使用 JWTDecode 来解析 JWT token 和过期时间。

接下来,我们想在 token 过期的时候自动刷新。更成熟的设计是提供有一个专门刷新 token 的接口,调用它去刷新 token。在我们的例子中,我们考虑一个简化的实现,只是重新发送一次登录请求。

下面是发送登录请求并得到 token 的代码:

@discardableResult func login(_ email: String, _ password: String, onSuccess: @escaping () -> Void, onFailure: @escaping (String) -> Void) -> Request {let request = service.resource("/login").request(.post, json: ["email": email, "password": password]).onSuccess { entity inguard let json: [String: String] = entity.typedContent() else {onFailure("JSON parsing error")return}guard let token = json["jwt"] else {onFailure("JWT token missing")return}self.authToken = tokenonSuccess()}.onFailure { (error) inonFailure(error.userMessage)}return request
}
复制代码

我们发送一个携带用户验证信息的 POST 请求给/login。在onSuccessonFailure两个方法中处理返回信息,如果验证成功,则存储起来。

最后,我们来实现在过期之前更新用户验证信息。使用计时器来实现:

private var refreshTimer: Timer?public private(set) var tokenExpiryDate: Date? {didSet {guard let tokenExpiryDate = tokenExpiryDate else { return }let timeToExpire = tokenExpiryDate.timeIntervalSinceNow// try to refresh JWT token before the expiration timelet timeToRefresh = Date(timeIntervalSinceNow: timeToExpire * 0.9)refreshTimer = Timer.scheduledTimer(withTimeInterval: timeToRefresh.timeIntervalSinceNow, repeats: false) { _ inAwesomeAPI.login("test", "test", onSuccess: {}, onFailure: { _ in })}}
}
复制代码

我们测试接口的验证信息为testtestAwesomeAPI.login()很容易集成进 ViewController。解析登录请求返回的信息,同样需要定义一个转换器:

service.configureTransformer("/login", requestMethods: [.post]) {try jsonDecoder.decode([String: String].self, from: $0.content)
}
复制代码

调用 API 的时候需要我们将 JWT token 信息放在 Authorization HTTP 头中。为了达到这个目的,我们增加一项配置:

service.configure("**") {if let authToken = self.authToken {$0.headers["Authorization"] = "Bearer \(authToken)"}
}
复制代码

现在我们的请求已经被认证了,接着尝试去请求一些需要认证的资源,比如/expenses。这个断点返回一个数组,成员结构包含以下字段:

{"amount": -50.0,"created_at": "2017-12-07T16:00:52.988245","description": "pizza","type": "TransactionType.EXPENSE"
}
复制代码

我们创建一个模型来存储返回值的这种格式。增加一个名为Expense的类。接下来使用JSONDecoder,从 Codable 继承:

import Foundationstruct Expense: Decodable {let amount: Floatlet createdAt: Datelet description: Stringlet type: Stringenum CodingKeys: String, CodingKey {case amountcase createdAt = "created_at"case descriptioncase type}
}
复制代码

CodingKeys 枚举允许我们映射返回的 JSON 字段名到刚刚创建的结构体的属性名。这里映射了日期字段(createdAt)。因为我们的自定义了日期格式,我们还需要通过JSONDecoder.dateDecodingStrategy来进行配置。

let jsonDecoder = JSONDecoder()
let jsonDateFormatter = DateFormatter()
jsonDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.A"
jsonDecoder.dateDecodingStrategy = .formatted(jsonDateFormatter)
复制代码

最后,创建这个类的转换器:

service.configureTransformer("/expenses") {try jsonDecoder.decode([Expense].self, from: $0.content)
}
复制代码

我们期待得到 Expense 数组,通过[Expense]定义。

参考刚才的定义,我们增加一个expenses()资源访问器,然后我们可以调用需要验证信息的资源:

import Siestaclass ViewController: UIViewController {override func viewDidLoad() {super.viewDidLoad()AwesomeAPI.expenses().addObserver(self)}override func viewWillAppear(_ animated: Bool) {super.viewWillAppear(animated)AwesomeAPI.login("test", "test", onSuccess: {AwesomeAPI.expenses().loadIfNeeded()}, onFailure: { error inprint(error)})}
}extension ViewController: ResourceObserver {func resourceChanged(_ resource: Resource, event: ResourceEvent) {if let expenses: [Expense] = resource.typedContent() {print(expenses)}}
}
复制代码

最后一件事

最后我想讨论一下认证信息过期之后的一些实践。配合 Siesta,我们能自动执行认证以及重试因为认证失败的请求。

增加配置:

service.configure("**") {// Retry requests on auth failure$0.decorateRequests {self.refreshTokenOnAuthFailure(request: $1)}
}
复制代码

将请求串联起来,然后带着新 token 再次调用。

func refreshAuth(_ username: String, _ password: String) -> Request {return self.login(username, password, onSuccess: {}, onFailure: { error in})
}func refreshTokenOnAuthFailure(request: Siesta.Request) -> Request {return request.chained {guard case .failure(let error) = $0.response,  // Did request fail…error.httpStatusCode == 401 else {           // …because of expired token?return .useThisResponse                    // If not, use the response we got.}return .passTo(self.refreshAuth("test", "test").chained {             // If so, first request a new token, then:if case .failure = $0.response {           // If token request failed…return .useThisResponse                  // …report that error.} else {return .passTo(request.repeated())       // We have a new token! Repeat the original request.}})}
}复制代码

最后,项目地址奉上:https://github.com/nderkach/AwesomeAPI

Happy hacking!

[译]使用 Siesta 处理 Swift 网络请求相关推荐

  1. 使用 Siesta 处理 Swift 网络请求

    (原文地址:https://medium.freecodecamp.o...) 今天我跟大家分享一下我的 iOS 网络库新欢,名字叫做 Siesta."她有啥特殊的?为啥我不直接用 Almo ...

  2. Alamofire网络库基础教程:使用 Alamofire 轻松实现 Swift 网络请求

    Alamofire网络库基础教程:使用 Alamofire 轻松实现 Swift 网络请求 转自 http://www.cocoachina.com/ios/20141202/10390.html 本 ...

  3. Swift 网络请求数据与解析

    一: Swift 网络数据请求与处理最常用第三方 又有时间出来装天才了,还是在学swift,从中又发现一些问题,这两天上网找博客看问题弄的真的心都累.博客一篇写出来,好多就直接照抄,就没有实质性的把问 ...

  4. 二、Swift网络请求回来的数据我这样取

    网络请求框架Alamofire 源码地址 Swift 2.3 Alamofire3.0版本支持 iOS 8 Swift 3 Alamofire4.0以上版本支持 iOS 9及以上系统 json数据: ...

  5. Swift 网络请求 Moya+RxSwift

    Swift中优雅的网络请求 官方github // 1.定一个enum enum MyService {xxxxcase showUser(id: Int)xxx }// 2.扩展这个enum,符合 ...

  6. Swift 网络请求库Moya的使用

    Moya是Swift中的网络库Alamofire的二次封装,Alamofire本身使用起来是很简单方便的,例子如下: func loadData(){var param = [String:Strin ...

  7. swift 网络请求中含有特殊字符的解决方案

    在网络请求时,URL出现中文或特殊字符时会造成请求失败,通常可使用  addingPercentEncoding(withAllowedCharacters: CharacterSet) 方法进行解决 ...

  8. Swift - 使用Alamofire通过HTTPS进行网络请求,及证书的使用

    (本文代码已升级至Swift3) 我原来写过一篇文章介绍如何使用证书通过SSL/TLS方式进行网络请求(Swift - 使用URLSession通过HTTPS进行网络请求,及证书的使用),当时用的是 ...

  9. swift网络数据请求方法

    搭建一个apache服务器,用php编写一个返回给客户端请求数据的脚本 1 <?php2 //header("Content-type:text/html;charset=utf-8& ...

最新文章

  1. 仿射加密简述和Win32版本实现
  2. 【实验】华为静态路由基础配置
  3. 用cn.hutool工具包进行图片上传下载示例
  4. 2020 中国开源年会(COSCon'20)再启程:开源向善(Open Source for Good)
  5. leetcode 684. 冗余连接()
  6. Andoid TextView显示富文本html内容及问题处理
  7. 软件系统中的颗粒度_意式浓缩咖啡丨甘醇香浓余韵长,研磨的度与质千万别忽视...
  8. 计算机网络(一)——一些概念
  9. Repo Jacking:依赖关系仓库劫持漏洞,影响谷歌GitHub等7万多个开源项目的供应链...
  10. 配置tomcat远程debug
  11. Scrapy(一)爬知乎所有用户信息
  12. Linux 下文件IO编程进程控制实验
  13. 小众软件android,七款优秀的小众软件,每款都是装逼神器!
  14. RouterOS如何实现多线路带宽叠加功能
  15. Vin码识别功能实现
  16. MacBook雷电3接口失灵不可用
  17. 什么牌子的蓝牙耳机好?重低音分体式蓝牙耳机!
  18. 线性代数 线性相关与线性表示的理解
  19. 【Git版本控制管理】Gitee(码云)和GitHub的使用
  20. Router-based Federation架构下如何解析路径

热门文章

  1. Dubbo的负载均衡
  2. 办公软件 Word2010 所有操作界面
  3. [机缘参悟-66]:深度思考-廉价的情绪抚慰
  4. 忘尘彼岸用计算机怎么弹出来,彼岸月华
  5. 港科夜闻|香港科大校友李圣泼先生向“科大校友基金”慷慨捐款,本科生宿舍第一座命名为「李贤义楼」...
  6. 环信WebIM 超详细教程01:点对点单聊
  7. 火鸟字幕合并器V0.5 Build2006.5.9正式发布,下载地址不变
  8. php discuz 顶,discuz模拟登录实现自动顶帖php程序 - Discuz
  9. vivado flash启动时间提速设置
  10. Android app集成支付宝支付