SwiftUI View leaks in iOS 17 – Swiftui

by
Alexei Petrov
androidcardview ios17 memory-leaks swiftui-ontapgesture

Quick Fix: Implement the optional variable along with the property that is experiencing leaks, and reset the variable value to nil in the onDisappear function.

The Solutions:

Solution 1: Using Optional Properties and Resetting Them on Disappearance

In iOS 17, SwiftUI views can sometimes leak memory due to a bug. One way to work around this issue is to use optional properties and reset them when the view disappears. This ensures that the objects referenced by the optional properties are deallocated when the view is dismissed.

To implement this solution, follow these steps:

  1. Declare the property as an optional.
  2. In the view’s onDisappear method, set the optional property to nil.
  3. Optionally, you can wrap the optional property in a property wrapper to simplify the code.

Here’s an example:

// Property wrapper to simplify the code
@propertyWrapper struct Resettable<Value> {
    private var value: Value?

    var wrappedValue: Value? {
        get { value }
        set { value = newValue }
    }

    init(wrappedValue: Value?) {
        self.value = wrappedValue
    }

    func reset() {
        value = nil
    }
}

struct MyView: View {
    // Declare the property as an optional
    @Resettable var reference: Service?

    var body: some View {
        // Use the property as usual
        Text("Hello, world!")
    }

    func onDisappear() {
        // Reset the optional property when the view disappears
        reference = nil
    }
}

By following this approach, you can work around the memory leak issue caused by SwiftUI view leaks in iOS 17.

Solution 2: Stop creating objects directly in the view

Most already know that `@ObservedObject` is a leak because Apple said so in a WWDC video, e.g. this has a bad leak:

struct BadLeakView: View {
    @ObserverdObject var log = LogDeinit() // bad leak
}

However, your example is a worse leak because not only did you init an object in a View struct (SwiftUI keeps many copies of `View` structs so it can diff them) but you didn’t even wrap it in any property wrapper, this is a very bad leak:

struct VeryBadLeakView: View {
    let log = LogDeinit() // very bad leak!
}

You can fix this with `@StateObject`, however, you should question if you even need an object, usually, it’s only when you need to do something asynchronous, like a Combine pipeline. Since we now have async/await usually you don’t even need a `@StateObject` anymore since we have `.task` which is like a state object but as a simpler view modifier.

Q&A

What is the workaround for fixing the leak in SwiftUI views?

Use an optional property and reset it in onDisappear.

How can the leak in @ObservedObject be fixed?

Use @StateObject instead.

When should @StateObject be used?

Only when asynchronous operations are needed, such as a Combine pipeline.

Video Explanation:

The following video, titled "4. Create a To-Do App Part 1 - YouTube", provides additional insights and in-depth exploration related to the topics discussed in this post.

Play video

Code all the views and components for a fully functional To-Do app Watch the complete Build Quick Apps with SwiftUI course ...