close
自訂Model to query string encoder,
在使用HttpGet protocol旳時候,
會將Model encode 成 query string。
另外在使用HttpPost的時候,
將相同的Model encode 成Json,可供塞進body內。
之前是採用Reflection的作法,
這次想研究看看使用Swift 4 之後的Encodable看看是否能做到。
Encoder
import Foundation
class QueryItemEncoder {
/// Returns a strings file-encoded representation of the specified value.
func encode(_ value: T) throws -> [URLQueryItem] {
let encoding = QueryItemEncoding()
try value.encode(to: encoding)
return encoding.data.queryItems
}
}
fileprivate struct QueryItemEncoding: Encoder {
/// Stores the actual strings file data during encoding.
fileprivate final class Data {
private(set) var queryItems: [URLQueryItem] = []
func encode(key codingKey: CodingKey, value: String?) {
// 略過nil
guard let value = value else {
return
}
queryItems.append(URLQueryItem(name: codingKey.stringValue, value: value))
}
}
fileprivate var data: Data
init(to encodedData: Data = Data()) {
self.data = encodedData
}
var codingPath: [CodingKey] = []
let userInfo: [CodingUserInfoKey : Any] = [:]
func container(keyedBy type: Key.Type) -> KeyedEncodingContainer {
var container = QueryItemNestedContainerEncoding(to: data)
container.codingPath = codingPath
return KeyedEncodingContainer(container)
}
func unkeyedContainer() -> UnkeyedEncodingContainer {
var container = QueryItemUnkeyedEncoding(to: data)
container.codingPath = codingPath
return container
}
func singleValueContainer() -> SingleValueEncodingContainer {
var container = StringsSingleValueEncoding(to: data)
container.codingPath = codingPath
return container
}
}
fileprivate struct QueryItemNestedContainerEncoding: KeyedEncodingContainerProtocol {
var codingPath: [CodingKey] = []
private let data: QueryItemEncoding.Data
private let desc: String = "不處理這型態"
init(to data: QueryItemEncoding.Data) {
self.data = data
}
mutating func encodeNil(forKey key: Key) throws {
data.encode(key: key, value: nil)
}
mutating func encode(_ value: Bool, forKey key: Key) throws {
assert(false, desc)
throw APIError(type: .error, localDesc: desc)
}
mutating func encode(_ value: String, forKey key: Key) throws {
data.encode(key: key, value: value)
}
mutating func encode(_ value: Double, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: Float, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: Int, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: Int8, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: Int16, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: Int32, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: Int64, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: UInt, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: UInt8, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: UInt16, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: UInt32, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: UInt64, forKey key: Key) throws {
data.encode(key: key, value: String(value))
}
mutating func encode(_ value: T, forKey key: Key) throws {
var encoding = QueryItemEncoding(to: data)
encoding.codingPath.append(key)
try value.encode(to: encoding)
}
mutating func nestedContainer(
keyedBy keyType: NestedKey.Type,
forKey key: Key) -> KeyedEncodingContainer {
var container = QueryItemNestedContainerEncoding(to: data)
container.codingPath = [key]
return KeyedEncodingContainer(container)
}
mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
var container = QueryItemUnkeyedEncoding(to: data)
container.codingPath = [key]
return container
}
mutating func superEncoder() -> Encoder {
let superKey = Key(stringValue: "super")!
return superEncoder(forKey: superKey)
}
mutating func superEncoder(forKey key: Key) -> Encoder {
var encoding = QueryItemEncoding(to: data)
encoding.codingPath = [key]
return encoding
}
}
fileprivate struct QueryItemUnkeyedEncoding: UnkeyedEncodingContainer {
private let data: QueryItemEncoding.Data
init(to data: QueryItemEncoding.Data) {
self.data = data
}
var codingPath: [CodingKey] = []
var firstKey: CodingKey { codingPath.first! }
private(set) var count: Int = 0
private mutating func nextIndexedKey() -> CodingKey {
let nextCodingKey = IndexedCodingKey(intValue: count)!
count += 1
return nextCodingKey
}
private struct IndexedCodingKey: CodingKey {
let intValue: Int?
let stringValue: String
init?(intValue: Int) {
self.intValue = intValue
self.stringValue = String(intValue)
}
init?(stringValue: String) {
return nil
}
}
mutating func encodeNil() throws {
data.encode(key: firstKey, value: nil)
}
mutating func encode(_ value: Bool) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: String) throws {
data.encode(key: firstKey, value: value)
}
mutating func encode(_ value: Double) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: Float) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: Int) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: Int8) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: Int16) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: Int32) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: Int64) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: UInt) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: UInt8) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: UInt16) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: UInt32) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: UInt64) throws {
data.encode(key: firstKey, value: String(value))
}
mutating func encode(_ value: T) throws {
var stringsEncoding = QueryItemEncoding(to: data)
stringsEncoding.codingPath = [firstKey]
try value.encode(to: stringsEncoding)
}
mutating func nestedContainer(
keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer {
var container = QueryItemNestedContainerEncoding(to: data)
container.codingPath = [nextIndexedKey()]
return KeyedEncodingContainer(container)
}
mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
var container = QueryItemUnkeyedEncoding(to: data)
container.codingPath = [nextIndexedKey()]
return container
}
mutating func superEncoder() -> Encoder {
var encoding = QueryItemEncoding(to: data)
encoding.codingPath.append(nextIndexedKey())
return encoding
}
}
fileprivate struct StringsSingleValueEncoding: SingleValueEncodingContainer {
private let data: QueryItemEncoding.Data
init(to data: QueryItemEncoding.Data) {
self.data = data
}
var codingPath: [CodingKey] = []
private var codingKey: CodingKey { codingPath.first! }
mutating func encodeNil() throws {
data.encode(key: codingKey, value: nil)
}
mutating func encode(_ value: Bool) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: String) throws {
data.encode(key: codingKey, value: value)
}
mutating func encode(_ value: Double) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: Float) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: Int) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: Int8) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: Int16) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: Int32) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: Int64) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: UInt) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: UInt8) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: UInt16) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: UInt32) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: UInt64) throws {
data.encode(key: codingKey, value: String(value))
}
mutating func encode(_ value: T) throws {
var encoding = QueryItemEncoding(to: data)
encoding.codingPath = codingPath
try value.encode(to: encoding)
}
}
HttpGet extension
/// Parameter is model and conform Encodable
extension HTTPGet where Parameter: Encodable {
func invokeAsync(_ parameter: Parameter, completion: @escaping (Swift.Result) -> Void) {
callAPIWithReachability { (error: Error) in
completion(.failure(error))
} api: {
do {
var urlComponent = URLComponents(string: urlString)
urlComponent?.queryItems = try QueryItemEncoder().encode(parameter)
get(url: urlComponent?.url, completion: completion)
} catch {
completion(.failure(error))
}
}
}
@available(iOS 13.0.0, *)
func invokeAsync(_ parameter: Parameter) async throws -> Model {
try await reachability()
// Parameter to URLComponent's query string
var urlComponent = URLComponents(string: urlString)
urlComponent?.queryItems = try QueryItemEncoder().encode(parameter)
return try await get(url: urlComponent?.url)
}
}
WebAPI request parameter swift model
struct XXXWebAPIParameter: Encodable {
let id: String
let value: Int
let desc: [String]
let option: String? = "5"
}
輸出
https://xxxHost/xxxPath?id=1&value=2&desc=3&desc=4&option=5
全站熱搜
留言列表