【问题标题】:@FetchRequest results not updating in SwiftUI when change source is share extension当更改源为共享扩展时,@FetchRequest 结果未在 SwiftUI 中更新
【发布时间】:2026-02-22 16:20:04
【问题描述】:

@FetchRequest 上有许多问题的答案提供了强制 SwiftUI 重绘视图的变通方法。我找不到任何解决这个问题的方法。

我的应用程序使用共享扩展将新项目插入我的 CoreData 模型 (sqlite)。在主应用程序和共享扩展目标之间共享一个框架。我的应用程序中的主要 SwiftUI ContentView 使用 @FetchRequest 属性来监视数据库的内容并更新视图:

struct ContentView: View {
    @FetchRequest(fetchRequest: MyObj.allFetchRequest()) var allObjs: FetchedResults<MyObj>
    ...
}

我的 xcdatamodel 扩展为:

    public static func allFetchRequest() -> NSFetchRequest<MyObj> {
        let request: NSFetchRequest<MyObj> = MyObj.fetchRequest()
        // update request to sort by name
        request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
        return request
    }

共享扩展使用以下方法添加新项目:

let newObj = MyObj(context: context)
newObj.name = "new name"
try! context.save()

使用共享扩展添加新项目后,当我通过在设备上切换应用返回我的应用时,SceneDelegate 检测到sceneDidBecomeActive。在这个函数中,我可以手动运行一个 fetch 请求并查看新项目是否已添加。我什至可以使用其他 SO 问题的答案来欺骗 UI 在 ContentView 中重建(通过在收到通知时切换构建器中使用的 Bool),但无论哪种情况,当 UI 重建时,allObjs 不包含来自数据库的更新结果。

在我的sceneDidBecomeActive 中,我尝试了多种代码组合,以尝试以@FetchRequest 可以看到的方式使用数据库的最新内容更新上下文。数据就在那里,我的托管上下文可以看到这个新数据,因为手动获取请求会返回新项目,但 @FetchRequest 始终是陈旧的,阻止了 UI 重建。在这两个位置,我都打印了上下文,并且确信它是相同的上下文。我试过了:

  • context.reset()
  • context.refreshAllObjects()
  • context.save()

我不知道为什么@FetchRequestContentView 重绘时没有更新其值。

如何强制@FetchRequest 在我的共享扩展将新对象添加到数据库后更新其对象并导致 SwiftUI 重建?

(Xcode 11.4.1,Swift 5,目标:iOS 13.1)

编辑:

在我的ContentView 中,我打印了allObjs 变量、它们的状态、上下文的内存地址等。现有对象是相同的,除了新的 fetch 显示新添加的对象,而 allObjs 不包含新对象。很明显@FetchRequest 对象没有得到更新。

编辑 2:

我尝试使用@Published 属性和显式update() 方法创建自定义ObservableObject 类,然后将其用作ContentView 中的@ObservedObject。这行得通。

class MyObjList: ObservableObject {
    static let shared = MyObjList()
    @Published var allObjs: [MyObj]!

    init() {
        update()
    }

    func update() {
        allObjs = try! DataStore.persistentContainer.viewContext.fetch(MyObj.allFetchRequest())
    }
}

然后在ContentView:

@EnvironmentObject var allObjs: MyObjList

这有助于确认数据存在,只是@FetchRequest 没有正确更新它。这是一种实用的解决方法。

【问题讨论】:

  • 如果您在应用内添加相同的对象(不是扩展),它会出现在@FetchRequest 中吗?
  • @Aspid 是的,在应用程序(不是扩展程序)中创建的相同对象出现没有问题。请记住,它是在 ContentView 显示的相同上下文中创建的
  • 我只是想知道这是否是 WAL 模式和 FRC 缓存问题的另一个例子。请参阅this answer 了解更多信息。
  • @pbasdf 我不知道的有趣的东西。我尝试了let storeDescription = NSPersistentStoreDescription; storeDescription.setValue("DELETE" as NSString, forPragmaNamed: "journal_mode") 并将描述添加到NSPersistentContainer(),但这并没有解决问题。
  • 对不起。那是一个红鲱鱼。

标签: ios swift core-data swiftui


【解决方案1】:

我使用持久历史跟踪和远程更改通知解决了类似的问题。这些使 Core Data 记录 SQLite 存储发生的每一次更改,并在这些更改之一远程发生时向您发送通知(例如,从扩展程序或 iCloud)。

默认情况下不启用这些功能。您必须在加载持久存储之前配置它们:

persistentStoreDescriptions.forEach {
    $0.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
    $0.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
}

然后订阅远程更改通知:

NotificationCenter.default.addObserver(
    /* The persistent container/view model/MyObjList/etc that acts as observer */, 
    selector: #selector(coordinatorReceivedRemoteChanges(_:)), 
    name: .NSPersistentStoreRemoteChange, 
    object: persistentStoreCoordinator // don't forget to pass the PSC as the object!
)

