Лучшая модель для CoreData, когда имеется огромное количество полей и интенсивное обновление во время синхронизации - PullRequest
0 голосов
/ 25 мая 2018

У меня недавно сильно болела голова в моем приложении:

В моем приложении каждый из блоков имеет почти 100 атрибутов, которые можно сохранить, и некоторые из них могут обновляться очень часто, даже десятки или большеобновляется на единицу в течение нескольких секунд во время начальной синхронизации.Поскольку объединение их всех в одну таблицу может быть очень неэффективным, и есть некоторые из них, которые с большей вероятностью будут повторно синхронизированы, я попытался разбить блок на 5-6 меньших таблиц в соответствии с функциональностью и возможной частотой обновления, каждая из которыху них есть 8 - 32 поля и еще одно для первичного ключа.Чтобы предотвратить блокировку основного потока, я также создал фоновый контекст для обновления данных.Когда вызывается context.save(), основной контекст объединяется с фоновым контекстом.

Однако, когда я пытаюсь выполнить пакетное тестирование, скажем, около 50 единиц в таблице, я обнаруживаю, что пользовательский интерфейс все еще серьезнозаблокирован, поскольку основной контекст (который отвечает за выборку макета пользовательского интерфейса в соответствии с атрибутами единиц в базе данных) занят при объединении с интенсивным фоновым обновлением контекста.

Я также обнаружил, что при повторной загрузке данных происходит более 10000 слияний, даже если их всего 50 единиц, я посмотрел на них и обнаружил, что всякий раз, когда вызывается один context.save(), мое приложение достигает 9 NSManagedObjectContextDidSaveуведомления, что означает, что большая часть слияния на самом деле является избыточной.

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

Не уверен, если периодически переходить на сохранение контекстаЭто хорошая идея, потому что это приложение Bluetooth, и данные могут приходить и уходить в любое время, в любом потоке, поэтому точность мгновенных данных в локальной БД имеет решающее значение для принятия правильных действий.Тем не менее, изменения не будут приняты, если я не передам их через context.save() сразу.

Подводя итог, здесь можно задать два вопроса:

  1. Почему несколько NSManagedObjectContextDidSave сработало, а context.save() вызывается только один раз?

  2. При работе с такого рода структурой базы данных (с большим количеством полей в записи), есть лилучшая модель, которая может обеспечить лучший компромисс между опытом пользовательского интерфейса и точностью данных?

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

Мой код для управления основными данными записан следующим образом:

// MARK: - Example on saving a NSManagedObject in background thread
class ExampleRepository : CoreDataStack {

    func updateRecordToDbInBackground(uuid: String, values: [String: Data]) {
        let ctx = getBackgroundContext()
        ctx.perform {
            guard let resultId = self.fetchByUuid(uuid: uuid, context: ctx) else {
                self.addRecordToDb(uuid: uuid, values: values, context: ctx)
                self.saveContext(context: ctx, method: #function)
                return
            }
            let result = ctx.object(with: resultId)
            _ = self.updateRecordToDb(uuid: uuid, values: values, managedObject: result, context: ctx)
            self.saveContext(context: ctx, method: #function)
        }
    }

    func fetchByUuid(uuid: String, context: NSManagedObjectContext) -> NSManagedObjectID? {

        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: ENTITY_NAME)
        fetchRequest.predicate = NSPredicate(format: "SELF.uuid = %@", uuid)

        do {
            return try context.fetch(fetchRequest).first?.objectID
        } catch let error as NSError {
            print("Could not fetch. \(error), \(error.userInfo)")
        }
        return nil
    }

    func updateRecordToDb(uuid: String, values: [String: Data], managedObject: NSManagedObject?, context: NSManagedObjectContext) -> NSManagedObject {
        let registerObj = managedObject ?? getManagedObject(context: context)        
        registerObj.setValue(uuid, forKey: FIELD_NAME_UUID)
        let convertedValues: NSMutableDictionary = [:]
        values.forEach { e in
            convertedValues.addEntries(from: e.key: e.value])
        }
        registerObj.setValuesForKeys(convertedValues as! [String: Data])
        return registerObj
    }
    func addRecordToDb(uuid: String, values: [String: Data], managedObject: NSManagedObject?, context: NSManagedObjectContext) -> NSManagedObject {
        // add is generally similar to updateRecoredToDb, but adding some relationships to other entities
    }
}

// MARK: - Core data stack
class CoreDataStack {

    init(){
        NotificationCenter.default.addObserver(self, selector: #selector(self.contextDidSaveContext), name: NSNotification.Name.NSManagedObjectContextDidSave, object: nil)
    }

    deinit{
        NotificationCenter.default.removeObserver(self)
    }
    var _context: NSManagedObjectContext {
        return SingletonContext.sharedInstance._context
    }
    var _backgroundContext: NSManagedObjectContext {
        return SingletonContext.sharedInstance._backgroundContext
    }

    func saveContext(context: NSManagedObjectContext, method: String) {
        guard context.hasChanges else {
            return
        }

        context.perform {
            do {
                try context.save()
                print("Context called from \(method) saved!")
            } catch let error as NSError  {
                print("Could not save context called from \(method), Error: \(error), \(error.userInfo)")
            }
        }
    }

    @objc func contextDidSaveContext(notification: NSNotification) {

        // The question is here: This func is called nine times when the context.save() were called once.

        let sender = notification.object as! NSManagedObjectContext

        if sender === self._context {
            print("******** Merge background context with main ********")
            self._backgroundContext.perform {
                self._backgroundContext.mergeChanges(fromContextDidSave: notification as Notification)
            }
        }
        else if sender === self._backgroundContext {
            print("******** main main context with background ********")
            self._context.perform {
                self._context.mergeChanges(fromContextDidSave: notification as Notification)
            }
        }
    }
}


class SingletonContext {
    static let sharedInstance = SingletonContext()

    var _persistentStoreCoordinator: NSPersistentStoreCoordinator {
        return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.persistentStoreCoordinator
    }

    lazy var _context: NSManagedObjectContext = {
        let ctx = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.mainQueueConcurrencyType)
        ctx.persistentStoreCoordinator = self._persistentStoreCoordinator
        return ctx
    }()

    lazy var _backgroundContext: NSManagedObjectContext = {
        let ctx = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
        ctx.persistentStoreCoordinator = self._context.persistentStoreCoordinator
        return ctx
    }()

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