使用Swift 5结果类型构建简单的异步API请求
Apple终于在2019年3月底将Swift 5发布到稳定版频道。它捆绑在macOS的Xcode 10.2中。 Swift 5为Apple平台中的Swift标准库提供了ABI稳定性。 这意味着,所有未来的Swift版本将与使用Swift 5代码编写的应用二进制兼容。 它还引入了App Thinning,缩小了应用程序的大小,因为ABI的稳定性意味着二进制文件不必将所有Swift标准库嵌入应用程序包中,从而减小了应用程序的大小。 从iOS 12.2开始,操作系统将包括Swift运行时和标准库。
您可以在下面的链接中阅读与Swift的未来有关的所有有关模块稳定性和库演变的信息。
ABI稳定性和更多
在MacOS,iOS,watchOS和tvOS上稳定Swift的ABI是一个长期的目标。 swift.org 是一个稳定的ABI。
在本文中,我们将讨论Swift 5的新Result
类型,以及如何利用它来创建异步API请求并简化对完成处理程序闭包的处理。
在开始之前,让我们看看我们通常如何在Swift中创建一个异步函数。 我们通常使用几种方法。
1. Objective-C风格
使用Objective-C样式,我们创建一个带有多个参数的单个回调closure
,其中包含异步函数中的可选result value
和可选error
。
func fetchMovies(URL:URL,completionHandler:@ 转义 ([Movie] ?, Error?)-> Void){
...
}
2. Swift风格
在这种样式中,我们创建2个完成closures
。 一种是将result value
作为参数处理成功,另一种是the error
作为参数处理失败。
func fetchMovies(URL:URL,successHandler:@ 逃逸 ([Movie])->无效,errorHandler:@ 逃避 (Error?)->无效){
...
}
介绍Swift 5结果类型
Swift 5最后引入了新的Result
类型,以使用enum
来处理异步函数的结果。 只有两种情况都使用带有关联值的Swift Generic
:
1. Success
与结果的价值。
2.实现Error
协议的类型Failure
。
Result
类型确实可以帮助我们简化异步函数结果的处理,因为只有两种情况需要处理, success
和failure
。 让我们开始为URLSession
数据任务创建一个简单的扩展,该扩展将Result
类型用于完成处理程序的关闭。
使用结果类型扩展URL会话数据任务
众所周知,使用URLSession
进行数据任务时,我们需要传递带有URLResponse
, Data
和Error
参数的完成处理程序闭包。 它们的类型都是可选的,我们需要使用let或guard语句来检查它们,以检查http响应状态代码,检索数据并将数据解码到模型中。
让 url = URL(string:“ https://exampleapi.com/data.json”)!
URLSession.shared.dataTask(with:url){(data:Data ?,响应:URLResponse ?,错误:Error?) 在
如果 让错误=错误{
//处理错误
返回
}
警卫队 让回应=回应否则 {
//处理空响应
返回
}
保护数据=数据其他 {
//处理空数据
返回
}
//处理将数据解码为模型
}
每次都很难处理这种方法。 我们可以使用“ Result
类型”创建更好的解决方案。 因此,让我们为URLSession
做一个简单的扩展,以处理带有Result
类型的数据任务。
扩展 URLSession {
func dataTask(使用url:URL,结果: @转义 (结果)->无效)-> URLSessionDataTask {
return dataTask(with:url){(data,response,error) in
如果 让错误=错误{
结果(。失败(错误))
返回
}
保护 let response =响应, let data = data else {
let error = NSError(domain:“ error”,code:0,userInfo: nil )
结果(。失败(错误))
返回
}
结果(.success((响应,数据)))
}
}
}
我们可以像通常调用传递URL
的dataTask一样调用此函数,但是这次我们将以Result
Type作为参数传递完成闭包。
URLSession.shared.dataTask(with:url){(结果) 在
切换结果{
case .success( 让响应, 让数据):
//处理数据和响应
打破
情况 .failure( 让错误):
//处理错误
打破
}
}
与使用以前没有Result
类型的完成句柄相比,此方法看起来非常简单干净。
为MovieDB(TMDb)API创建API服务
让我们使用我们的新知识来构建一个简单的MovieServiceAPI
类,该类使用电影数据库(TMDb)API来按类别检索电影,按ID检索单个电影。
您可以在下面的链接中注册API密钥。
API文件
针对每个OAS(Swagger)和RAML规范的托管API文档。 由Stoplight.io驱动。 文档,模拟…… developers.themoviedb.org
首先,让我们创建一个实现Codable
的Model
,以处理将JSON
数据解码为Swift Model
。
public struct MoviesResponse:Codable {
公开 让页:Int
public let totalResults:整数
public let totalPages:Int
公开 让步结果:[电影]
}
公共 结构电影:可编码{
公开 让 ID:Int
公开 租借标题:字符串
公共 让概述:字符串
公开发布日期:日期
公众 让票平均:双倍
公众 让票数:整数
公众 让成年人:布尔
}
接下来,让我们创建具有所有属性的类,例如baseURL
, apiKey
, jsonDecoder
和由enum
表示的endpoint
。
MovieServiceAPI
类 {
公共 静态 让共享=MovieServiceAPI
()
私人 初始化 (){}
私人 让 urlSession = URLSession.shared
私有 let baseURL = URL(string:“ https://api.themoviedb.org/3”)!
私人 let apiKey =“ PUT_API KEY HERE”
私人 让 jsonDecoder:JSONDecoder = {
让jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
让dateFormatter = DateFormatter()
dateFormatter.dateFormat =“ yyyy-mm-dd”
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
返回jsonDecoder
}()
//枚举端点
枚举端点:String,CustomStringConvertible,CaseIterable {
case nowPlaying =“ now_playing”
案件即将到来
案例受欢迎
case topRated =“ top_rated”
}
}
我们还需要创建一个Error
enum
以表示我们的API请求中的几种错误情况。
MovieServiceAPI
类 {
...
公共 枚举 APIServiceError:错误{
案例 apiError
大小写无效
大小写无效
案例号
案例解码错误
}
}
接下来,让我们创建一个私有函数,该函数使用decodable
类型的Swift Generic Constraint获取资源。 该函数将接受将用于以Result
类型启动URLsession
数据任务的URL
。
私人 功能 fetchResources <T:可解码的(URL:URL,完成: @转义 (Result )->无效){
保护 var urlComponents = URLComponents(url:url,resolvingAgainstBaseURL: true ) else {
完成(.failure(.invalidEndpoint))
返回
}
让 queryItems = [URLQueryItem(name:“ api_key”,value:apiKey)]
urlComponents.queryItems = queryItems
警卫 让 url = urlComponents.url else {
完成(.failure(.invalidEndpoint))
返回
}
urlSession.dataTask(with:url){(结果) 在
切换结果{
case .success( let (响应,数据)):
guard let statusCode =(响应为 ?HTTPURLResponse)?. statusCode,200。.<299〜= statusCode 其他 {
完成(.failure(.invalidResponse))
返回
}
做 {
让值= 尝试 self .jsonDecoder.decode(T。self,from:data)
完成(。成功(值))
} {
完成(.failure(.decodeError))
}
情况 .failure( 让错误):
完成(.failure(.apiError))
}
}。恢复()
}
在具有完成关闭功能的URLSession
dataTask中,我们检查result enum
。 如果成功,我们将检索数据,然后检查响应HTTP状态代码以确保其范围在200到299之间。然后,使用jsonDecoder
我们使用来自函数参数的模型的通用decodable
约束对json
数据进行decodable
。
从端点获取电影列表
接下来,让我们创建第一个方法来使用特定endpoint
检索电影列表。 首先,我们通过将baseURL
附加传递的端点开始定义URL
。 然后,让我们使用之前创建的fetchResource
方法来检索已经解码的电影数组。
MovieServiceAPI
类 {
...
公共 函数 fetchMovies(来自端点:端点,结果: @转义 (Result )->无效){
让 movieURL = baseURL
.appendingPathComponent(“ movie”)
.appendingPathComponent(endpoint.rawValue)
fetchResources(URL:movieURL,完成:result)
}
要使用该方法,我们可以像这样调用它:
MovieAPIService.shared.fetchMovies(来自:.nowPlaying){(结果:Result ) 在
切换结果{
case .success( 让 movieResponse):
打印(movieResponse.results)
情况 .failure( 让错误):
打印(error.localizedDescription)
}
}
使用电影ID提取单个电影
为了使用电影ID检索单个电影,我们将通过附加电影ID来构造url
,然后使用相同的fetchResource
方法为结果检索单个解码的电影。
MovieServiceAPI
类 {
...
public func fetchMovie(movieId:Int,result: @escaping (Result )-> Void){
让 movieURL = baseURL
.appendingPathComponent(“ movie”)
.appendingPathComponent(String(movieId))
fetchResources(URL:movieURL,完成:result)
}
要使用该方法,我们可以像这样调用它:
MovieAPIService.shared.fetchMovie(movieId:1234){(结果:Result ) 在
切换结果{
案例。成功( 让电影):
打印(电影)
情况 .failure( 让错误):
打印(error.localizedDescription)
}
}
结论
就是这样,我们的API服务看起来很简单且干净,使用Result
Type作为完成处理程序。 Result
类型还有许多其他功能,例如map
和flatMap
Result
到另一个Result
。 Result
类型确实将回调完成处理程序简化为成功和失败的情况。
Swift 5非常出色,我相信还有更多令人惊叹的新功能,我们可以期待Swift的下一个发展。 继续进行终身学习,让Swifting happy开心。
- 添加collections夹functioniPhone应用程序iPhone SDK
- 带有背景的UINavigationBar
- UINavigationBar setBackgroundImage:forBarMetrics:不工作
- 寻找GPS信号强度
- 我可以做NSVariableFromString像NSClassFromString和NSSelectorFromString?
- 在iOS中检测PAN手势的方向
- 在Objective-C中存储和加载大量数据的最佳方式
- 将UITabBarController与UINavigationController结合使用
- 我怎样才能inheritanceUITableView?