This article presumes you already know goshdarn closure syntax.
Use standard Swift syntax to declare parameters of closure types:
func example1(
value: Int,
closure1: (Int) -> String,
closure2: (String) -> Bool,
closure3: (Bool) -> Int
) -> Int {
let value1 = closure1(value)
let value2 = closure2(value1)
let value3 = closure3(value2)
return value3
}
Swift versions 5.2 and earlier would let you “pop” only the last closure out of the function call parenthesis while all closures before it remained inside.
// Swift 5.2 and before:
let value = example1(
value: 420,
closure1: { number in
String(describing: number)
},
closure2: { string in
string.count.isMultiple(of: 69)
}
) { boolean in
boolean ? 1 : 0
}
New in Swift 5.3 and later, you can “pop” all the trailing closures outside the closing parenthesis of the function call:
// Swift 5.3 and after:
let value = example1(value: 420) { number in
String(describing: number)
} closure2: { string in
string.count.isMultiple(of: 69)
} closure3: { boolean in
boolean ? 1 : 0
}
The rules for closure labelling are:
You may omit the argument label while declaring a function:
func example2(
_ value: Int,
_ closure1: (Int) -> String,
_ closure2: (String) -> Bool,
_ closure3: (Bool) -> Int
) -> Int {
let value1 = closure1(value)
let value2 = closure2(value1)
let value3 = closure3(value2)
return value3
}
In Swift 5.2 and earlier, you would use the function with no argument labels:
// Swift 5.2 and before:
let value = example2(
420,
{ number in
String(describing: number)
}, { string in
string.count.isMultiple(of: 69)
}
) { boolean in
boolean ? 1 : 0
}
While using the new syntax in Swift 5.3 and after, you must use _:
to label closures that were previously unlabelled.
// Swift 5.3 and after:
let value = example2(420) { number in
String(describing: number)
} _: { string in
string.count.isMultiple(of: 69)
} _: { boolean in
boolean ? 1 : 0
}
Declaration works exactly as above:
func example3(closure1: () -> Int, closure2: (Int) -> Bool) -> Bool {
let value1 = closure1()
let value2 = closure2(value1)
return value2
}
While using, you can omit the function parentheses and get straight to the closures:
let value = example3 { 420 } closure2: { number in
number.isMultiple(of: 69)
}
This syntax carries over into subscript declaration…
struct Example {
private let array: [Int]
init(array: [Int]) {
self.array = array
}
subscript(
index: Int,
closure1: (Int) -> Int,
closure2: (Int) -> Int
) -> Int {
let index1 = closure1(index)
let index2 = closure2(index1)
return array[index2]
}
subscript(closure1: () -> Int, closure2: (Int) -> Int) -> Int {
let index1 = closure1()
let index2 = closure2(index1)
return array[index2]
}
}
… and usage:
let example = Example(array: [0, 1, 2, 3, 4])
let value1 = example[1] { $0 * $0 } _: { $0 + $0 }
let value2 = example[] { 2 } _: { $0 * $0 }
Unlike function parenthesis, the brackets that denote subscript access cannot be omitted.
If an external argument label is not provided explicitly, subscript calls will use _:
instead of the parameter name. This applies to subscript calls in general, not just this specific case.
Provide an explicit external argument label…
extension Example {
subscript(
index index: Int,
transform1 closure1: (Int) -> Int,
transform2 closure2: (Int) -> Int
) -> Int {
let index1 = closure1(index)
let index2 = closure2(index1)
return array[index2]
}
}
… to use labelled arguments at your call site. As usual, the label of the first trailing closure is ommitted:
let value3 = example[index: 3] { $0 + 2 } transform2: { $0 - 2 }
It is possible to declare closures that take multiple trailing closure parameters:
let example: (Int, (Int) -> Int, (Int) -> Int, (Int) -> Int) -> Int = {
value, closure1, closure2, closure3 in
let value1 = closure1(value)
let value2 = closure2(value1)
let value3 = closure3(value2)
return value3
}
While using this closure, the first trailing closure argument label is omitted, while subsequent closures must be labelled with _:
as closures do not have labelled arguments:
let value = example(42) { $0 + 2 } _
: { $0 * $0 } _: { -$0 }
The behaviour for handling default arguments is unchanged. Consider the function:
func example4(closure: () -> Int = { 69 }, any: Any? = nil) {
print(closure())
print(any == nil)
}
Invoking it like this:
example4 { 420 }
Is the same as:
example4(any: { 420 })
Although the provided trailing closure matches the type of the closure:
parameter, existing trailing closure behaviour matches the closure to any:
.
Wish to access this site but with raging profanity in the URL? !@#$ingmultipletrailingclosuresyntax.com is a less work-friendly mirror.
With many thanks to Zoë Smith, Zev Eisenberg, and Em Lazer-Walker.
Special thanks to John Sundell for Splash and Guilherme Rambo for hosting it online. Splash has been instrumental in syntax highlighting all the code you see here.