2015-08-18 2 views
17

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

стек
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) 
    .... 
} 

Моих основных данных выглядит примерно следующий с двумя управляемыми контекстами объектов, чтобы улучшить время отклика пользовательского интерфейса для объекта сохраняет для новые и отредактированные объекты «История». Это основной образец, который я видел в таких местах, как книга основных данных Маркуса Зарры и blog post.

// 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 или так, как это с одним контекстом и includesPropertyValues ​​дефолте истина:

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. 

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

+1

Поскольку я еще не получил никаких предложений, я задал вопрос технической поддержке Apple Developer.Они ответили: «Пакетная выборка не поддерживается во вложенном контексте, но установка fetchBatchSize не будет работать в этой конфигурации. Вы можете подать запрос на повышение для этого». О, хорошо. Но затем они продолжили: «Чтобы мы могли оказать дополнительную помощь, не могли бы вы рассказать о том, как вы собираетесь сохранять новые и отредактированные объекты и почему один контекст или два контекста, соединяющиеся с одним постоянным координатором магазина, Ты работаешь? Хммммм ... – LenK

ответ

1

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

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