Closures are blocks of code that can be executed later, and functions are a special case of closures. Functions and closures can be passed around in your code, returned by other functions or closures. You can store a closure or a function in a variable, and execute them later:
let runMe = { () -> Int in print(“run”) return 0 } runMe()
The preceding code is equivalent to the following:
func runMe() -> Int { print(“run”) return 0 } runMe()
Closures and functions are almost always interchangeable, except when it comes to class
or struct
members:
class MyClass{ var running = false lazyvar runWithClosure: () -> Void = { self.running = true } func runWithFunction() { self.running = true } }
While both implementations are somewhat equivalent, we rarely want this function to be overridable at runtime. The closure can't reference self
inside of it, unless marked lazy
. Marking it lazy
forces the implementation to be var
, which, in turn, doesn't reflect what we want to express. In practice, we never declare instance methods as closures.
Functions and closures don't have to be defined at the top level. This can be unintuitive, when coming from languages such as Objective-C and Java. Swift, like JavaScript, lets you define functions and closures anywhere in your code. Functions can also return functions. This mechanism is known as currying.
Imagine that you want to create a logger
method that will print a single argument, but it will always pretend to be a string to find it easily in your logs.
Let's start with the following basic implementation:
private let PREFIX = ‘MyPrefix' private func log(_ value: String) { print(PREFIX + “ “ + value) } class MyClass { func doSomething() { log(“before”) /* complex code */ log(“after”) } }
While this works properly in the scope of a simple class, if you need to reuse the log
method or change the internal implementation, this will lead to a lot of duplication.
You can use currying to overcome that issue, as follows:
func logger(prefix: String) -> (String) -> Void { func log(value: String) { print(prefix + “ “ + value) } return log } let log = logger(prefix: “MyClass”) log(“before”) // do something log(“after”) // console: MyClass before MyClass after
Functions and closures can capture the current scope, which means all of the declared variables outside of the function or closure definition, such as local variables or self
. In the case of self
, you can inadvertently extended the lifetime of your objects and leak memory:
class MyClass { var running = false func run() { running = true DispatchQueue.main.asyncAfter(deadline: .now() + 10) { self.running = false } } } var instance: MyClass? = MyClass() instance?.run() instance = nil
Can you spot the potential issue in this code?
Depending on the use case, you may want instance
to be destroyed when it is not referenced by any owner. In our case, we'll probably cause a memory leak, as the dispatch block is referencing self
without any memory management qualifier.
Swift provides us with two keywords that indicate how we want to extend the lifetime of an object in a closure. While both prevent creating retain cycles, they are fundamentally different.
Using weak
will wrap the captured value inside of an optional, indicating that the instance may have been deallocated before the closure was executed:
class MyClass { var running = false func run() { running = true DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in self?.running = false } } } var instance: MyClass? = MyClass() instance?.run() instance = nil
In this execution, instance
will immediately be deallocated when set to nil
.
Using unowned
indicates that the variable won't be owned by the block. Another mechanism should be responsible for ensuring that the lifetime of the captured object is properly extended until the block is executed:
class MyClass { var running = false func run() { running = true DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [unowned self] in self.running = false } } } var instance: MyClass? = MyClass() instance?.run() instance = nil
In this case, your program will crash when the block is executing, because the self
variable will be deallocated upon the execution of the block:
Fatal error: Attempted to read an unowned reference but object 0x7f80bc75a4e0 was already deallocated