Closures - RaduG/swift_learning GitHub Wiki
{ (params) -> returnType in
statements
}
Example:
reduce(
{ (a: Int, b: Int) -> Int in
a + b
}, vals: [10, 20, 30])
Types can also be inferred from context:
reduce({a, b in
a + b
}, vals: [10, 20, 30])
An even shorter form is possible for closure expressions:
reduce({ $0 + $1 }, vals: [10, 20, 30])
Trailing closures help make the code look better by providing closures outside the argument list when calling a function. The closure must be the last argument.
func map<T, U>(vals: [T], f: (T) -> U) -> [U] {
var result = [U]()
if vals.count == 0 {
return result
}
for v in vals {
result.append(f(v))
}
return result
}
// Normal form
map(vals: ["abc", "defg", "hijlk"], f: { $0.count })
// Trailing closure
map(vals: ["abc", "defg", "hijlk"]) {
$0.count
}
It's also possible to have multiple trailing closures, however all but the first one need to be called by label. This works in swift 5.3 and up only.
func httpGetRequest(to endpoint: String, onSuccess: (String, Int) -> Void, onError: (String, Int) -> Void) {
if endpoint.count > 5 {
onSuccess("great", 200)
} else {
onError("oops", 400)
}
}
httpGetRequest(to: "/items") { response, statusCode in
print("Successful request! \(response) \(statusCode)")
} onError: { response, statusCode in
print("Failed request \(response) \(statusCode)")
}
httpGetRequest(to: "/its") { response, statusCode in
print("Successful request! \(response) \(statusCode)")
} onError: { response, statusCode in
print("Failed request \(response) \(statusCode)")
}
By default, a closure cannot live longer than the function it was passed to. For example, this snippet:
var messagePrinters = [() -> Void]()
func registerMessagePrinter(_ mp: () -> Void) {
messagePrinters.append(mp)
}
causes a compile-time error:
closures.swift:70:26: error: converting non-escaping parameter 'mp' to generic parameter 'Element' may allow it to escape
messagePrinters.append(mp)
To fix this, the closure needs to be marked as escaping:
var messagePrinters = [() -> Void]()
func registerMessagePrinter(_ mp: @escaping () -> Void) {
messagePrinters.append(mp)
}
However, @escaping
should be used with care as it can lead to strong reference cycles (Swift uses reference counting). To bind self
to an escaping closure, it has to be referred to explicitly either in the capture list or in the body of the closure. For example:
class SomeClass {
var attr = 10
func makeUsefulThings() {
registerMessagePrinter({ print("\(self.attr)")})
// or
registerMessagePrinter({ [self] in print("\(attr)")})
}
}
An autoclosure is used to automatically wrap an expression into a function.
func printIfFails(
_ condition: @autoclosure () -> Bool,
message: @autoclosure () -> String
) {
if condition() == false {
print(message())
}
}
printIfFails(200 > 100, message: "Some numbers for you: \([100, 200, 300])")
In this example, 200 > 100
and "Some numbers for you: \([100, 200, 300])"
are not actually evaluated until they are called explicitly. This is how the assert("condition:message:file:line:")
is implemented.