Strong, weak, and unowned self in Swift

Mastering Self in Swift: Strong, Weak, and Unowned

Strong, weak, and unowned self in Swift

In Swift, understanding how to reference self is crucial for writing clean, efficient, and memory-safe code. Swift provides three primary reference types when capturing self in closures: strong, weak, and unowned. In this blog, we will explore these reference types, discuss when and how to use them, when not to use them, and provide practical examples to help you master the art of handling self in Swift.

Strong Self

Strong references are the default reference type when capturing self in closures. When you capture self strongly, it increases the reference count, ensuring the referred instance remains in memory until there’s at least one strong reference to it.

When to Use Strong Self:

  • Use strong self when you want to ensure that the referred instance is retained in memory for as long as the closure is active.
  • Typically used when the closure needs to retain the object, such as in animation or long-running tasks.
class DataManager {
    var data: [String] = []

    func fetchData(completion: @escaping () -> Void) {
        fetchDataFromServer { [self] newItems in
            self.data.append(contents of newItems)
            completion()
        }
    }
}

In this example, capturing self strongly ensures that DataManager remains in memory while the closure processes the fetched data.

There are two ways of capturing strong self.

  1. Implicit Strong Capture: In this case, you capture self without any additional specifiers. This implicitly creates a strong reference to self.
class SomeClass {
    func performTask() {
        DispatchQueue.global().async {
            // Implicit strong capture of self
            self.doSomething()
        }
    }
    
    func doSomething() {
        // Your code here
    }
}

2. Explicit Strong Capture: You can also explicitly capture self strongly by specifying [self] in the capture list.

class SomeClass {
    func performTask() {
        DispatchQueue.global().async { [self] in
            // Explicit strong capture of self
            self.doSomething()
        }
    }
    
    func doSomething() {
        // Your code here
    }
}

Both methods will create a strong reference to self, and it’s important to be mindful of strong reference cycles when using strong captures.

When Not to Use Strong Self:

  • Avoid strong self when it leads to strong reference cycles, which can cause memory leaks. For example, when a closure captures self and the closure itself is stored within self.

Weak Self

Weak references are used to capture self without increasing the reference count. This prevents strong reference cycles and potential memory leaks, as the instance can be deallocated even if the closure still references it.

When to Use Weak Self:

  • Use weak self when there’s a potential risk of creating a strong reference cycle between the closure and the referred instance.
  • Commonly used in closures where self might be deallocated before the closure finishes.
class NetworkManager {
    func fetchData(completion: @escaping (Result) -> Void) {
        performNetworkRequest { [weak self] result in
            guard let self = self else {
                // Handle the case where self has been deallocated
                return
            }
            self.processResult(result)
            completion(result)
        }
    }
}

Here, capturing self weakly prevents a strong reference cycle, allowing NetworkManager to be deallocated if needed.

When Not to Use Weak Self:

  • Avoid weak self when self must remain in memory during the closure’s execution to ensure its correctness. Using weak self in such cases could lead to crashes due to accessing deallocated memory.

Unowned Self

Unowned references capture self without increasing the reference count but assume that self will always be valid throughout the closure’s execution. Accessing an unowned reference after self has been deallocated leads to a runtime crash.

When to Use Unowned Self:

  • Use unowned self when you can guarantee that self will outlive the closure and won’t be deallocated.
  • Ideal for cases where the closure is a callback to a parent object.
class ViewController {
    var data: [String] = []

    func fetchData() {
        DataManager.shared.fetchData { [unowned self] newData in
            self.data.append(contents of newData)
            self.updateUI()
        }
    }
}

In this example, unowned self is used because it’s guaranteed that the ViewController will exist throughout the data fetching process.

When Not to Use Unowned Self:

  • Avoid unowned self when there’s a chance that self might be deallocated before the closure finishes, as this would result in a crash.

Best Practices

Here are some best practices for using strong, weak, and unowned references in Swift:

  • Prefer using weak or unowned references when capturing self in closures to prevent strong reference cycles.
  • Only use unowned when you are certain that self will outlive the closure. If there’s any doubt, choose weak.
  • Always use strong references when you need to keep an instance in memory for the duration of its usage.

Mastering the art of handling self in Swift is essential for writing memory-efficient and robust code. By following best practices and choosing the appropriate reference type, you can ensure your code performs smoothly, free from memory-related issues, and optimized for efficiency.

Master strong, weak, and unowned self references, and you’ll be well on your way to becoming a Swift coding expert.