Deinitializers (`deinit`) in Swift

beginner
16 min

Deinitializers (deinit) in Swift

Welcome back! In this tutorial, we'll dive into the fascinating world of Deinitializers in Swift. šŸŽÆ This powerful feature helps us manage the lifecycle of our classes and structs effectively. Let's get started!

Understanding Deinitializers

Deinitializers are special functions in Swift that are automatically called when an instance of a class or struct is about to be deallocated (released from memory). They allow us to perform any necessary cleanup before the instance is fully deleted.

šŸ“ Note: Unlike initializers, we don't explicitly call deinitializers. They are called automatically by the Swift runtime when the instance is about to be deallocated.

swift
class Person { var name: String init(name: String) { self.name = name print("Person initialized with name: \(name)") } deinit { print("Person deinitialized with name: \(name)") } } var john = Person(name: "John")

In the example above, we create a Person class with an initializer and a deinitializer. When we create an instance of Person and assign it to a variable john, the initializer is called, and when john goes out of scope, the deinitializer is called.

Deinitializers and Reference Cycles

Deinitializers are particularly useful when dealing with reference cycles. Reference cycles occur when two or more objects refer to each other and neither can be deallocated because neither has a strong reference outside of the other.

Here's an example of a reference cycle:

swift
class Image { var imageData: Data var imageView: UIImageView init(imageData: Data, imageView: UIImageView) { self.imageData = imageData self.imageView = imageView self.imageView.image = UIImage(data: imageData) } deinit { self.imageView.image = nil print("Image deinitialized") } } class ViewController: UIViewController { var image: Image? override func viewDidLoad() { super.viewDidLoad() let imageData = ... // some image data let imageView = UIImageView(frame: self.view.frame) image = Image(imageData: imageData, imageView: imageView) self.view.addSubview(imageView) } deinit { print("ViewController deinitialized") } }

In the above example, the Image class holds a strong reference to the UIImageView, and the ViewController class holds a weak reference to the Image. If we don't release the reference to the Image object, the UIImageView will prevent the Image object from being deallocated, and we'll have a reference cycle.

To break the reference cycle, we can implement a deinitializer in both Image and ViewController classes. In the deinitializer, we set the image property of the Image class to nil and remove the ImageView from the ViewController's view.

swift
class Image { // ... (previous code) deinit { self.imageView.image = nil self.imageView.removeFromSuperview() print("Image deinitialized") } } class ViewController: UIViewController { // ... (previous code) deinit { self.image = nil print("ViewController deinitialized") } }

Now, when ViewController goes out of scope, it sets the image property to nil, breaking the reference cycle, and allowing the Image object to be deallocated.

Deinitializers and Class Clusters

Deinitializers can also help manage class clusters effectively. Class clusters are a design pattern where a class holds multiple instances of a class that conforms to a protocol.

Let's create a class cluster that holds multiple Circle objects:

swift
protocol Shape { var x: Double { get } var y: Double { get } var radius: Double { get } } class Circle: Shape { var x: Double var y: Double var radius: Double init(x: Double, y: Double, radius: Double) { self.x = x self.y = y self.radius = radius } deinit { print("Circle deinitialized with x: \(x), y: \(y), radius: \(radius)") } } class ShapeContainer { var shapes: [Shape] = [] func addShape(shape: Shape) { shapes.append(shape) } func removeShape(shape: Shape) { if let index = shapes.firstIndex(where: { $0 === shape }) { shapes.remove(at: index) } } deinit { for shape in shapes { shape.radius = 0 print("Shape deinitialized: \(shape)") } shapes.removeAll() print("ShapeContainer deinitialized") } }

In this example, we create a Shape protocol and a Circle class that conforms to the protocol. We also create a ShapeContainer class that holds multiple Circle objects.

When the ShapeContainer deinitializes, it iterates through the shapes array, sets the radius of each Circle object to 0, and then removes them from the array. This helps manage the lifecycle of the Circle objects effectively.

Quiz

Quick Quiz
Question 1 of 1

Which of the following functions in Swift is called when an instance of a class or struct is about to be deallocated?

That's it for today! In the next tutorial, we'll dive deeper into managing the lifecycle of classes and structs in Swift, covering willDeinit and didDeinit.

Stay curious and keep coding! šŸš€šŸŒŸ