CoreData
не thread safe.NSManagedObject's
нельзя передавать между потоками. Можно передавать толькоObjectId's
. Сущности, которые используются в главном потоке, должны загружаться также в главном потоке. Это связано с тем, что у каждой сущности свой контекст в котором она существует + каждый контекст имеет свой тип. Если этоNSPrivateQueueConcurrencyType
, то у них разные приватные очереди, в которых они работают, и в каком именно бэкграунд потоке будет сохранение мы знать не можем. Если изменяем сущность в одном контексте, то другие контексты не видят этих изменений. Для мержа изменений из одного контекста в другой можно использовать нотификации - каждый контекст кидает эвент когда сохраняет изменения.- http://www.slideshare.net/xzolian/core-data-with-multiple-managed-object-contexts
- http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/
- https://medium.com/@tosinaf/core-data-reading-list-10d1eb0a89d4
-
Глобальный (синглтон) MOC-writer с типом
NSPrivateQueueConcurrencyType
. Привязан к PersistentStore. Его задача только сохранять в базу. Т.к. он имеет свою приватную Queue, то он сохраняет в бэкграунде. -
Глобальный MOC-main с типом
NSMainQueueConcurrencyType
. В качестве parent у него MOC-writer и поэтому при сохранении он мержит все изменения в parent. Его задача загружать/удалять/апдейтить сущности в главном потоке. В нашем случае его стоит использовать только дляNSFetchedResultsController
. -
Для выполнения каких-то действий с сущностями (создание/апдейт/удаление/конвертация их в json модельки), которые можно выполнять не в главном потоке, создаем локальный контекст MOC-Child с типом
NSPrivateQueueConcurrencyType
и c родительским контекстом MOC-writer. В бэкграунд блоке выполнять действия.[managedContext performBlock:^{ ... }]
При сохранении все изменения будут мержиться в MOC-writer и он будет сохранять в бэкграунде.
Сущности нельзя шарить между контекстами и потоками, но можно шарить objectId
сущности. Для чего это нужно - например, проставить во вновь созданную сущность какие-то relationship
.
Предполагаем, что сущность уже существует и сохранялась в persistent store
(все новые и несохраненные объекты имеют временный objectId
и доступны только в контексте где были созданы. Временный objectId
будет изменен при сохранении)
. Т.о. если сущность еще не сохранялась, то из другого контекста ее нельзя будет подгрузить.
NSManagedObjectID *objectID = addressEntity.objectID;
// Варианты подгрузки сущности по id в другом контексте
dispatch_async(my_private_queue, ^{
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context];
// returns the object for the specified ID if it is registered in the context already or nil. It never performs I/O.
NSManagedObject *addressEntity = [localContext objectRegisteredForID:objectID];
or
// returns the object for the specified ID if it is already registered, otherwise it creates a fault corresponding to that objectID.
// It never returns nil, and never performs I/O.
// The object specified by objectID is assumed to exist, and if that assumption is wrong the fault may throw an exception when used.
NSManagedObject *addressEntity = [localContext objectWithID:objectID];
or
// returns the object for the specified ID if it is already registered in the context, or faults the object into the context.
// It might perform I/O if the data is uncached. If the object cannot be fetched, or does not exist, or cannot be faulted, it returns nil.
// Unlike -objectWithID: it never returns a fault.
NSManagedObject *addressEntity = [localContext existingObjectWithID:objectID error:&error];
});
- при показе желательно использовать paging, т.е. подгружать сущности для показа постепенно. Конвертация сущностей в модельки может занимать довольно большое время, поэтому это можно разбить на два этапа - быстро сконвертировать небольшую часть сущностей, которые будут видимы на экране, и отдать их в UI. А остальные доконвертить в бэкграунде.
- Загрузку выполнять через локальный контекст (кроме случаев когда используется
NSFetchedResultsController
, то проставлять ему[NSManagedObjectContext MR_defaultContext]
).// Создаем конекст с типом `NSPrivateQueueConcurrencyType.` // Parent-ом будет root saving context. NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context]; NSPredicate *predicate = [NSPredicate predicateWithFormat:...]; // Perform block in a background thread [localContext performBlock:^{ // Fetch entities NSArray *addressesEntity = [TaxiFavouriteAddressEntity MR_findAllWithPredicate:predicate inContext:localContext]; // Convert to models NSMutableArray *addresses = [NSMutableArray array]; for (TaxiFavouriteAddressEntity *entity in addressesEntity) { addresses addObject:[entity toDictionary]; } dispatch_async(dispatch_get_main_queue(), ^{ // apply fetched addresses }); }];
-
импорт сущностей разбиваем на батчи (размер батчей подбираем эмпирически - 10..15..100 сущностей за раз). Не надо создавать сотни сущностей и потом сохранять их скопом - операция записи на диск займет время. Нужно сохранять на диск небольшими порциями.
-
Пример импорта:
- (void)importFavouriteAddresses:(NSArray *)addresses completion:(void (^)())completion { NSBlockOperation *op = [[NSBlockOperation alloc] init]; op.completionBlock = ^{ dispatch_async(dispatch_get_main_queue(), ^{ if (completion) { completion(); } }); }; const NSUInteger batchSize = 15; const NSUInteger addressesSize = addresses.count; NSUInteger index = 0; while (index < addressesSize) { NSUInteger localSize = batchSize; if (index + localSize > addressesSize) { localSize = addressesSize - index; } NSArray *subArray = [addresses subarrayWithRange:NSMakeRange(index, localSize)]; index += localSize; [op addExecutionBlock:^{ [MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) { NSArray *newAddresses = [self __updateRelatedAddressHierarchy:subArray inContext:localContext]; [self __importRelatedAddressHierarchy:newAddresses inContext:localContext]; }]; }]; } if (op.executionBlocks.count) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [op start]; }); } else { if (completion) { completion(); } } }