【问题标题】:How to use Generics to implement a REST APIManager in Swift如何使用泛型在 Swift 中实现 REST APIManager
【发布时间】:2021-04-13 07:07:03
【问题描述】:

APIManager 应该能够根据相关 API 接受不同的 HTTP 正文作为输入,并将响应映射到所需的模型结构以在 ui 中使用

这是我想在我的代码中使用的示例 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 的非通用 APIResponse 结构:

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 代码中使用

【问题讨论】:

    标签: swift generics urlsession


    【解决方案1】:

    首先使结构通用——顺便说一下,如果你指定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
        }
    }
    

    然后使函数也泛型并返回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()
    }
    

    关于感叹号:如果errornil,则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)
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2014-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-10-28
      • 1970-01-01
      相关资源
      最近更新 更多