Wednesday, December 23, 2015

Swift: Closure

The concept of closure in Swift is similar to lambdas in .Net C# or blocks in C and Objective-C, but it is more powerful in Swift.

A closure in Swift can capture constants and variables in its context or its outer function. Closure provides a way to plug in dynamic codes. In advanced level, a closure can be used as a type passing to a function's parameter or return result.

Depending on how the closure is used in parameters, the closure can be escaped or none-escaped. This is an advanced feature. My understand of escaped closure is the one kept somewhere for later call, while none-escaped closure will be called in function.

In the following example, I modified codes in Mac Developer Library to explore the advanced feature. It was hard to comprehensive at first. However, I figured out how it works with some modifications.

It is amazed to see that closure, as in function parameters, can be either used and through away, or stored for later use. In most cases of completion handler, closure is called at later completion time. The following simple example explains how it works.

  1. /* The closure in parameter will be called in func
  2. the closure will be through away, not stored for later use */
  3. func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
  4.    print("closure as @noescape parameter called and not saved")
  5.    closure()
  6. }
  7. // array to store closures
  8. var completionHandlers: [() -> Void] = []
  9. /* The closure in parameter will NOt be called in func
  10. instead, stored into array for later call.
  11. */
  12. func someFunctionWithEscapingClosure(closure: () -> Void) {
  13.    print ("add closure to complitionHandlers array")
  14.    completionHandlers.append(closure)
  15. }
  16. // Example class with property x modified by closure
  17. // referenced by above funcs
  18. class SomeClass {
  19.    var x = 10
  20.    func doSomething() {
  21.        someFunctionWithEscapingClosure {
  22.            let value = 100
  23.            print("set instance.x to \(value)")
  24.            self.x = value }
  25.        someFunctionWithNoescapeClosure {
  26.            let value = 200
  27.            print("set instance's x to \(value)")
  28.            x = value }
  29.    }
  30. }
  31. let instance = SomeClass()
  32. print("instance of SoSomething created, and method doSomething() called")
  33. instance.doSomething()
  34. print("instance.x: \(instance.x)")
  35. print("Call complitionHandler's first. Escaped closure being involked")
  36. completionHandlers.first?()
  37. print("instance.x: \(instance.x)")

The func at line 3 has a closure as none-escaped parameter, and the closure is called at line 5, not kept. While the func at line 9 has a closure as escaped closure, appended to outer array at line 15.

As a result, after an instance of SomeClass is created at line 34, and its method of doSomething() is called, the instance property value x is set 200 by func call of someFunctionWithNoescapeSclosure at line 27.

The instance's property x is set to 100 as the result of calling global array's first item, which triggers escaped closure called.

And the result is as follows:

instance of SoSomething created, and method doSomething() called
add closure to complitionHandlers array
closure as @noescape parameter called and not saved
set instance's x to 200
instance.x: 200
Call complitionHandler's first. Escaped closure being involked
set instance.x to 100
instance.x: 100