在 SwiftUI 视图中发布后台上下文核心数据更改,而不阻塞 UI

问题描述 投票:0回答:3

运行后台上下文核心数据任务后,当更新在 SwiftUI 视图中发布时,Xcode 会显示以下紫色运行时警告:

"[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates."

除了下面的

ContentView.swift
代码之外,我还在默认的
container.viewContext.automaticallyMergesChangesFromParent = true
代码中将
init
添加到了
Persistence.swift

如何在主线程上发布后台更改来修复警告? (iOS 14、Swift 5)

编辑:为了响应第一个答案,我更改了下面的代码,以澄清我正在寻找一种在保存大量更改时不会阻塞 UI 的解决方案。

struct PersistenceHelper {
    private let context: NSManagedObjectContext
    
    init(context: NSManagedObjectContext = PersistenceController.shared.container.viewContext) {
        self.context = context
    }
    
    public func fetchItem() -> [Item] {
        do {
            let request: NSFetchRequest<Item> = Item.fetchRequest()
            var items = try self.context.fetch(request)
            if items.isEmpty { // Create items if none exist
                for _ in 0 ..< 250_000 {
                    let item = Item(context: context)
                    item.timestamp = Date()
                    item.data = "a"
                }
                try! context.save()
                items = try self.context.fetch(request)
            }
            return items
        } catch { assert(false) }
    }

    public func updateItemTimestamp(completionHandler: @escaping () -> ()) {
        PersistenceController.shared.container.performBackgroundTask({ backgroundContext in
            let start = Date(), request: NSFetchRequest<Item> = Item.fetchRequest()
            do {
                let items = try backgroundContext.fetch(request)
                for item in items {
                    item.timestamp = Date()
                    item.data = item.data == "a" ? "b" : "a"
                }
                try backgroundContext.save() // Purple warning appears here

                let interval = Double(Date().timeIntervalSince(start) * 1000) // Artificial two-second delay so cover view has time to appear
                if interval < 2000 { sleep(UInt32((2000 - interval) / 1000)) }
                
                completionHandler()
            } catch { assert(false) }
        })
    }
}
// A cover view with an animation that shouldn't be blocked when saving the background context changes
struct CoverView: View {
    @State private var toggle = true
    var body: some View {
        Circle()
            .offset(x: toggle ? -15 : 15, y: 0)
            .frame(width: 10, height: 10)
            .animation(Animation.easeInOut(duration: 0.25).repeatForever(autoreverses: true))
            .onAppear { toggle.toggle() }
    }
}
struct ContentView: View {
    @State private var items: [Item] = []
    @State private var showingCoverView = false
    @State private var refresh = UUID()

    let persistence = PersistenceHelper()
    let formatter = DateFormatter()
    var didSave = NotificationCenter.default
        .publisher(for: .NSManagedObjectContextDidSave)
        // .receive(on: DispatchQuene.main) // Doesn't help
    
    var body: some View {
        ScrollView {
            LazyVStack {
                Button("Update Timestamp") {
                    showingCoverView = true
                    persistence.updateItemTimestamp(completionHandler: { showingCoverView = false })
                }
                ForEach(items, id: \.self) { item in
                    Text(formatter.string(from: item.timestamp!) + " " + (item.data ?? ""))
                }
            }
        }
        .id(refresh)
        .onAppear {
            formatter.dateFormat = "HH:mm:ss"
            items = persistence.fetchItem()
        }
        .onReceive(didSave) { _ in
            items = persistence.fetchItem()
        }
        .fullScreenCover(isPresented: $showingCoverView) {
            CoverView().onDisappear { refresh = UUID() }
        }
    }
}
core-data swiftui publish combine background-thread
3个回答
0
投票

由于您正在执行后台任务,因此您位于后台线程 - 而不是主线程。

要切换到主线程,请将生成运行时警告的行更改为以下内容:

DispatchQueue.main.async {
    try backgroundContext.save()
}

0
投票

您应该使用组合并观察背景上下文的变化,并更新状态值以便您的 UI 做出反应。

@State private var coreDataAttribute = ""

 var body: some View {
Text(coreDataAttribute)
.onReceive(

            CoreDataManager.shared.moc.publisher(for: \.hasChanges)
            .subscribe(on: DispatchQueue.global())
            .receive(on: DispatchQueue.global())
                .map{_ in CoreDataManager.shared.fetchCoreDataValue()}
                .filter{$0 != coreDataAttribute}

            .receive(on: DispatchQueue.main))
        { value in
            coreDataAttribute = value
        }
}

0
投票

问题在于:

⠇
    var didSave = NotificationCenter.default
        .publisher(for: .NSManagedObjectContextDidSave)
⠇
    .onReceive(didSave)

…接收应用程序中

所有 CoreData 上下文
NSManagedObjectContextDidSave。保存后台上下文时,SwiftUI 会在后台线程上收到通知并发出抱怨。然后,当 CoreData 更改合并到 UI 上下文中时,SwiftUI 再次收到通知,一切都很好。

要解决此问题,只需观察 UI 代码使用的上下文的通知:

    .onReceive(NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave, object: context)) {
© www.soinside.com 2019 - 2024. All rights reserved.