Как я могу узнать, находится ли мой NSManagedObject в NSPersistentStore только для чтения? - PullRequest
1 голос
/ 27 февраля 2020

Я хотел бы добавить примеры / учебные данные только для чтения в мое MacOS-приложение на основе Core Data.

Я включу файл SQL в свой комплект приложений, содержащий данные примера. Мой NSPersistentContainer будет иметь 2 NSPersistentStores, один для записи и один только для чтения. У меня будет только конфигурация по умолчанию для моей модели, поскольку оба хранилища будут иметь одну и ту же модель.

Мой пользовательский интерфейс должен будет знать, являются ли отображаемые данные доступными только для чтения или нет, например, чтобы остановить перетаскивание этих данных .

Я знаю, что NSManagedObject не поддерживает состояние только для чтения, см. И: Можно ли вернуть NSManagedObjects только для чтения в Базовых данных? ... и документы.

Я думаю, что лучшим подходом было бы добавить свойство только для чтения к моему производному классу NSManagedObject, к которому можно обращаться при необходимости. Тем не менее, я не вижу, как я мог легко установить это свойство! Я не могу найти прямую ссылку на NSPersistentStore от NSManagedObject.

Я мог бы настроить NSFetchRequest и указать хранилище только для чтения и посмотреть, есть ли в нем NSManagedObject, но это кажется немного нелепым.

Не упустил ли я что-нибудь более очевидное здесь, пожалуйста?

1 Ответ

1 голос
/ 03 марта 2020

Благодаря pbasdf за его предложение ...

Я не смог найти прямого пути для достижения этой цели. Мне пришлось отказаться от использования NSPersistentContainer, чтобы упростить мой стек основных данных. Тем не менее, я думаю, что это довольно элегантное решение, если вам нужно небольшое подмножество вашего графика, чтобы он был доступен только для чтения.

Я вложил в класс NSPersistentStoreCoordinator для кэширования NSManagedObjectIDs любого хранилища, добавленного в него:

class GraphStoreCoordinator: NSPersistentStoreCoordinator
{
    override init(managedObjectModel model: NSManagedObjectModel)
    {
        readOnlyTestContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        super.init(managedObjectModel: model)
        readOnlyTestContext.persistentStoreCoordinator = self

        NotificationCenter.default
            .addObserver(forName: .NSPersistentStoreCoordinatorStoresDidChange,
                         object: self, queue: nil)  { [unowned self] notification in

            // userInfo will be in this form for add/remove keys - not supporting migration here
            guard let userInfo = notification.userInfo as? [String: [NSPersistentStore]] else {
                unhandledError("Invalid userInfo for NSPersistentStoreCoordinatorStoresDidChange.") }

            userInfo[NSAddedPersistentStoresKey]?.forEach { self.didAddStore($0) }
            userInfo[NSRemovedPersistentStoresKey]?.forEach { self.didRemoveStore($0) }
        }
    }

    deinit {
        NotificationCenter.default
            .removeObserver(self, name: .NSPersistentStoreCoordinatorStoresDidChange, object: self)
    }

    private func didAddStore(_ store: NSPersistentStore) {
        guard store.isReadOnly else { return }

        var addedObjects = Set<NSManagedObjectID>()
        baseEntityNames.forEach { entityName in
            let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: entityName)
            fetchRequest.affectedStores = [store]

            do {
                let addedEntityObjects = try readOnlyTestContext.fetch(fetchRequest)
                addedObjects = addedObjects.union(addedEntityObjects.map { $0.objectID })
            } catch {
                unhandledError("Failed to fetch all \(entityName) for read only check: \(error)") }
        }

        readOnlyObjects[store.identifier] = addedObjects
    }

    private func didRemoveStore(_ store: NSPersistentStore) {
        guard store.isReadOnly else { return }
        readOnlyObjects.removeValue(forKey: store.identifier)
    }

    /// Returns the minimum set of entities that can be fetched for readonly checking
    private lazy var baseEntityNames: [String] = {
        return managedObjectModel.entitiesByName.compactMap { $1.superentity == nil ? $0 : nil }
    }()

    private var readOnlyTestContext: NSManagedObjectContext

    /// Readonly objectIDs keyed per persistent store
    private var readOnlyObjects = [String : Set<NSManagedObjectID>]()

    internal func isObjectReadOnly(_ objectID: NSManagedObjectID) -> Bool {
        return readOnlyObjects.contains(where: { $1.contains(objectID) } )
    }
}

Затем я добавил расширение к NSManagedObject, которое запрашивает у NSPersistentStoreCoordinator статус только для чтения:

public extension NSManagedObject
{
    /// Does this managed object reside in a read-only persistent store?
    var isReadOnly: Bool {
        guard let coordinator = managedObjectContext?
            .persistentStoreCoordinator as? GraphStoreCoordinator else {
            unhandledError("Should only check readonly status in a GraphStoreCoordinator") }

        return coordinator.isObjectReadOnly(objectID)
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...