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

arrow
arrow
    全站熱搜

    小賢 發表在 痞客邦 留言(0) 人氣()