I'm having problems with Core Data concurrency on my iOS app.
On my executeFetchRequest I tried to synchronize the managedObjectContext request, but some times this method makes my app freeze.
- (NSArray *)synchronizedWithFetchRequest:(NSFetchRequest *)request andError:(NSError **)error
{
#synchronized(self.managedObjectContext)
{
return [self.managedObjectContext executeFetchRequest:request error:error];
}
}
I've already tried many things like lock/unlock, performBlock/performBlockAndWait, dispatch_sync/dispatch_async and nothing seems to work.
Managed Object Context creation:
...
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
Is there some way around this? and keep my request returning the results objects on this method?
Thanks!
Synchronising on the MOC suggests that there is more than one thread accessing the same MOC.
That in itself is a violation of CoreData concurrency protocols.
This access is prohibited unless it is wrapped in the context performBlock: method (or its "wait" counterpart). this will negate the need for the #synchronized block altogether.
This thread/queue "boundness" extends to the contexts fetched/registered managed objects, and so, you will not be able to access them as the return values of your method.
Related
I'm trying to separate my application work when there is a bigger work to do to optimize performance. My problem is about a NSManagedObjectContext used in another thread than the main one.
I'm calling:
[NSThread detachNewThreadSelector:#selector(test:) toTarget:self withObject:myObject];
On the test method there are some stuff to do and I have a problem here:
NSArray *fetchResults = [moc
executeFetchRequest:request
error:&error];
Here is my test method:
-(void) test:(MyObject *)myObject{
#autoreleasepool {
//Mycode
}
}
The second time I call the test method, my new thread is blocked when the executeFetchRequest is called.
This problem arrived when my test method is called more than one time in succession. I think the problem comes from the moc but I can't really understand why.
Edit:
With #Charlie's method it's almost working. Here is my code to save my NSManagedObjectContext (object created on my new thread).
- (void) saveContext:(NSManagedObjectContext *) moc{
NSError *error = nil;
if ([moc hasChanges] && ![moc save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
This method is called on the new thread. My problem now is that with this save, I have a deadlock and I don't really understand why. Without it's perfectly working.
Edit2
I'm working on this issue but I still can't fix it. I changed my code about the detachNewThreadSelector. Here is my new code:
NSManagedObjectContext* context = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.persistentStoreCoordinator = self.persistentStoreCoordinator;
context.undoManager = nil;
[context performBlock:^
{
CCImages* cachedImage;
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
childContext.parentContext = context;
cachedImage=[CCImages getCCImageForKey:path inManagedObjectContext:childContext];
UIImage *image = [self getImageFromCacheWithPath:path andCachedImage:cachedImage atDate:now];
if (image != nil){
if(![weakSelf.delegate respondsToSelector:#selector(CacheCacheDidLoadImageFromCache:)])
[weakSelf setDelegate:appDelegate.callbacksCollector];
//[weakSelf useCallbackCollectorForDelegate:weakSelf inMethod:#"initPaginatorForListMoments"];
[weakSelf.delegate CacheCacheDidLoadImageFromCache:image];
}
}
- (UIImage*) getImageFromCacheWithPath:(NSString*) path andCachedImage:(CCImages *) cachedImage atDate: (NSDate *) now{
NSURL* localURL=[NSURL URLWithString:cachedImage.path relativeToURL:[self imageCacheDirectory]];
UIImage * image;
//restore uiimage from local file system
if (localURL) {
image=[UIImage imageWithContentsOfFile:[localURL path]];
//update cache
[cachedImage setLastAccessedAt:now];
[self saveContext];
if(image)
return image;
}
return nil;
}
Just after that, I'm saving my contexts (manually for now)
[childContext performBlock:^{
NSError *error = nil;
if (![childContext save:&error]) {
DDLogError(#"Error during context saving when getting image from cache : %#",[error description]);
}
else{
[context performBlock:^{
NSError *error = nil;
if (![context save:&error]) {
DDLogError(#"Error during context saving when getting image from cache : %#",[error description]);
}
}];
}
}];
There is a strange problem. My call back method is called without any problem on my controller (which implements the CacheCacheDidLoadImageFromCache: method). On this method I attest the reception of the image (DDLogInfo) and say that I want my spinner to stop. It does not directly but only 15secondes after the callback method was called.
My main problem is that my context (I guess) is still loading my image from the cache while it was already found. I said 'already' because the callback method has been called and the image was present. There is no suspicious activity of the CPU or of the memory. Instruments didn't find any leak.
I'm pretty sure that I'm using wrongly the NSManagedObjectContext but I can't find where.
You are using the old concurrency model of thread confinement, and violating it's rules (as described in the Core Data Concurrency Guide, which has not been updated yet for queue confinement). Specifically, you are trying to use an NSManagedObjectContext or NSManagedObject between multiple threads.
This is bad.
Thread confinement should not be used for new code, only to maintain the compatibility of old code while it's being migrated to queue confinement. This does not seem to apply to you.
To use queue confinement to solve your problem, first you should create a context attached to your persistent store coordinator. This will serve as the parent for all other contexts:
+ (NSManagedObjectContent *) parentContextWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator {
NSManagedObjectContext *result = nil;
result = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[result setPersistentStoreCoordinator:coordinator];
return result;
}
Next, you want the ability to create child managed object contexts. You will use these to perform work on the data, wether reading or writing. An NSManagedObjectContext is a scratchpad of the work you are doing. You can think of it as a transaction. For example, if you're updating the store from a detail view controller you would create a new child context. Or if you were performing a multi-step import of a large data set, you would create a child for each step.
This will create a new child context from a parent:
+ (NSManagedObjectContext *) childContextWithParent:(NSManagedObjectContext *)parent {
NSManagedObjectContext *result = nil;
result = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[result setParent:parent];
return result;
}
Now you have a parent context, and you can create child contexts to perform work. To perform work on a context, you must wrap that work in performBlock: to execute it on the context's queue. I do not recommend using performBlockAndWait:. That is intended only for re-rentrant methods, and does not provide an autorelease pool or processing of user events (user events are what drives nearly all of Core Data, so they're important. performBlockAndWait: is an easy way to introduce bugs).
Instead of performBlockAndWait: for your example above, create a method that takes a block to process the results of your fetch. The fetch, and the block, will run from the context's queue - the threading is done for you by Core Data:
- (void) doThingWithFetchResults:(void (^)(NSArray *results, NSError *error))resultsHandler{
if (resultsHandler != nil){
[[self context] performBlock:^{
NSArray *fetchResults = [[self context] executeFetchRequest:request error:&error];
resultsHandler(fetchResults, error);
}];
}
}
Which you would call like this:
[self doThingsWithFetchResults:^(NSArray *something, NSError *error){
if ([something count] > 0){
// Do stuff with your array of managed objects
} else {
// Handle the error
}
}];
That said, always prefer using an NSFetchedResultsController over using executeFetch:. There seems to be a belief that NSFetchedResultsController is for powering table views or that it can only be used from the main thread or queue. This is not true. A fetched results controller can be used with a private queue context as shown above, it does not require a main queue context. The delegate callbacks the fetched results controller emits will come from whatever queue it's context is using, so UIKit calls need to be made on the main queue inside your delegate method implementations. The one issue with using a fetched results controller this way is that caching does not work due to a bug.
Again, always prefer the higher level NSFetchedResultsController to executeFetch:.
When you save a context using queue confinement you are only saving that context, and the save will push the changes in that context to it's parent. To save to the store you must recursively save all the way. This is easy to do. Save the current context, then call save on the parent as well. Doing this recursively will save all the way to the store - the context that has no parent context.
Example:
- (void) saveContextAllTheWayBaby:(NSManagedObjectContext *)context {
[context performBlock:^{
NSError *error = nil;
if (![context save:&error]){
// Handle the error appropriately.
} else {
[self saveContextAllTheWayBaby:[context parentContext]];
}
}];
}
You do not, and should not, use merge notifications and mergeChangesFromContextDidSaveNotification: with queue confinement. mergeChangesFromContextDidSaveNotification: is a mechanism for the thread confinement model that is replaced by the parent-child context model. Using it can cause a whole slew of problems.
Following the examples above you should be able to abandon thread confinement and all of the issues that come with it. The problems you are seeing with your current implementation are only the tip of the iceberg.
There are a number of Core Data sessions from the past several years of WWDC that may also be of help. The 2012 WWDC Session "Core Data Best Practices" should be of particular interest.
if you want to use managed object context in background thread, there are two approaches,
1 Create a new context set concurrency type to NSPrivateQueueConcurrencyType and set the parentContext to main thread context
2 Create a new context set concurrency type to NSPrivateQueueConcurrencyType and set persistentStoreCoordinator to main thread persistentStoreCoordinator
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
privateContext.persistentStoreCoordinator = mainManagedObjectContext.persistentStoreCoordinator;
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {
NSManagedObjectContext *moc = mainManagedObjectContext;
if (note.object != moc) {
[moc mergeChangesFromContextDidSaveNotification:note];
}
}];
// do work here
// remember managed object is not thread save, so you need to reload the object in private context
});
before exist the thread, make sure remove the observer, bad thing can happen if you don't
for more details read http://www.objc.io/issue-2/common-background-practices.html
I'm trying to best format my project's use of RestKit and Core Data. There are a couple of things that I've got working, but I have a feeling they are implemented poorly and potentially thread unsafe... I have an object that manages all of my data transfer, storage, etc., which has a function that sets up restkit. I have an instance variable that I use for RKObjectManager, and in this setup function I create the objectStore, setup all the attribute mappings, create the persistent store, etc., - all of the normal restkit setup code. Outside of this function, the only thing available to this object is the _objectManager instance variable, which I've been using for NSFetchRequests and such.
There are two things I want to make sure I'm implementing properly, fetching managed objects, and savings changes to managed objects.
If I want to update a property on an object, I've been doing this:
object.whatever = #"something here";
NSError *error;
if (![object.managedObjectContext save:&error]) {
// log the error here
}
Is this the proper way to update/save a property on an object? Is accessing the object's managed object context directly save to do at any point in the code, or is this something that should be done only in the background/foreground? My current implementation could potentially have this called in both the background and foreground and I just want to make sure this is acceptable.
When I want to fetch an object, I wrote a function that takes an entity name, an array of predicates, and a sort descriptor as parameters so it can be reused:
NSManagedObjectContext *managedObjectContext = // I DONT KNOW WHAT TO PUT HERE! //
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
[fetchRequest setPredicate:compoundPredicate];
NSError *error;
NSArray *fetchedRecords = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
// log error
}
// if we were given a sort descriptor, sort the array appropriately
if (sortDescriptor) {
fetchedRecords = [fetchedRecords sortedArrayUsingDescriptors:#[sortDescriptor]];
}
return fetchedRecords;
My problem here is creating/accessing the correct managed object context. How should I do this? Do I access some property on the RKObjectManager I created before such as:
_objectManager.managedObjectStore.mainQueueManagedObjectContext
or is that not thread safe because its for the main thread? What can I do to make sure I'm using the right managed object context, and that it is thread safe? I was using:
_objectManager.managedObjectStore.persistentStoreManagedObjectContext
but I was told this was definitely not best practice and was not thread safe, so I'm trying to determine the best solution.
EDIT - perhaps I can call this function to get the context whenever I want to fetch objects?
- (NSManagedObjectContext *)getManagedObjectContext {
if ([NSThread isMainThread]) {
return _objectManager.managedObjectStore.mainQueueManagedObjectContext;
}
else {
return [_objectManager.managedObjectStore newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType tracksChanges:YES];
}
}
For saving, instead of this:
if (![object.managedObjectContext save:&error]) {
you should do:
if (![object.managedObjectContext saveToPersistentStore:&error]) {
so that the changes are persisted right up the chain to the on-disk store. You should only be doing this on the thread that created / fetched the managed object (thus the thread ownership of the MOC is maintained).
Foreground / background isn't important so much as which MOC is used by each thread. If the MOC thread ownership is respected then you should be fine.
Same applies to fetching. For UI updates, you must use the main thread and the mainQueueManagedObjectContext. You should never directly use the persistentStoreManagedObjectContext. For arbitrary background threads you should be asking the managed object store to create a new child managed object context for you and using that.
I get runtime errors which seem to result from my incorrect implementation of GCD in combination with my custom NSManagedObjects.
Nested in a GCD call, I am using custom NSManagedObjects which (seem to) have their own managed object contexts (= self.managedObjectContext).
I am creating the managed object context in the app delegate by using the managed object context provided by UIManagedDocument: self.managedDocument.managedObjectContext.
I don't understand how to pass the correct managed object context down to my custom NSManagedObjects. How would I need to change my code to use the correct managed object context?
This is my main method (inside a view controller):
dispatch_queue_t queue;
queue = dispatch_queue_create("queue", NULL);
dispatch_async(queue, ^{
// ...
NSDecimalNumber *value = [reportedPeriod
valueForCoa:figure.code
convertedTo:self.currencySymbol];
// ...});
}
In this main method I do not have any reference to a managed object context, I do just call valueForCoa:convertedTo: (which is coded as follows):
- (NSDecimalNumber*)valueForCoa:(NSString*)coaStr
convertedTo:(NSString*)targetCurrencyStr {
// ...
CoaMap *coa = [CoaMap coaItemForString:coaStr
inManagedObjectContext:self.managedObjectContext];
// ...
}
valueForCoa is a method in my custom subclassed NSManagedObject ReportedPeriod and uses its (default) managed object context self.managedObjectContext.
The app then usually crashes in the custom subclassed NSManagedObject CoaMap in the following method when it executes the fetch request:
+ (CoaMap*)coaItemForString:(NSString*)coaStr
inManagedObjectContext:(NSManagedObjectContext*)context {
NSFetchRequest *request = [NSFetchRequest
fetchRequestWithEntityName:NSStringFromClass([self class])];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"coa == %#",coaStr];
request.predicate = predicate;
// ** The runtime error occurs in the following line **
NSArray *results = [context executeFetchRequest:request error:nil];
// ...
}
The error message is: Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x9a8a4a0> was mutated while being enumerated.
Could you please help me with this issue and give me some suggestions on how to improve my code to pass the correct managed object contexts (or on how to make sure that the correct context is used in all methods)?
Thank you very much!
That error generally relates to using a managed object incorrectly context across different threads or queues. You created the MOC on the main queue, but you're using it on a background queue without considering that fact. It's not wrong to use the MOC on a background queue, but you need to be aware of that and take preparations.
You didn't say how you're creating the MOC. I suggest that you should be doing this:
NSManagedObjectContext *context = [[NSManagedObjectContext alloc]
initWithConcurrencyType: NSMainQueueConcurrencyType];
With main queue concurrency you can just use it normally on the main thread. When you're in your dispatch queue though, do this:
[context performBlockAndWait:^{
NSFetchRequest *request = [NSFetchRequest
fetchRequestWithEntityName:NSStringFromClass([self class])];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"coa == %#",coaStr];
request.predicate = predicate;
NSArray *results = [context executeFetchRequest:request error:nil];
// ...
}];
This will ensure that the MOC's work occurs on the main thread even though you're on a background queue. (Technically what it actually means is that the MOC's work in the background will be correctly synchronized with work it does on the main queue, but the result is the same: this is the safe way to do this).
A similar approach would be to use NSPrivateQueueConcurrencyType instead. If you do that, you'd use performBlock or performBlockAndWait everywhere for the MOC, not just on background threads.
First,
"how to pass the correct managed object context down to my custom NSManagedObjects."
We create NSManagedObject with NSManagedObjectContext. not the other way around. So, when you have a NSManagedObject, you can access the NSManagedObjectContext by asking its property: – managedObjectContext as listed in Apple Document
Second,
When working with CoreData, multi-threading could be a little tricky. especially for the beginner. The are all kind of details that you need to take care of.
I highly recommend you checkout the Parent-Child NSManagedContext. then, using MagicRecord.
By using MagicRecord, you can simply Grand Central Dispatch with a Block like this:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
// here use the `localContext` as your NSManagedContext and do things in the background.
// it will take care of all the rest.
}];
If you need to pass NSManagedObject into this block, remember to only pass the NSManagedObjectID instead of the proprety.
this is a example.
NSManagedObjectID *coaMapID = CoaMap.objectID;
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
coaMap *aCoaMap = (coaMap *)[localContext existingObjectWithID:coaMapID error:&error];
// then use aCoaMap normally.
}];
Hope this helped.
When using NSPrivateQueueConcurrencyType and NSMainQueueConcurrencyType types for NSManagedObjectContext,
is it safe to make nested performBlock calls on the same context ?
[backgroundContext performBlock:^{
NSFetchRequest *myRequest = ...;
__block NSArray *result= nil;
[backgroundContext performBlockAndWait:^{
results = [backgroundContext executeFetchRequest:myRequest error:NULL];
}];
}];
It may seem stupid but I have an existing codebase with a lot of helpers methods which encapsulate the executeFetchRequest calls. I don't want to make assumptions about whether the caller has already used performBlock or not.
For example:
-(void)updateObjects:(BOOL)synchronous
{
if (YES == synchronous)
[self fetchHelper];
else
{
[backgroundContext performBlock:^{
[self fetchHelper];
}];
}
}
-(NSArray*)fetchHelper
{
[self.backgroundContext performBlockAndWait:^{
//Fetch the objects...
[self.backgroundContext executeFetchRequest: (...)];
}];
}
I have tried it and it works. But I have learned (the hard way) to be very careful with Core Data and multi-threading.
Yes, performBlockAndWait is reentrant. Directly from Apple's release notes...
Core Data formalizes the concurrency model for the
NSManagedObjectContext class with new options. When you create a
context, you can specify the concurrency pattern to use with it:
thread confinement, a private dispatch queue, or the main dispatch
queue. The NSConfinementConcurrencyType option provides the same
behavior that was present on versions of iOS prior to 5.0 and is the
default. When sending messages to a context created with a queue
association, you must use the performBlock: or performBlockAndWait:
method if your code is not already executing on that queue (for the
main queue type) or within the scope of a performBlock... invocation
(for the private queue type). Within the blocks passed to those
methods, you can use the methods of NSManagedObjectContext freely. The
performBlockAndWait: method supports API reentrancy. The performBlock:
method includes an autorelease pool and calls the
processPendingChanges method upon completion.
I'm developing an application through Core Data and I need to perform some calculation in a background thread to create an xml file based on specific NSManagedObject.
Following the documentation, I set up NSOperation subclass. This class has a property like the following:
#property (nonatomic, retain) NSArray* objectIDs;
where objectIDs is an array of managed object ids (of type NSManagedObjectID). This is necessary according to the documentation: NSManagedObject are not thread safe.
Inside the main of NSOperation subclass I'm doing the following:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSManagedObjectContext *exportContext = [[NSManagedObjectContext alloc] init];
[exportContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
for (NSManagedObjectID* objectID in self.orderObjectIDs) {
NSError* error = nil;
Order* order = (Order*)[exportContext existingObjectWithID:objectID error:&error];
// order.someRelationship is fault here...
// Create XML file here...
}
[exportContext reset];
[exportContext release], exportContext = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:kRegisterComplete object:self];
[pool drain];
pool = nil;
Inside the for loop I'm fetching the right object using existingObjectWithID:error method of NSManagedObjectContext class since
Unlike objectWithID:, this method never returns a fault.
The method works. I'm able to retrieve the properties of that retrieved object. The only problem is that relationships are fetched as faults.
Said this, I have two questions.
First, is this the right approach to fecth NSManagedObject in a background thread?
Then, how can I fetch relationships for each fetched object within the for loop? Do I have to create a NSFetchedRequest to fetch the relationship object based on the specific object that has been fetched through the id?
Thank you in advance.
Why do you care that the relationship is a fault? Accessing it will fill the fault and return the data, are you concerned about disk I/O for some reason?
You may try using prefetching to alleviate some of the I/O overhead, but it's utility is limited to the relationship on the entity being fetched:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html
In that case you would create a fetch request with a predicate like so:
[NSPredicate predicateWithFormat:#"SELF == %#", objectID];
That will return to you only the managed object you desire, and with relationship pre-fetching the relationship won't be a fault.
You could further optimize this by using a IN clause and fetching all the objects at once:
[NSPredicate predicateWithFormat:#"SELF IN (%#)", desiredObjectIDs];
You need to make sure of the following:
Before you pass around any object IDs you do - [NSManagedObjectContext obtainPermanentIDsForObjects:error:] on the originating thread.
(on main thread)
[managedObjectContext obtainPermanentIDsForObjects:objectIDs error:nil]
Before attempting to access these objects from another thread (including their relationships) you need to save the NSManagedObjectContext of the originating thread. Otherwise the objects will not exist in persistence and your background thread will be unable to fetch it (or other relationships). Is easiest way to insure this is to call a blocking save on the main thread at the beginning of your NSOperation execution. For example:
dispatch_sync(dispatch_get_main_queue(), {
[managedObjectContext save:nil];
});
You may make an NSFetchRequest or access the relationships directly depending on which way is more convenient; probably accessing the relationships directly. You do not need to call a [NSManagedObjectContext reset] at the end of your routine.