Подтвердить что ты не робот

Пазл производительности Core Data Multi-NSManagedObjectContext

У меня проблема с производительностью IOS Core Data, которую я не смог найти. Мое приложение имеет относительно простой UICollectionViewController с функцией создания NSFetchedResultsController примерно так:

func loadCollection(collectionID: String) {
    let fetchRequest = NSFetchRequest(entityName: "Story")
    fetchRequest.predicate = NSPredicate(format: "collectionID == %@ AND wasDeleted == FALSE", collectionID)

    fetchRequest.sortDescriptors = [NSSortDescriptor(key: "yearMonthDay", ascending: true), NSSortDescriptor(key: "startDate", ascending: true)]
    fetchRequest.fetchBatchSize = 100

    self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: mainObjectContext!,
                                                                sectionNameKeyPath: "yearMonth", cacheName: collectionID)
    var error: NSError?
    self.fetchedResultsController.performFetch(&error)
    ....
}

Мой основной стек данных выглядит примерно так, как показано ниже, с двумя контекстами управляемых объектов, чтобы улучшить время отклика пользовательского интерфейса для сохранения объектов для новых и отредактированных объектов "История". Это базовая модель, которую я видел в таких местах, как Marcus Zarra Core Data book и сообщение в блоге.

// Variation "A" with two managed object contexts.
lazy var mainObjectContext: NSManagedObjectContext? = {

    if let coordinator = self.persistentStoreCoordinator {

        self.rootContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
        self.rootContext?.persistentStoreCoordinator = coordinator

        let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
        managedObjectContext.parentContext = self.rootContext
        return managedObjectContext  

    } else {
        return nil
    }
}()

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

Например, выполнение вышеприведенной выборки с двумя контекстами и около 1700 объектов занимает более 5 секунд на iPad Air, но менее 0,4 секунды с одним контекстом, как показано ниже.

// Variation "B" with one managed object context.
    lazy var mainObjectContext: NSManagedObjectContext? = {

        if let coordinator = self.persistentStoreCoordinator {

            let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
            managedObjectContext.persistentStoreCoordinator = coordinator
            return managedObjectContext  

        } else {
            return nil
        }
    }()

Я включил ведение журнала Core Data (-com.apple.CoreData.SQLDebug 1), и из регистрации я вижу, что когда у меня есть два контекста объекта, все свойства из 1700 объектов загружаются из базы данных SQL для начальная выборка. Но если у меня только один контекст, из базы данных изначально не загружаются никакие свойства.

    Log from variation "A" with two object contexts:
    2015-08-18 13:31:49.761 Myapp[27646:4374573] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAUTOCREATED, t0.ZBLOCKS, t0.ZCHANGETOKEN, t0.ZCOLLECTIONID, t0.ZCREATIONDATE, t0.ZEDITDATE, t0.ZENDDATE, t0.ZFULLTEXT, t0.ZGLOBALID, t0.ZLOCATION, t0.ZLOCATIONTEXT, t0.ZSTARTDATE, t0.ZTAGS, t0.ZTHUMBNAILIMAGE, t0.ZTIMEZONE, t0.ZTITLE, t0.ZWASDELETED, t0.ZYEARMONTH, t0.ZYEARMONTHDAY FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND  t0.ZWASDELETED = ?) ORDER BY t0.ZYEARMONTHDAY, t0.ZSTARTDATE
    2015-08-18 13:31:52.093 Myapp[27646:4374573] CoreData: annotation: sql connection fetch time: 2.3314s
    2015-08-18 13:31:55.011 Myapp[27646:4374573] CoreData: annotation: total fetch execution time: 5.2502s for 1743 rows.
    2015-08-18 13:31:55.016 Myapp[27646:4374573] CoreData: sql: SELECT t0.ZYEARMONTH, COUNT (DISTINCT  t0.Z_PK) FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND  t0.ZWASDELETED = ?) GROUP BY  t0.ZYEARMONTH ORDER BY t0.ZYEARMONTH
    2015-08-18 13:31:55.276 Myapp[27646:4374573] CoreData: annotation: sql connection fetch time: 0.2590s
    2015-08-18 13:31:55.276 Myapp[27646:4374573] CoreData: annotation: total fetch execution time: 0.2600s for 371 rows.


    Log from variation "B" with one object context:
    2015-08-18 13:25:19.448 Myapp[27635:4362501] CoreData: sql: SELECT 0, t0.Z_PK FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND  t0.ZWASDELETED = ?) ORDER BY t0.ZYEARMONTHDAY, t0.ZSTARTDATE
    2015-08-18 13:25:19.789 Myapp[27635:4362501] CoreData: annotation: sql connection fetch time: 0.3405s
    2015-08-18 13:25:19.789 Myapp[27635:4362501] CoreData: annotation: total fetch execution time: 0.3410s for 1743 rows.
    2015-08-18 13:25:19.790 Myapp[27635:4362501] CoreData: sql: SELECT t0.ZYEARMONTH, COUNT (DISTINCT  t0.Z_PK) FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND  t0.ZWASDELETED = ?) GROUP BY  t0.ZYEARMONTH ORDER BY t0.ZYEARMONTH
    2015-08-18 13:25:19.825 Myapp[27635:4362501] CoreData: annotation: sql connection fetch time: 0.0339s
    2015-08-18 13:25:19.825 Myapp[27635:4362501] CoreData: annotation: total fetch execution time: 0.0349s for 371 rows.

Итак, основываясь на этих результатах, я изменил функцию loadCollection, чтобы добавить

fetchRequest.includesPropertyValues = false 

