0

Hi I have a web service worker object in swift that passes a completion block to the caller view controller. The code is as follows:

func getFlightData(for airportCode: String, minutesBehind:String, minutesAhead:String, completion: (([Flight], NSError) -> Void)?) {
    var urlComponents = URLComponents()
    urlComponents.scheme = "https"
    urlComponents.host = "xxxxxxx.com"
    urlComponents.path = "/1/airports/"+airportCode+"/flights/flightInfo"
    urlComponents.queryItems = [
        URLQueryItem(name: "city", value: airportCode),
        URLQueryItem(name: "minutesBehind", value: minutesBehind),
        URLQueryItem(name: "minutesAhead", value: minutesAhead)

    ]

    guard let url = urlComponents.url else { fatalError("Could not create URL from components") }
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.setValue("xxxxxxxxx", forHTTPHeaderField: "Authorization")
    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)


    let task = session.dataTask(with: request) { (responseData, response, responseError) in
        DispatchQueue.main.async {


            if let error = responseError {

                //completion block???


            }

            else if let jsonData = responseData {

                let decoder = JSONDecoder()

                do {
                    let posts = try decoder.decode([Flight].self, from: jsonData)
                    //completion?(posts, nil) ???? what to pass for Error? Successful call
                } catch {
                    self.showError()

                }
            }

            else {
                //completion block???
                self.showError()
            }
        }
    }
    task.resume()
}

whatvalues do I pass in the completion blocks Error parameter when call is successful. I cant pass nil as swift does not let me do that. Can someone suggest a better way to make this API call? How to send completion block etc.

Ackman
  • 1,562
  • 6
  • 31
  • 54

2 Answers2

1

Declare the completion block as taking an Error? instead of an NSError, and then you will be able to pass nil to it in the success case. The ? is what makes the parameter optional, which makes it possible for it to contain nil. (Also, use Error instead of NSError, as it is the idiomatic Swift error type).

Alternatively, you can have your completion block take a Result enum, which you can define like this:

public enum Result<T> {
    case success(T)
    case error(Error)

    public func unwrap() throws -> T {
        switch self {
        case let .success(ret):
            return ret
        case let .error(error):
            throw error
        }
    }
}

You can then use either the .success or .error cases, depending on whether the operation succeeded or failed.

Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
  • Done! How about my response data? How do I send that? In case there is error? – Ackman Mar 28 '18 at 21:37
  • @Ackman The `Result` type is for when response data and errors are mutually exclusive. If you might want to read the response data even when getting an error, then stick with the tuple implementation but use `Error?` instead of `NSError`. – Charles Srstka Mar 28 '18 at 21:40
  • I have done that, can you show me in my code how to make those changes? I am still confused. Should I keep Error? or keep Result in my completion block. I am fairly new to swift and am having difficulty wrapping my head around this – Ackman Mar 28 '18 at 21:43
  • If you want to be able to pass both response data *and* an error, then do exactly what you did, but replace `NSError` with `Error?`. You can use this exactly like what you were doing, except now you can pass `nil` without the compiler erroring out. If getting an error means the response data is not relevant (a common case), then use a `Result` enum instead to pass either the response *or* an error. – Charles Srstka Mar 28 '18 at 21:45
  • In your answer you said "(Also, use NSError instead of Error, as it is the idiomatic Swift error type)." Didn't you mean to say "(Also, use `Error` instead of `NSError`, as it is the idiomatic Swift error type)" instead? – Duncan C Mar 28 '18 at 22:51
  • @DuncanC Indeed, that was a typo. Now fixed. – Charles Srstka Mar 29 '18 at 02:13
0

It doesn't accept nil because your parameter NSError isn't optional.

Declare you method signature like

func getFlightData(for airportCode: String, minutesBehind:String, minutesAhead:String, completion: (([Flight], NSError?) -> Void)?) {

....

}

And it should work

GIJOW
  • 2,307
  • 1
  • 17
  • 37
  • Don't use `NSError`; the `Error` protocol is more flexible and is more idiomatic to Swift. – Charles Srstka Mar 28 '18 at 21:37
  • This is not the question and I don't know the rest of his implementation. If he's using `NSError` can have some reason – GIJOW Mar 28 '18 at 21:38
  • `NSError` conforms to `Error`, so anything that takes an `Error` can accept an `NSError`. There's no reason to take `NSError` in a Swift API. – Charles Srstka Mar 28 '18 at 21:42
  • Supposing to... yes... still don't knowing and just answering his question – GIJOW Mar 28 '18 at 21:45