04. Why should we avoid using closures in structs?

It will take about 3 minutes to finish reading this article.

Look at the following example, it will have a surprise result.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Car {
var speed: Float = 0.0
var increaseSpeed: (() -> ())?
}
var myCar = Car()
myCar.increaseSpeed = {
myCar.speed += 30 // The retain cycle occurs here. We cannot use [weak myCar] as myCar is a value type.
}
myCar.increaseSpeed?()
print("My car's speed :")
print(myCar.speed) // Prints 30

var myNewCar = myCar
myNewCar.increaseSpeed?()
myNewCar.increaseSpeed?()
print("My new car's speed :")
print(myNewCar.speed) // Prints 30 still!
print(myCar.speed) // Prints 90 !!!

The result is:

1
2
3
4
5
6
My car's speed :
30.0
My new car's speed :
30.0
My car's speed :
90.0

But why?
Well, the reason is, “myNewCar” is a partial copy of “newCar.” Since closures and their environments cannot be copied completely. The value of “speed” is copied, but the property “increaseSpeed” of “myNewCar” (myNewCar.increaseSpeed?()) holds a reference to the “increaseSpeed” of “myCar” with the “speed” of “myCar” in the captured environment. So, the “increaseSpeed” of “myCar” is invoked.
So what do we do now?
The straight forward solution is, avoid using closures in value types or we should change the struct to a class. If you have to use them, you should be very careful with it, or else it might lead to unexpected results. Regarding the retain cycle, the only way to break them is to set the variables “myCar” and “myNewCar” to nil manually. It doesn’t sound ideal, but there is no other way.

Reference

[1] Why should we avoid using closures in Swift structs?
[2] https://cloud.tencent.com/developer/article/1602230