Swift typealias to the rescue
Writing async code in Swift is (mostly) a joyful experience. Things can get hairy though when writing something like an API Client with functions that accept multiple closure arguments, which themselves accept multiple arguments.
Example:
class ApiClient { // .... func getUsers(success: ((result: AnyObject, operation: AFHTTPRequestOperation) -> Void)? = nil,
error: ((error: NSError, operation: AFHTTPRequestOperation) -> Bool)? = nil,
finished: (() -> Void)? = nil) {
// Do stuff
}func getUser(user: User, success: ((result: AnyObject, operation: AFHTTPRequestOperation) -> Void)? = nil,
error: ((error: NSError, operation: AFHTTPRequestOperation) -> Bool)? = nil,
finished: (() -> Void)? = nil) {
// Do stuff
}func getInvitations(success: ((result: AnyObject, operation: AFHTTPRequestOperation) -> Void)? = nil,
error: ((error: NSError, operation: AFHTTPRequestOperation) -> Bool)? = nil,
finished: (() -> Void)? = nil) {
// Do stuff
} // ....}
Not only is this painful to read, there’s also a lot of needless repetition leading to a high probability of copy-paste-itis. There is a better way!
Introducing typealias
Luckily, Swift’s got our backs here with a handy feature called typealias.
From the Swift Docs:
A type alias declaration introduces a named alias of an existing type into your program. Type alias declarations are declared using the keyword typealias and have the following form:
typealias name = existing type
After a type alias is declared, the aliased name can be used instead of the existing type everywhere in your program. The existing type can be a named type or a compound type. Type aliases do not create new types; they simply allow a name to refer to an existing type.
Functions and closures in Swift have a type (consisting of the function’s parameter types and return type) and so can be aliased like anything else:
typealias MyFunctionDefinition = (Integer, String) -> Void
Applying that to our example API client, we end up with this:
class ApiClient { // .... typealias SuccessHandler = (result: AnyObject, operation: AFHTTPRequestOperation)
-> Void
typealias ErrorHandler = (error: NSError, operation: AFHTTPRequestOperation)
-> Void
typealias FinishedHandler = () -> Void func getUsers(success: (SuccessHandler)? = nil,
error: (ErrorHandler)? = nil,
finished: (FinishedHandler)? = nil) {
// Do stuff
} func getUsers(success: (SuccessHandler)? = nil,
error: (ErrorHandler)? = nil,
finished: (FinishedHandler)? = nil) {
// Do stuff
} func getInvitations(user: User,
success: (SuccessHandler)? = nil,
error: (ErrorHandler)? = nil,
finished: (FinishedHandler)? = nil) {
// Do stuff
} // ....}
To me those function definitions are now a lot easier to read, and undeniably easier to write. Best of all, this is a drop-in solution. Nothing else about your functions need change and everything can be called as before.
ApiClient.sharedInstance.getUsers(success: { result, operation in
// Do stuff
}, error { error, operation in
// Do stuff
}, finished {
// Do stuff
})
Disclaimer: This pattern works well where you have a few closure types used by a majority of a class’s functions, but I would be cautious when you have many closure types used by only a few functions. Doing so may introduce indirection into your code without much benefit.
All patterns have trade-offs, and in this case we’re trading increased indirection for decreased duplication. There are no magic bullets!
Thoughts? Feedback? Disagreements? Twitter me: @mattvague