QuantumMindsLogo
Back to top

Better Browsers:

NEXT

Swift 4.0 Completion Blocks with Generics and Decodable protocol

February 25, 2018

Writing networking API calls for the endpoints that exist on a server is not hard but it's tedious work. This usually ends up
being handled by some sort of a networking class that uses URLSession to make URLRequests for each endpoint. This is all
good and dandy but usually a networking function that makes a URLRequest and expects a response looks like this:




public func getUser(completion: @escaping UserCompletionBlock) {

let url = URL(string: "https://urlEndPointForThisRequest.com/getuser")
let request = URLRequest(url: url)

let completionWrapper = { (object: User?, error: error?) -> Void in
DispatchQueue.main.async {
completion(success, error)
}
}


let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
completionWrapper(nil, error)
return
}

if let response = response {
if let error = self.handleNetworkError(data: data, response: response) {
completionWrapper(nil, error)
return
}
}

//We got a response to parse
if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode == 200 {
//Parse the json here or something
let user = User(....)
completionWrapper(user, nil)
} else {
//We don't know what happened but we should still return
completionWrapper(nil, nil)
}
}
}
}


I'm sure we've all seen code like the above. It's not good or fun to write such repetitive code, checking for errors, response
and then finally parsing the JSON data... And now our getUser function is quite big and we have lots of other functions that
are so similar to it... Sigh

GENERICS TO THE RESCUE!


Side Tip:
In Swift 4.0 we can use the Decodable (or Codable if you like) protocol to parse the json to create our data model objects!

So let's take all this error, response and json parsing part into it's own function called processNetworkResponse using
Generics:




public typealias GenericCompletionBlock = (T?, ApplicationError?) -> Void

public func processNetworkResponse(data: Data?, response: URLResponse?, error: Error?, type: T.Type,
completion: @escaping GenericCompletionBlock) {

let completionWrapper = { (object: T?, error: ApplicationError?) -> Void in
DispatchQueue.main.async {
completion(object, error)
}
}

if error != nil {
let applicationError = ApplicationError(error: error)
completionWrapper(nil, applicationError)
}

if let response = response, let error = self.handleNetworkError(data: data, response: response) {
completionWrapper(nil, error)
}

if let json = data {
if let jsonString = String(data: json, encoding: String.Encoding.utf8) {
print("json: \(jsonString)")
}
if let object = self.jsonParser.parse(json: json, toType: T.self) {
completionWrapper(object, nil)
return
}
}
}



T is the type of object we want to parse using Generics and Decodable protocol. In our example above, it'd be the User
objects. And once we call this processNetworkResponse function in our getUser function, we're basically done. We can call
processNetworkResponse in all our network calls and it works beautifully.

I hope this helps some other programmer struggling with such code out there.

Cheers,
Kaan



NEXT


Add Comment:





COMMENTS