现在我们会在发生远程更改时收到通知,但我们需要在整个应用程序中与托管对象上下文共享这些更改。对于每个应用程序来说,这看起来都不同:有时您的 MOC 根本不需要知道这些更改,有时他们只需要了解其中的一些,有时是全部。 Apple 最近published a guide 深入探讨了如何确保只处理相关的更改。

在这种情况下,您可能会这样做:

@objc
func coordinatorReceivedRemoteChanges(_ notification: Notification) {
    let currentToken = notification.userInfo?[NSPersistentHistoryTokenKey] as? NSPersistentHistoryToken
    let existingToken = ... // retrieve the previously processed history token so we don't repeat work; you might have saved it to disk using NSKeyedArchiver
    
    // Fetch the history
    let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: existingToken)
    let result = try! context.execute(historyRequest) as! NSPersistentHistoryResult
    let transactions = result.result as! [NSPersistentHistoryTransaction]

    // Filter out the non-relevant changes
    for transaction in transaction {
        // Maybe we only care about changes to MyObjs 
        guard transaction.entityDescription.name == MyObj.entity().name else { continue }
        // Maybe we only care about specific kinds of changes
        guard let changes = transaction.changes, changes.contains(where: { ... }) else { continue }
        // Merge transaction into the view context
        viewContext.mergeChanges(fromContextDidSave: transaction.objectIDNotification())
    }

    // Finally, save `currentToken` so it can be used next time and optionally delete old history.
}

任何使用viewContext@FetchRequests 现在都将接收所有合并的更改并相应地刷新其数据。

【讨论】:

  • 与原始问题有完全相同的问题。这个解决方案对我有用。我所做的唯一更改是在我的主视图上使用.onReceive(NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange), perform: refresh)。虽然这真的取决于应用程序
  • 这很好用。我在这里找到了有关持久历史跟踪的更多详细信息以及对上面代码的一些改进:avanderlee.com/swift/persistent-history-tracking-core-data
【解决方案2】:

尝试移动,如果它适用于您的应用,则在应用上创建ContentView 转到前台(因此将从头重新创建获取请求)

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

    // leave here only window creation
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        self.window = window
    }
}

// ... other code here

func sceneWillEnterForeground(_ scene: UIScene) {

    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    let contentView = ContentView().environment(\.managedObjectContext, context)
    let controller = UIHostingController(rootView: contentView)

    window?.rootViewController = controller
    window?.makeKeyAndVisible()
}

【讨论】:

  • 我很欣赏这个建议。这是一种解决方法,而且是一个相当重量级的解决方法。
【解决方案3】:

我使用引用 answer 的评论中的方法。我可以从 URI 解析 ManagedID。但无法通过existingObject(with:) 检索对象。只有 nil 返回。 object(with:) 方法使应用程序崩溃。

020-05-13 18:34:54.324914+0800 SwiftUI-FetchRequest[7802:388610] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Object's persistent store is not reachable from this NSManagedObjectContext's coordinator'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff23e39f0e __exceptionPreprocess + 350
    1   libobjc.A.dylib                     0x00007fff50ad79b2 objc_exception_throw + 48
    2   CoreData                            0x00007fff23a0b0a9 -[_NSCoreManagedObjectID _isPersistentStoreAlive] + 0
    3   CoreData                            0x00007fff239f704a -[NSManagedObjectContext objectWithID:] + 555
    4   SwiftUI-FetchRequest                0x000000010e922933 $s20SwiftUI_FetchRequest16ContentViewModelCACycfcyypSgcfU_ + 1827
    5   SwiftUI-FetchRequest                0x000000010e922d21 $sypSgIegn_yXlSgIeyBy_TR + 161
    6   MMWormhole                          0x000000010ebbfe34 __61-[MMWormhole notifyListenerForMessageWithIdentifier:message:]_block_invoke + 36
    7   libdispatch.dylib                   0x000000010ebe1f11 _dispatch_call_block_and_release + 12
    8   libdispatch.dylib                   0x000000010ebe2e8e _dispatch_client_callout + 8
    9   libdispatch.dylib                   0x000000010ebf0d97 _dispatch_main_queue_callback_4CF + 1149
    10  CoreFoundation                      0x00007fff23d9da89 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    11  CoreFoundation                      0x00007fff23d985d9 __CFRunLoopRun + 2041
    12  CoreFoundation                      0x00007fff23d97ac4 CFRunLoopRunSpecific + 404
    13  GraphicsServices                    0x00007fff38b2fc1a GSEventRunModal + 139
    14  UIKitCore                           0x00007fff48bc7f80 UIApplicationMain + 1605
    15  SwiftUI-FetchRequest                0x000000010e920c3b main + 75
    16  libdyld.dylib                       0x00007fff519521fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

上下文已经被reset() 方法刷新。并且手动查询最新的条目可以正确解析。我认为它的 SwiftUI.framework 错误。并通过 Feedback.app 提出问题。

【讨论】:

  • 您链接的答案中描述的方法似乎没有更新 @FetchRequest 属性包装器。我的应用程序本身可以看到上下文中的新对象——它只是在属性包装器中获取的结果没有更新。
  • 是的。 @FetchRequest 有问题。而且基本上没有办法修复黑盒属性包装器。