Closures - RaduG/swift_learning GitHub Wiki

Syntax:

{ (params) -> returnType in
    statements
}

Example:

reduce(
  { (a: Int, b: Int) -> Int in
        a + b
}, vals: [10, 20, 30])

Type inference

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

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)")
}

Escaping closures

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)")})
  }
}

Autoclosures

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.

⚠️ **GitHub.com Fallback** ⚠️