Search code examples
urlsessionswift5

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


Swift 5 introduces new Result type to handle the result of an asynchronous function. I want to know the way to use this new result type for 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()
    }

How can i rewrite this function using swift 5 Result type?


Solution

  • You want to create an enum that specifies the possible cases in your result (e.g. success or failure). Then you add a completion to your getCategorByAPI() method that is of type Result<Data, Error>. From there, inside url session, you will call your completion handler passing in either the data on .success or the error on .failure.

    You can also do cool things like override Result's get() method and extend Result to decode your data :D

    Check it out:

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

    I haven't tested the above but have something like this implemented in a current project. Here are some articles I read to learn about how to implement:

    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