I'm still coding my RSS reader and I have gotten to the point where I'd like things to go smoother by background filling my Feeds at once with the newest Posts.
The problem is that it crashes my app quite badly with messages such as:
2013-10-02 21:06:25.474 uRSS[97209:a0b] *** Terminating app due to uncaught
exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource
must return a cell from tableView:cellForRowAtIndexPath:'
(stacktrace)
I came to the conclusion that I am not running thread safe here and I then discovered this kind of CoreData snippets:
//Core Data's NSPrivateQueueConcurrencyType and sharing objects between threads
[context performBlock:^{
// fetch request code
NSArray *results = [context executeFetchRequest:request error:nil];
dispatch_async(dispatch_get_main_queue(), ^(void) {
Class *firstObject = [results objectAtIndex:0];
// do something with firstObject
});
}];
// Assume we have these two context (They need to be set up. Assume they are.)
NSManagedObjectContext *mainMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType] autorelease];
NSManagedObjectContext *backgroundMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
// Now this can safely be called from ANY thread:
[backgroundMOC performBlock:^{
NSArray *results = [context executeFetchRequest:request error:nil];
for (NSManagedObject *mo in results) {
NSManagedObjectID *moid = [mo objectID];
[mainMOC performBlock:^{
NSManagedObject *mainMO = [mainMOC objectWithID:moid];
// Do stuff with 'mainMO'. Be careful NOT to use 'mo'.
}];
}
}];
Now, what I would like to know is the following:
should the backgroundMOC be defined as a Class member property, or everytime the method that uses it is invoked?
what if this method is itself invoked asynchronously (the RSS parsing method create the objects on the fly)?
How may I securely notify my UITAbleView that my MOC's been updated so that it can refresh without crashing?
Does this only apply to fetches, or also to objects insertions, deletions, etc?
Where could I find a working example of this concept successfully applied?
1) backgroundMOC should be defined in the scope, where you use it. Say, if you use context inside of SomeClass, it's good to define it as property of SomeClass. However, usually many classes share same context (for example, it's quite OK to share mainMOC between all your viewControllers) so i suggest to define mainMOC and backgroundMOC in your AppDelegate or some other singleton.
2) It's OK. However, it's bad idea to create contexts every time — see 1 and initialize them once in singleton.
3) Take a look at NSFetchedResultsController. It's exactly what you need to setup your tableView and track CoreData changes.
4) Yes
5) Cannot really point you to working example. Find something out on developer.apple.com =)
Also remarks:
1) Your class cannot be named Class
2) Use existingObjectWithID:error:, not objectWithID: — check this answer, it was really annoying issue in my experience
3) Read about NSManagedObjectContext concurrency patterns
Related
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.
Quite simply I want to iterate through CoreData's [self.fetchedResultsController fetchedObjects] (More specifically I am using MagicalRecord in this example) and change all items that match my criteria (the if statement), then make changes to those objects.
for (Task *aTask in [[self.fetchedResultsController fetchedObjects] mutableCopy]) {
if ([aTask.day past] && [[aTask isArchived] isEqual:#(NO)]) {
NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];
[context MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) {
aTask.day = date;
}];
}
}
But as it turns out it doesn't work! I am trying a solution where I don't have to manually manage everything CoreData as this answer specifies and instead find a succinct solution to the problem, few lines of code and not much iteration.
EDIT: Following from Dan's answer, I have edited my imperfect code to...
NSPredicate *uncompletedTasks = [NSCompoundPredicate andPredicateWithSubpredicates:#[[NSPredicate predicateWithFormat:#"day < %# AND isArchived = %#",[NSDate date],#NO]]];
self.fetchedResultsController = [Task MR_fetchAllSortedBy:#"dateScheduled" ascending:YES withPredicate:uncompletedTasks groupBy:nil delegate:self inContext:[NSManagedObjectContext MR_defaultContext]];
for (Task *aTask in [self.fetchedResultsController fetchedObjects]) {
aTask.day = date;
}
NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];
[context MR_saveOnlySelfWithCompletion:^(BOOL success, NSError *error) {}];
This solves the problem, although only momentarily when I update the view, I get this error:
CoreData: error: Serious application error. Exception was caught
during Core Data change processing. This is usually a bug within an
observer of NSManagedObjectContextObjectsDidChangeNotification. ***
-[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects1 with userInfo (null)
I really know nothing about MagicalRecord, but ...
If you iterate through all items fetched by the FRC just to make an update to some of them you better:
1) perform the update in the background
2) fetch only the objects you need updated. example predicate:
NSPredicate* needUpdate = [NSPredicate predicateWithFormat:#"day < %# AND isArchived = %#",[NSDate date],#NO];
NSPredicate* p = [NSCompoundPredicate andPredicateWithSubpredicates:#[FRC_predicate,needUpdate]];
3. perform a single save after you updated all objects (or in batches, don't save one by one)
Guessing ...
You wrote code that make an update to an object (aTask.day = date;) in a completion block.
This might not actually persist the change the way you think it does.
make the update before you call the "save" procedure.
Several problems here.
First, I am not sure why you need mutableCopy. You are not modifying the members of the array yo are iterating through, so this should not be necessary. I am not even sure what the effect of making a managed object copy is in this case. Just leave it out.
Second, your boolean comparison is a bit confusing and can be written in a simpler way. This should be enough:
"... && aTalk.isArchived.boolValue"
Third, you are changing the object in the completion block after the save. So you are not saving the change, just perhaps the previous one made in some earlier iteration through the loop. Instead, just write the change and call save after the loop.
When saving, make sure the used context is the same as that of the fetched results controller.
I am experiencing issues with Core Data which I cannot resolve. I've learned about concurrency issues in Core Data the hard way, thus I am really careful and only perform any core data operations in performBlock: and performBlockAndWait: blocks.
Here goes my code:
/// Executes a fetch request with given parameters in context's block.
+ (NSArray *)executeFetchRequestWithEntityName:(NSString *)entityName
predicate:(NSPredicate *)predicate
fetchLimit:(NSUInteger)fetchLimit
sortDescriptor:(NSSortDescriptor *)sortDescriptor
inContext:(NSManagedObjectContext *)context{
NSCAssert(entityName.length > 0,
#"entityName parameter in executeFetchRequestWithEntityName:predicate:fetchLimit:sortDescriptor:inContext:\
is invalid");
__block NSArray * results = nil;
NSPredicate * newPredicate = [CWFCoreDataUtilities currentUserPredicateInContext:context];
if (predicate){
newPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:#[newPredicate, predicate]];
}
[context performBlockAndWait:^{
NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:entityName];
request.fetchLimit = fetchLimit;
request.predicate = newPredicate;
if (sortDescriptor) {
request.sortDescriptors = #[sortDescriptor];
}
NSError * error = nil;
results = [context executeFetchRequest:request error:&error];
if (error){
#throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:#"Fetch requests are required to succeed."
userInfo:#{#"error":error}];
NSLog(#"ERROR! %#", error);
}
NSCAssert(results != nil, #"Fetch requests must succeed");
}];
return results;
}
When I enter this method concurrently from two different threads and pass two different contexts, I result in a deadlock on this row: results = [context executeFetchRequest:request error:&error];
Which is interesting: it seems like both threads cannot acquire some lock on the Persistent Store Coordinator in order to execute a fetch request.
All of my contexts are NSPrivateQueueConcurrencyType.
I can't put my finger on, why am I locking the app and what should I do differently. My research on Stack Overflow gave me nothing, since most of the people fixed all the locks by dispatching the fetch requests on the MOC's queue, which I already do.
I will appreciate any information on this issue. Feel free to provide documentation links and other long reads: I am eager to learn more about all kind of concurrency problems and strategies.
Which is interesting: it seems like both threads cannot acquire some lock on the Persistent Store Coordinator in order to execute a fetch request.
The persistent store coordinator is a serial queue. If one context is accessing it another context will be blocked.
From Apple Docs:
Coordinators do the opposite of providing for concurrency—they serialize operations. If you want to use multiple threads for different write operations you use multiple coordinators. Note that if multiple threads work directly with a coordinator, they need to lock and unlock it explicitly.
If you need to perform multiple background fetch request concurrently you will need multiple persistent store coordinators.
Multiple coordinators will make your code only slightly more complex but should be avoided if possible. Do you really need to do multiple fetches at the same time? Could you do one larger fetch and then filter the in-memory results?
If you are interested in learning more about Core Data (and threading), the following website will be extremely helpful. I attended Matthew Morey's talk at the Atlanta CocoaConf 2013.
High Performance Core Data (http://highperformancecoredata.com)
The companion sample code to the website is available at: https://github.com/mmorey/MDMHPCoreData
All you need to do in your app, is to have a Singleton instance (somewhere) of the MDMPersistenceStack class.
As far as your problem/issue, even though the Apple documentation for NSManagedObjectContext class, does allow for code to be written the way that you have (with the Core Data operations performed on an NSManagedObjectContext instance in a block) and to some extent encourages that - I would like to point out that is not the only way.
Whenever I have fixed apps that have Core Data concurrency issues (locking), the simplest thing, in my opinion, is to create a private NSManagedObjectContext inside the thread or block that I want to execute Core Data operations on.
It may not be the most elegant approach, but it's never failed me. One is always guaranteed that the NSManagedObjectContext is created and executed in the same thread, because the NSManagedObjectContext is explicitly created.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
/*
Create NSManagedObjectContext
Concurrency of Managed Object Context should be set to NSPrivateQueueConcurrencyType
If you use the MDMPersistenceStack class this is handled for you.
*/
NSManagedObjectContext *managedObjectContext = ....
/*
Call the method that you have listed in your code.
Let's assume that this class method is in MyClass
Remove the block that you have in your method, as it's not needed
*/
[MyClass executeFetchRequestWithEntityName: ......] // rest of parameters
});
I did it this way. It fixed the issue for me. I was experiencing a lot of deadlocks too. See if it works for you.
+ (NSArray *)getRecordsForFetchRequest:(NSFetchRequest *)request inContext:(NSManagedObjectContext *)context
{
#try
{
__weak __block NSError *error = nil;
__block __weak NSArray *results = nil;
[context performBlockAndWait:^{
[context lock];
results = [context executeFetchRequest:request error:&error];
[context processPendingChanges];
[context unlock];
}];
[self handleErrors:error];
request = nil;
context = nil;
return results;
}
#catch (NSException *exception)
{
if([exception.description rangeOfString:#"Can only use -performBlockAndWait: on an NSManagedObjectContext that was created with a queue"].location!=NSNotFound)
{
NSError *error = nil;
[context lock];
__weak NSArray *results = [context executeFetchRequest:request error:&error];
[context processPendingChanges];
[context unlock];
[self handleErrors:error];
request = nil;
context = nil;
return results;
}
return nil;
}
}
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.
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.