Swift Capture Lists

Introduction

Welcome to Lesson 4 of Intro to Functional Swift. Today, you will discover a unique behavior of closures, and how to make it right.

Discovery Stage

First, let us create two variables.

var a = 0
var b = 0

Design Closure

Create a closure block which prints a and b.

let newClosure = { print(a, b) }
newClosure() // (0, 0)

Design Closure Array

Create an array whose type is [() -> ()]. newClosure will be added to closureArray.

var closureArray: [() -> ()] = []
var i = 0

Append Closure

Let us add multiple closure blocks to the array using a for-in loop.

for _ in 1...5 {
  closureArray.append {
    print(i)
  }
  i += 1
}
// i is 5 by now

Execute Closure

Let us call the function.

closureArray[0]() // 5 😲
closureArray[1]() // 5 🤔
closureArray[2]() // 5 😨
closureArray[3]() // 5 😭
closureArray[4]() // 5 😱

You might have expected each element to print, 0, 1, 2, 3, 4. However, each closure uses the final value of i which is 5 at the end of the loop.

Important: A closure block is a reference type. You will learn more in Chapter 5.

Characteristic of Closure

Let us dig deeper.

Design Closure

var c = 0
var d = 0

let smartClosure: () -> () = { _ in
  print(c, d)
}

First, modify the value of c and d. Second execute the closure.

c = 9
d = 9
smartClosure() // (9, 9)

smartClosure() prints (9, 9). When the value of c and d mutate, the change is reflected on the closure by default.

Introducing Capture List

Let us destroy the default behavior.

Strategy: Do not reference. Copy

Add an array [a, b] before in. The array is referred to as a capture list.

let smarterClosure: () -> () = { [c, d] in
  print(c, d)
}

smarterClosure() // (9, 9)

Now , attempt to modify the value of c and d

c = 10
d = 10

Execute smarterClosure. You will discover that the change is not reflected on the closure anymore.

smarterClosure() // (9, 9)

The capture list "copies" the value of c and d at the particular time the closure was created. It become invincible.

Destroy Unique Behavior

Let us go back to the early example with the for-in loop to append the closure block to the array.

Design Closure

var smartClosureArray: [() -> ()] = []
var j = 0

Append Closure

Capture j

for _ in 1...5 {
  smartClosureArray.append { [j] in
    print(j)
  }
  j += 1
}

You may rebrand j. In this case, I will refer to j as num within the closure block.

for _ in 1...5 {
  smartClosureArray.append { [num = j] in
    print(num)
  }
  j += 1
}

Execute

Let us find out whether you've killed the default behavior.

smartClosureArray[0]() // 0 ☝️
smartClosureArray[1]() // 1 💪
smartClosureArray[2]() // 2 🎁
smartClosureArray[3]() // 3 🎉
smartClosureArray[4]() // 4 🎅

Resources

Capture List in Closures

Source Code

3004_capture_lists.playground

Conclusion

You've learned the default behavior of a closure block which is identical to that of instances created with class objects. In fact, closures are reference types. In Chapter 5, however, you will discover some of problems that may arise due to the default behavior. Stay tuned.

In the following lesson, you will learn how to beautify closures.

results matching ""

    No results matching ""