在繼承 APIKit 必須實作的 Request 方法,通常會在這個地方做 mapping 成 model 物件這件事:
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {}
雖然在 iOS 12.* 這樣寫是可行的,但是在 iOS 11 會報錯,就來看看解決辦法:
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
if let data = object as? Data {
return try JSONDecoder().decode(Response.self, from: data)
}
throw DataCastingError // 丟自訂轉型的錯誤
}
APIKit.DataParser
找了一些文章發現有這個 protocol 可以用 (官方程式碼),自訂義完之後,可以設定到 Request#dataParser 去。
預設值
先來看這個 dataParser 屬性的預設值,預設是用了 JSONDataParser 這個類別的物件
// Request.swift#L88-L90
// https://github.com/ishkawa/APIKit/blob/master/Sources/APIKit/Request.swift#L88-L90
var dataParser: DataParser {
return JSONDataParser(readingOptions: [])
}
在 parse(data:)
可以看出來是用 JSONSerialization 來處理,回傳值可能會是 Array 或是 Dictionary
// JSONDataParser.swift#L22-L29
// https://github.com/ishkawa/APIKit/blob/master/Sources/APIKit/DataParser/JSONDataParser.swift#L22-L29
public func parse(data: Data) throws -> Any {
guard data.count > 0 else {
return [:]
}
return try JSONSerialization.jsonObject(with: data, options: readingOptions)
}
原理
在 DataParser 裡面可以直接拿到原始的 Data 物件,接著在 parse 裡面可以預先處理這個資料
處理完之後作為返回值回傳,接著在 Request#response 這個方法就可以拿到了
動手做 DataParser
想做的事 |
---|
寫一次、所有的 Response 是 Codable 的 Request 都可以套用這個 DataParser |
DataParser
想要做到 解耦合 ,所以就把 decoder 另外宣告成一個屬性:
ModelDataParser.swift
import Foundation
import APIKit
struct ModelDataParser<T: Codable>: APIKit.DataParser {
var decoder: JSONDecoder
// MARK: APIKit.DataParser
var contentType: String? {
return "application/json"
}
func parse(data: Data) throws -> Any {
return try decoder.decode(T.self, from: data)
}
}
Request 的設定
條件是「Response 是 Codable 的 Request 就必須採用這個 DataParser」
因此可以透過 extension 和條件判斷來設定:
import APIKit
extension Request where Response: Codable {
var decoder: JSONDecoder {
// 如果有客製化的 Decoder 可以直接放在這邊
return JSONDecoder()
}
var dataParser: DataParser {
return ModelDataParser<Response>(decoder: decoder)
}
}
使用方法
回到 Request 的 response 方法,寫法如下:
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
if let result = object as? Response {
return result
}
throw DataParsingError // 丟符合情境的錯誤
}
因為這邊資料出口都是 Any 這個型別,所以還需要再 as ?
轉型一次才有辦法回傳