如何使用 swift 5 URLSession 中引入的新结果类型?

How to use new Result type introduced in swift 5 URLSession?

Swift 5引入了新的Result类型来处理异步函数的结果。我想知道如何为 URLSession 使用这种新的结果类型。

I have this following code.

func getCategorByAPI()
    {
        //Base Url is from an static variable
        let url = URL(string: URLManager.aPIBaseURL+"category")!
        var request  = URLRequest(url: url)
        request.httpMethod = "GET"
        let boundary = "Boundary-\(UUID().uuidString)"
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

        let task = URLSession.shared.dataTask(with: request as URLRequest) {
            data, response, error in

            if error != nil {
                //print("error=\(error)")
                return
            }

            do {
                let json = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary
                print(json)
            }catch
            {
                print(error)
            }

        }

        task.resume()
    }

如何使用 swift 5 结果类型重写此函数?

您想创建一个 enum 来指定结果中的可能情况(例如成功或失败)。然后,您向类型为 Result<Data, Error>getCategorByAPI() 方法添加完成。从那里,在 url 会话中,您将调用完成处理程序,传入 .success 上的 data.failure 上的 error

您还可以做一些很酷的事情,例如覆盖结果的 get() 方法并扩展 Result 来解码您的数据 :D

查看:

enum Result<Success, Error: Swift.Error> {
    case success(Success)
    case failure(Error)
}

// override the Result.get() method
extension Result {
    func get() throws -> Success {
        switch self {
        case .success(let value):
            return value
        case .failure(let error):
            throw error
        }
    }
}

// use generics - this is where you can decode your data
extension Result where Success == Data {
    func decoded<T: Decodable>(using decoder: JSONDecoder = .init()) throws -> T {
        let data = try get()
        return try decoder.decode(T.self, from: data)
    }
}


func getCategorByAPI(completion: (Result<Data, Error>) -> Void)
{
    // You might want to stick this into another method
    let url = URL(string: URLManager.aPIBaseURL+"category")!
    var request  = URLRequest(url: url)
    request.httpMethod = "GET"
    let boundary = "Boundary-\(UUID().uuidString)"
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

    URLSession.shared.dataTask(with: request as URLRequest) {
        data, response, error in

        if error != nil {
            completion(.failure(error))
            return
        }

        if !(200...299).contains(httpResponse.statusCode) && !(httpResponse.statusCode == 304) {
            let httpError = // ... convert httpResponse.statusCode into a more readable error
                completion(.failure(httpError))
        }

        if let data = data {
            completion(.success(data))
        }
        }.resume()
}

我没有测试过上面的内容,但是在当前项目中实现了类似的东西。以下是我阅读的一些文章,以了解如何实施:

https://www.hackingwithswift.com/articles/161/how-to-use-result-in-swift
https://medium.com/@pavlepesic/how-to-use-swift-5-result-with-codable-protocol-824c9a951af9

我在 URLSession 上写了一个扩展,它抽象出了错误处理和解码的样板代码。

enum URLError: Error {
    case noData, decodingError
}

extension URLSession {

    /// A type safe URL loader that either completes with success value or error with Error
    func jsonDecodableTask<T: Decodable>(with url: URLRequest, decoder: JSONDecoder = JSONDecoder(), completion: @escaping (Result<T, Error>) -> Void) -> URLSessionDataTask {
       self.dataTask(with: url) { (data, response, error) in
            DispatchQueue.main.async {
                guard error == nil else {
                    completion(.failure(error!))
                    return
                }
                guard let data = data, let _ = response else {
                    completion(.failure(URLError.noData))
                    return
                }
                do {
                    let decoded = try decoder.decode(T.self, from: data)
                    completion(.success(decoded))
                } catch  {
                    completion(.failure(error))
                }
            }
        }
    }

    func jsonDecodableTask<T: Decodable>(with url: URL, decoder: JSONDecoder = JSONDecoder(), completion: @escaping (Result<T, Error>) -> Void) -> URLSessionDataTask {
       self.jsonDecodableTask(with: URLRequest(url: url), decoder: decoder, completion: completion)
    }
}

用法

struct Person: Codable {
    let name: String
    let age: Int
}

let url = URL(string: "https://abcd.com")!

URLSession.shared.jsonDecodableTask(with: url) { (result: Result<Person, Error>) in
    switch result {
    case .success(let person):
        print("Person \(person.name)")
    case .failure(let error):
        print(error)
    }
}.resume()

你也可以像这样传递你自己的JSONDecoder

let urlRequest = URLRequest(url: url)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
URLSession.shared.jsonDecodableTask(with: urlRequest, decoder: decoder) { (result: Result<Person, Error>) in
    // Same as above
}.resume()