如何在Swift 5 URLSession中使用新的Result类型?

5

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

我有以下代码。

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的Result类型重写这个函数?

2个回答

7
您想创建一个枚举(enum),以指定结果的可能情况(例如成功或失败)。然后,将类型为 Result<Data, Error> 的完成函数添加到您的 getCategorByAPI() 方法中。从那里,在 url 会话中,您将调用完成处理程序,在 .success 上传递 data 或在 .failure 上传递 error
您还可以做一些很酷的事情,比如重写 Resultget() 方法并扩展 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


谢谢@christinam,你的帮助真是太棒了,无论是代码还是链接对我都很有帮助。在上面的代码中,在检查状态码之前需要添加一行代码。'let httpResponse = response as! HTTPURLResponse' - Asif Newaz

5

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

extension URLSession {

    /// A type safe URL loader that calls completion handler with Result type
    func jsonDecodableTask<T: Decodable>(with url: URLRequest, decoder: JSONDecoder = JSONDecoder(), completion: @escaping (Result<T, Error>) -> Void) -> URLSessionDataTask {
        self.dataTask(with: url) { (data, _, error) in
            guard error == nil else {
                completion(.failure(error!))
                return
            }
            guard let data = data 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()


你能分享一下你是如何调用这个函数的吗?谢谢。 - Muhammad Shahzad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接