Swift 如何使用泛型实现 REST APIManager
How to use Generics to implement a REST APIManager in Swift
APIManager 应该能够根据所讨论的 API 接受不同的 HTTP 正文作为输入,并将响应映射到所需的模型结构以供在 ui[ 中使用=14=]
这是我想在我的代码中使用的示例 JSON 响应:
{
"response_code": 0,
"data": {
"app_version_update": "",
"offers": [
{
"title": "Special Scheme1",
"image": "http://59.145.109.138:11101/Offers/BGL_banner_1080_x_540_1.jpg",
"r": 1.0,
"result_count": 5.0
},
{
"title": "test 1",
"image": "http://59.145.109.138:11101/Offers/Yoho-National-Park2018-10-27_10-10-52-11.jpg",
"r": 2.0,
"result_count": 5.0
},
{
"title": "Offer Test 1234444",
"image": "http://59.145.109.138:11101/Offers/Stanley-Park2018-10-27_10-11-27-44.jpg",
"r": 3.0,
"result_count": 5.0
}
],
"rate": 2000
},
"meta": {
"api_version": 2.0
}
}
下面是执行 HTTP Post 调用的非通用函数 load():
func load(urlRequest: URLRequest, withCompletion completion: @escaping (_ response: APIResponse) -> Void) {
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
print("Error fetching data from server\nERROR: \(String(describing: error))")
return
}
guard let jsonData = data else {
print("Response Data is empty")
return
}
printResponseBody(response: data)
let decoder = JSONDecoder()
let response = try? decoder.decode(APIResponse.self, from: jsonData)
guard let decodedResponse = response else {
print("Unable to parse data from response")
return
}
print("Decoded Response: ", decodedResponse)
DispatchQueue.main.async { completion(decodedResponse) }
}
task.resume()
}
下面是上述示例的非通用API响应结构API:
struct APIResponse: Codable {
let responseCode: Int
let data: ResultSet
let meta: Meta
enum CodingKeys: String, CodingKey {
case responseCode = "response_code"
case data, meta
}
}
// MARK: - DataClass
struct ResultSet: Codable {
let appVersionUpdate: String
let offers: [Offer]
let rate: Int
enum CodingKeys: String, CodingKey {
case appVersionUpdate = "app_version_update"
case offers, rate
}
}
// MARK: - Offer
struct Offer: Codable, Identifiable {
let id: Int
let title: String
let image: String?
let r, resultCount: Int
enum CodingKeys: CodingKey {
case id, r, title, image, resultCount
var stringValue: String {
switch self {
case .id, .r: return "r"
case .title: return "title"
case .image: return "image"
case .resultCount: return "result_count"
}
}
}
}
// MARK: - Meta
struct Meta: Codable {
let apiVersion: Int
enum CodingKeys: String, CodingKey {
case apiVersion = "api_version"
}
注意:
所有 api 共享相同的响应结构,但只有数据部分根据 api 发生变化。
谁能帮我实现一个通用的 APIManager 文件,它可以采用不同的 UrlRequest 输入,启动 URLSession 并将获得的 JSON 响应数据对象解码为不同的模型结构,以便在 ui代码
首先使结构通用——顺便说一下,如果你指定了convertFromSnakeCase
键解码策略,你可以省略CodingKeys
——如果你不打算将数据编码为符合Decodable
足够了
struct APIResponse<D : Decodable>: Decodable {
let responseCode: Int
let data: D
let meta: Meta
enum CodingKeys: String, CodingKey {
case responseCode = "response_code", data, meta
}
}
然后使函数也通用并且 return Result
类型中的所有错误
永远不要忽略解码环境中 try?
的错误
func load<T : Decodable>(urlRequest: URLRequest, type: T.Type, withCompletion completion: @escaping (Result<APIResponse<T>,Error>) -> Void) {
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
if let error = error { completion(.failure(error)); return }
do {
let response = try JSONDecoder().decode(APIResponse<T>.self, from: data!)
DispatchQueue.main.async { completion(.success(response)) }
} catch {
DispatchQueue.main.async { completion(.failure(error)) }
}
}
task.resume()
}
关于感叹号:如果error
是nil
.
,data
一定有值
如果将 DispatchQueue
闭包放入调用方法中,您甚至可以编写
func load<T : Decodable>(urlRequest: URLRequest, type: T.Type, withCompletion completion: @escaping (Result<APIResponse<T>,Error>) -> Void) {
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
if let error = error { completion(.failure(error)); return }
completion( Result{ try JSONDecoder().decode(APIResponse<T>.self, from: data!)})
}
task.resume()
}
如果你在调用方法时注释闭包类型你甚至可以省略type
参数
func load<T : Decodable>(urlRequest: URLRequest, completion: @escaping (Result<APIResponse<T>,Error>) -> Void) { ...
load(urlRequest: request) { (result : Result<APIResponse<ResultSet>,Error>) in
DispatchQueue.main.async {
switch result {
case .success(let response): print(response)
case .failure(let error): print(error)
}
}
}
APIManager 应该能够根据所讨论的 API 接受不同的 HTTP 正文作为输入,并将响应映射到所需的模型结构以供在 ui[ 中使用=14=]
这是我想在我的代码中使用的示例 JSON 响应:
{
"response_code": 0,
"data": {
"app_version_update": "",
"offers": [
{
"title": "Special Scheme1",
"image": "http://59.145.109.138:11101/Offers/BGL_banner_1080_x_540_1.jpg",
"r": 1.0,
"result_count": 5.0
},
{
"title": "test 1",
"image": "http://59.145.109.138:11101/Offers/Yoho-National-Park2018-10-27_10-10-52-11.jpg",
"r": 2.0,
"result_count": 5.0
},
{
"title": "Offer Test 1234444",
"image": "http://59.145.109.138:11101/Offers/Stanley-Park2018-10-27_10-11-27-44.jpg",
"r": 3.0,
"result_count": 5.0
}
],
"rate": 2000
},
"meta": {
"api_version": 2.0
}
}
下面是执行 HTTP Post 调用的非通用函数 load():
func load(urlRequest: URLRequest, withCompletion completion: @escaping (_ response: APIResponse) -> Void) {
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
print("Error fetching data from server\nERROR: \(String(describing: error))")
return
}
guard let jsonData = data else {
print("Response Data is empty")
return
}
printResponseBody(response: data)
let decoder = JSONDecoder()
let response = try? decoder.decode(APIResponse.self, from: jsonData)
guard let decodedResponse = response else {
print("Unable to parse data from response")
return
}
print("Decoded Response: ", decodedResponse)
DispatchQueue.main.async { completion(decodedResponse) }
}
task.resume()
}
下面是上述示例的非通用API响应结构API:
struct APIResponse: Codable {
let responseCode: Int
let data: ResultSet
let meta: Meta
enum CodingKeys: String, CodingKey {
case responseCode = "response_code"
case data, meta
}
}
// MARK: - DataClass
struct ResultSet: Codable {
let appVersionUpdate: String
let offers: [Offer]
let rate: Int
enum CodingKeys: String, CodingKey {
case appVersionUpdate = "app_version_update"
case offers, rate
}
}
// MARK: - Offer
struct Offer: Codable, Identifiable {
let id: Int
let title: String
let image: String?
let r, resultCount: Int
enum CodingKeys: CodingKey {
case id, r, title, image, resultCount
var stringValue: String {
switch self {
case .id, .r: return "r"
case .title: return "title"
case .image: return "image"
case .resultCount: return "result_count"
}
}
}
}
// MARK: - Meta
struct Meta: Codable {
let apiVersion: Int
enum CodingKeys: String, CodingKey {
case apiVersion = "api_version"
}
注意:
所有 api 共享相同的响应结构,但只有数据部分根据 api 发生变化。
谁能帮我实现一个通用的 APIManager 文件,它可以采用不同的 UrlRequest 输入,启动 URLSession 并将获得的 JSON 响应数据对象解码为不同的模型结构,以便在 ui代码
首先使结构通用——顺便说一下,如果你指定了convertFromSnakeCase
键解码策略,你可以省略CodingKeys
——如果你不打算将数据编码为符合Decodable
足够了
struct APIResponse<D : Decodable>: Decodable {
let responseCode: Int
let data: D
let meta: Meta
enum CodingKeys: String, CodingKey {
case responseCode = "response_code", data, meta
}
}
然后使函数也通用并且 return Result
类型中的所有错误
永远不要忽略解码环境中 try?
的错误
func load<T : Decodable>(urlRequest: URLRequest, type: T.Type, withCompletion completion: @escaping (Result<APIResponse<T>,Error>) -> Void) {
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
if let error = error { completion(.failure(error)); return }
do {
let response = try JSONDecoder().decode(APIResponse<T>.self, from: data!)
DispatchQueue.main.async { completion(.success(response)) }
} catch {
DispatchQueue.main.async { completion(.failure(error)) }
}
}
task.resume()
}
关于感叹号:如果error
是nil
.
data
一定有值
如果将 DispatchQueue
闭包放入调用方法中,您甚至可以编写
func load<T : Decodable>(urlRequest: URLRequest, type: T.Type, withCompletion completion: @escaping (Result<APIResponse<T>,Error>) -> Void) {
let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in
if let error = error { completion(.failure(error)); return }
completion( Result{ try JSONDecoder().decode(APIResponse<T>.self, from: data!)})
}
task.resume()
}
如果你在调用方法时注释闭包类型你甚至可以省略type
参数
func load<T : Decodable>(urlRequest: URLRequest, completion: @escaping (Result<APIResponse<T>,Error>) -> Void) { ...
load(urlRequest: request) { (result : Result<APIResponse<ResultSet>,Error>) in
DispatchQueue.main.async {
switch result {
case .success(let response): print(response)
case .failure(let error): print(error)
}
}
}