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()
}

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