You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI模态Sheet列表二次打开失效及生命周期异常问题求助

Hey there, let's break down what's happening with your code step by step — this is a common gotcha in early SwiftUI betas, especially with async operations and BindableObject lifecycle.

Why does DispatchQueue.main.async cause the issue?

When your AreaListView is first presented, the MockModel initializes and calls performFetch(), which schedules an async block on the main queue. This block runs, updates items, and the willChange subject notifies SwiftUI to refresh the view — that works fine the first time.

The problem hits when you dismiss the sheet:

  • Even though the AreaListView is destroyed, the MockModel instance isn't immediately deallocated (we'll cover why next).
  • When you present the sheet a second time, a new MockModel is created, and its performFetch() schedules another async block.
  • Before this new block can run, the old MockModel's deinit finally triggers, which interferes with the new model's state updates. Plus, due to the AttributeGraph cycle issue, SwiftUI fails to pick up the willChange event that should refresh the view with the new items.

Why is deinit triggered only when opening the sheet the second time?

This directly ties to the === AttributeGraph: cycle detected through attribute 18 === error you're seeing. In early SwiftUI Beta versions (like Beta 4), @ObjectBinding (the predecessor to @ObservedObject) had known cycle reference bugs between views and BindableObjects:

  • The view holds a reference to the MockModel, and the BindableObject's willChange subject creates a subscription back to the view's AttributeGraph. This creates a cycle that prevents the model from being deallocated when the view is dismissed.
  • When you present the sheet again, creating a new MockModel breaks the old cycle, allowing the original model to finally be deallocated (hence the deinit print at that point).

What's the fix?

Let's address both the async issue and lifecycle problems:

  1. Properly manage Combine subscriptions (and fix async loading)
    Your Future approach is better, but you need to retain and cancel subscriptions to avoid cycles. Add a Cancellable to hold the subscription, and cancel it in deinit:
class MockModel: BindableObject {
    struct MockItem: Identifiable {
        var id: String
    }
    var willChange = PassthroughSubject<Void, Never>()
    var items: [MockItem] = [] {
        willSet {
            self.willChange.send()
        }
    }
    private var cancellable: Cancellable? // Hold the subscription here

    init() {} // Don't call fetch in init yet

    func performFetch() {
        cancellable = getItem()
            .receive(on: RunLoop.main)
            .sink(receiveValue: { [weak self] items in
                self?.items = items
            })
    }

    func getItem() -> Future<[MockItem], Never> {
        return Future { promise in
            // Simulate background work here if needed
            DispatchQueue.global().async {
                let t = [MockItem(id: "first"), MockItem(id: "fjirst"), MockItem(id: "fiklkrst"), MockItem(id: "fijlkrst")]
                promise(.success(t))
            }
        }
    }

    deinit {
        cancellable?.cancel() // Cancel subscription to break cycles
        print("deinit")
    }
}
  1. Switch to @ObservedObject (if available in Beta 4)
    Later SwiftUI betas renamed @ObjectBinding to @ObservedObject, which fixed many lifecycle and cycle issues. Even in Beta 4, switching might resolve the AttributeGraph error:
struct AreaListView: View {
    @ObservedObject var model = MockModel() // Replace @ObjectBinding with @ObservedObject
    var body: some View {
        NavigationView {
            List {
                ForEach(model.items) { item in
                    Text(item.id)
                }
            }
        }
        .onAppear {
            self.model.performFetch() // Trigger fetch when view is fully loaded
        }
    }
}
  1. Trigger fetch when the view appears
    Calling performFetch() in init can lead to timing issues. Instead, use .onAppear to start the fetch only after the view is connected to SwiftUI's view hierarchy, ensuring updates are properly detected.

Key Takeaways

  • Early SwiftUI betas had rough edges with BindableObject lifecycle and async operations. Always cancel subscriptions to avoid memory cycles.
  • DispatchQueue.main.async in the model's init creates timing conflicts with view updates, especially when combined with delayed deallocation from cycles.
  • Using Combine's Future with proper subscription management is a more reliable way to handle async data loading in SwiftUI.

内容的提问来源于stack exchange,提问作者Congruent Tech. UG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 06:33:38