David Yang
Tips and posts for iOS developers from an iOS developer.
Dealing with trailing closure syntax in Swift
You are performing a task and expect two kind of results: success and failure (eg: performing a request to a server).
A simple way to deal with it would be to have two closures, one for success and the other for failure.
Here is how your method signature may look like.
struct SomeStruct {
static func doSomething(success:(String) -> Void, failure: (Error) -> Void) {
// do something
// eg: success("my success value")
}
}
For me, this was the most obvious way to do it when I was coding with Objective-C.
The problem with it is that with Swift, Xcode has this thing called “trailing closure syntax”. This simply means that for a method having a closure as its last parameter, Xcode and its autocomplete feature will write something like this:
SomeStruct.doSomething(success: { value in
// do something for success closure
}) { error in
// do something for failure closure
}
Sure, you can fix this by yourself to have something like this:
SomeStruct.doSomething(success: { value in
// do seomthing for success closure
}, failure: { error in
// do something for failure closure
})
But it will only annoy you more and more each time.
The only way to get something clean is to have only one closure that will handle both success and failure cases.
Here are two ways to do it.
Using two parameters in your trailing closure
Have a method signature like this:
struct SomeStruct {
static func doSomething(completion: (String?, Error?) -> Void) {
// do something
// eg: completion("my success value", nil)
}
}
Calling the method will then look like the following:
SomeStruct.doSomething() { (value, error) in
if error != nil {
// do something for failure
} else {
// do something for success
}
}
This is actually how Apple handle things in its URLSession methods.
Using an enum as the single parameter in the trailing closure
Have an enum that will handle both case!
enum Response {
case success(String)
case failure(Error)
}
Your method signature will look like this:
struct SomeStruct {
static func doSomething(completion: (Response) -> Void) {
// do something
// eg: completion(.success("my success value"))
}
}
And here is how calling your method will look like:
SomeStruct.doSomething() { response in
switch response {
case .success(let value):
// do something for success
case .failure(let error):
// do something for error
}
}
Conclusion
Xcode automatically apply trailing closure syntax with autocomplete features. With that, the most elegant way is to have a single closure to handle both case.
I presented you two ways to deal with it. Apple clearly went with the first way and you should chose the one that fits your needs.
Although for readability, the second way makes the code more expressive in my opinion by making each completion cases clearly visible and understandable.