As we work on building on the app, quite commonly we need to leverage APIs from web applications. This post will be a note of an implementation of convenience function that utilizes URLSession.shared.DataTask that allows us to make an API call and handle the results.
You can take a look at the documentation, but here I am going to give a simplified version of explanation and a loose description of URLSession.shared.dataTask. The URLSession.shared.dataTask has the following declaration:
func dataTask(with request: URLRequest,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
Basically what this says is a call will look like this:
URLSession.shared.dataTask(<A URLRequest>, completionHandler: {<Completion handler code>})
As we can see, we need to provide a URLRequest. The URLRequest describes the destination URL and the method (GET, POST, …etc) of this request. The dataTask sends the request out for us, and will call back our completion handler code, so that we can “handle” the results. For example, we may be getting some data using this request and we need to manipulate the data once it is back, or we may want to handle the errors (like a 404 error).
Since iOS 13, Swift UI adopted the Model-View-ViewModel (MVVM) concept. Typically we have a model typically implemented as a struct handling the data, and the view handles all the UI. ViewModel is the broker in between, which is typically implemented as a class. The view can depend on some data, which is essentially provided by the model, commonly these data are stored in a struct.
Often we need to initiate a task to retrieve data using URLRequest from the ViewModel (e.g. resulting from the user clicks a button and sends an intent to ViewModel). Also, we may need a completion handler to change some data in a struct based on the returned results from the API call. For example, say we are trying to get the age of a customer and we want to show the age on our display. This is a very common task that is worth writing a convenience function for:
private func resumeDataTask(withRequest request: URLRequest, completion: @escaping (Result<Data, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: request, completionHandler: {(data, response, error) -> Void in
if let error = error {
completion(.failure(error))
print(error)
return
}
else if let data = data {
completion(.success(data))
}
})
task.resume()
}
This snippet includes how we make a dataTask, and calls back the completion handler which will be made in the view controller. The convenience function takes the URLRequest and the completion handler as inputs. When the results come back, it calls the completion handler.
In addition to convenience, actually we also want to use a function like this to be clear defining the completion handler from a ViewModel is the way we want to do things. Why? It may be tempted to write the completion handler when we call dataTask from the model implementation if you only have one or two occasions where you want to get data from the Internet. This seems like an easy task, however, we will see error when we try to do this in the model implementation, because we cannot mutate the data in struct with an escaping completion handler, since model is typically a struct which is a value type. It is okay though to modify values in the struct from the ViewModel, since it is a class and has no problem making a separate function call to write data to the model.