Теперь с двумя объектными контекстами я получаю следующее: начальное время загрузки меньше 0,4 секунды - отлично!:

    2015-08-18 13:53:43.345 Myapp[27692:4414951] CoreData: sql: SELECT 0, t0.Z_PK FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND  t0.ZWASDELETED = ?) ORDER BY t0.ZYEARMONTHDAY, t0.ZSTARTDATE

    2015-08-18 13:53:43.683 Myapp[27692:4414951] CoreData: annotation: sql connection fetch time: 0.3378s

    2015-08-18 13:53:43.684 Myapp[27692:4414951] CoreData: annotation: total fetch execution time: 0.3383s for 1743 rows.

    2015-08-18 13:53:43.688 Myapp[27692:4414951] CoreData: sql: SELECT t0.ZYEARMONTH, COUNT (DISTINCT  t0.Z_PK) FROM ZSTORY t0 WHERE ( t0.ZCOLLECTIONID = ? AND  t0.ZWASDELETED = ?) GROUP BY  t0.ZYEARMONTH ORDER BY t0.ZYEARMONTH

    2015-08-18 13:53:43.722 Myapp[27692:4414951] CoreData: annotation: sql connection fetch time: 0.0329s

    2015-08-18 13:53:43.722 Myapp[27692:4414951] CoreData: annotation: total fetch execution time: 0.0339s for 371 rows.

НО, теперь каждый объект, полученный для NSFetchedResultsController, является ошибкой и приводит к SQL-запросу одной строки, что заметно замедляет прокрутку коллекции:

2015-08-18 13:53:44.078 Myapp[27692:4414951] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAUTOCREATED, t0.ZBLOCKS, t0.ZCHANGETOKEN, t0.ZCOLLECTIONID, t0.ZCREATIONDATE, t0.ZEDITDATE, t0.ZENDDATE, t0.ZFULLTEXT, t0.ZGLOBALID, t0.ZLOCATION, t0.ZLOCATIONTEXT, t0.ZSTARTDATE, t0.ZTAGS, t0.ZTHUMBNAILIMAGE, t0.ZTIMEZONE, t0.ZTITLE, t0.ZWASDELETED, t0.ZYEARMONTH, t0.ZYEARMONTHDAY FROM ZSTORY t0 WHERE  t0.Z_PK = ? 

2015-08-18 13:53:44.081 Myapp[27692:4414951] CoreData: annotation: sql connection fetch time: 0.0011s

2015-08-18 13:53:44.081 Myapp[27692:4414951] CoreData: annotation: total fetch execution time: 0.0029s for 1 rows.

2015-08-18 13:53:44.082 Myapp[27692:4414951] CoreData: annotation: fault fulfilled from database for : 0xd00000001ad8000c <x-coredata://967F5940-D73C-48E2-A26D-E60FF87BA9F7/Story/p1718>

Вместо партий по 100 или около того, подобных этому с единственным контекстом, и includePropertyValues ​​по умолчанию имеет значение true:

2015-08-18 14:47:44.221 Myapp[27792:4515252] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAUTOCREATED, t0.ZBLOCKS, t0.ZCHANGETOKEN, t0.ZCOLLECTIONID, t0.ZCREATIONDATE, t0.ZEDITDATE, t0.ZENDDATE, t0.ZFULLTEXT, t0.ZGLOBALID, t0.ZLOCATION, t0.ZLOCATIONTEXT, t0.ZSTARTDATE, t0.ZTAGS, t0.ZTHUMBNAILIMAGE, t0.ZTIMEZONE, t0.ZTITLE, t0.ZWASDELETED, t0.ZYEARMONTH, t0.ZYEARMONTHDAY FROM ZSTORY t0 WHERE  t0.Z_PK IN (SELECT * FROM _Z_intarray0)  ORDER BY t0.ZYEARMONTHDAY, t0.ZSTARTDATE LIMIT 100
2015-08-18 14:47:44.468 Myapp[27792:4515252] CoreData: annotation: sql connection fetch time: 0.0959s
2015-08-18 14:47:44.469 Myapp[27792:4515252] CoreData: annotation: total fetch execution time: 0.2566s for 100 rows.
2015-08-18 14:47:44.550 Myapp[27792:4515252] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAUTOCREATED, t0.ZBLOCKS, t0.ZCHANGETOKEN, t0.ZCOLLECTIONID, t0.ZCREATIONDATE, t0.ZEDITDATE, t0.ZENDDATE, t0.ZFULLTEXT, t0.ZGLOBALID, t0.ZLOCATION, t0.ZLOCATIONTEXT, t0.ZSTARTDATE, t0.ZTAGS, t0.ZTHUMBNAILIMAGE, t0.ZTIMEZONE, t0.ZTITLE, t0.ZWASDELETED, t0.ZYEARMONTH, t0.ZYEARMONTHDAY FROM ZSTORY t0 WHERE  t0.Z_PK IN  (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)  ORDER BY t0.ZYEARMONTHDAY, t0.ZSTARTDATE LIMIT 100
2015-08-18 14:47:44.614 Myapp[27792:4515252] CoreData: annotation: sql connection fetch time: 0.0067s
2015-08-18 14:47:44.614 Myapp[27792:4515252] CoreData: annotation: total fetch execution time: 0.0635s for 44 rows.

Любые мысли или предложения о лучшем способе сделать это будут наиболее ценными.

4b9b3361

Ответ 1

Это известная проблема с NSFetchedResultsController и размером партии. Вы не можете использовать контексты родителя-ребенка, не теряя при этом функцию batchSize. Подробнее см. .

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