What's the way to communicate a set of Core Data objects stored in the background to the main thread? - objective-c

Part of my iOS project polls a server for sets of objects, then converts and saves them to Core Data, to then update the UI with the results. The server tasks happens in a collection of NSOperation classes I call 'services' that operate in the background. If NSManagedObject and its ~Context were thread safe, I would have had the services call delegate methods on the main thread like this one:
- (void)service:(NSOperation *)service retrievedObjects:(NSArray *)objects;
Of course you can't pass around NSManagedObjects like this, so this delegate method is doomed. As far as I can see there are two solutions to get to the objects from the main thread. But I like neither of them, so I was hoping the great StackOverflow community could help me come up with a third.
I could perform an NSFetchRequest on the main thread to pull in the newly added or modified objects. The problem is that the Core Data store contains many more of these objects, so I have to add quite some verbosity to communicate the right set of objects. One way would be to add a property to the object like batchID, which I could then pass back to the delegate so it would know what to fetch. But adding data to the store to fix my concurrency limitations feels wrong.
I could also collect the newly added objects' objectID properties, put them in a list and send that list to the delegate method. The unfortunate thing though is that I have to populate the list after I save the context, which means I have to loop over the objects twice in the background before I have the correct list (first time is when parsing the server response). Then I still only have a list of objectIDs, which I have to individually reel in with existingObjectWithID:error: from the NSManagedObjectContext on the main thread. This just seems so cumbersome.
What piece of information am I missing? What's the third solution to bring a set of NSManagedObjects from a background thread to the main thread, without losing thread confinement?

epologee,
While you obviously have a solution you are happy with, let me suggest that you lose some valuable information, whether items are updated, deleted or inserted, with your mechanism. In my code, I just migrate the userInfo dictionary to the new MOC. Here is a general purpose routine to do so:
// Migrate a userInfo dictionary as defined by NSManagedObjectContextDidSaveNotification
// to the receiver context.
- (NSDictionary *) migrateUserInfo: (NSDictionary *) userInfo {
NSMutableDictionary *ui = [NSMutableDictionary dictionaryWithCapacity: userInfo.count];
NSSet * sourceSet = nil;
NSMutableSet *migratedSet = nil;
for (NSString *key in [userInfo allKeys]) {
sourceSet = [userInfo valueForKey: key];
migratedSet = [NSMutableSet setWithCapacity: sourceSet.count];
for (NSManagedObject *mo in sourceSet) {
[migratedSet addObject: [self.moc objectWithID: mo.objectID]];
}
[ui setValue: migratedSet forKey: key];
}
return ui;
} // -migrateUserInfo:
The above routine assumes it is a method of a class which has an #property NSManagedObjectContext *moc.
I hope you find the above useful.
Andrew

There's a section of the Core Data Programming Guide that addresses Concurrency with Core Data. In a nutshell, each thread should have its own managed object context and then use notifications to synchronize the contexts.

After a little experimentation, I decided to go for a slight alteration to my proposed method number 2. While performing background changes on the context, keep a score of the objects you want to delegate back to the main thread, say in an NSMutableArray *objectsOfInterest. We eventually want to get to the objectID keys of all the objects in this array, but because the objectID value changes when you save a context, we first have to perform that [context save:&error]. Right after the save, use the arrayFromObjectsAtKey: method from the NSArray category below to generate a list of objectID instances, like so:
NSArray *objectIDs = [objectsOfInterest arrayFromObjectsAtKey:#"objectID"];
That array you can pass back safely to the main thread via the delegate (do make sure your main thread context is updated with mergeChangesFromContextDidSaveNotification by listening to the NSManagedObjectContextDidSaveNotification). When you're ready to reel in the objects of the background operation, use the existingObjectsWithIDs:error: method from the category below to turn the array of objectID's back into a list of working NSManagedObjects.
Any suggestions to improve the conciseness or performance of these methods is appreciated.
#implementation NSArray (Concurrency)
- (NSArray *)arrayFromObjectsAtKey:(NSString *)key {
NSMutableArray *objectsAtKey = [NSMutableArray array];
for (id value in self) {
[objectsAtKey addObject:[value valueForKey:key]];
}
return objectsAtKey;
}
#end
#implementation NSManagedObjectContext (Concurrency)
- (NSArray *)existingObjectsWithIDs:(NSArray *)objectIDs error:(NSError **)error {
NSMutableArray *entities = [NSMutableArray array];
#try {
for (NSManagedObjectID *objectID in objectIDs) {
// existingObjectWithID might return nil if it can't find the objectID, but if you're not prepared for this,
// don't use this method but write your own.
[entities addObject:[self existingObjectWithID:objectID error:error]];
}
}
#catch (NSException *exception) {
return nil;
}
return entities;
}
#end

Related

Adding managedObjectContext objects with an NSArrayController

I have this application that is using core data and an NSArrayController to manage some objects in a table. I have the code below to pick up some objects on a directory. My questions is about the section below labeled "Handle Files". I create a new Video object using the url, I copy the metadata attributes using a custom function I wrote. The object is now inserted in the managedObjectContext. My question is, since I have my NSArrayController bound to my managedObjectContext, why do I have to still do [self addObject:newVideo] to have the object shown on my table? Is there a way to force the array controller to pull the object from the managedObjectContext without having to manually add it? It will be a hassle having to be updating both things every time I add or remove an object.
for (NSURL *url in _dirEnumerator) {
NSNumber *_isDirectory = nil;
[url getResourceValue:&_isDirectory forKey:NSURLIsDirectoryKey error:NULL];
if (![_isDirectory boolValue]) {
if (([_mediaTypes containsObject:[[url pathExtension]uppercaseString]])) {
// Handle the files
Video *newVideo = [NSEntityDescription insertNewObjectForEntityForName:#"Video" inManagedObjectContext:_managedObjectContext];
[newVideo copyAttributesFrom:url];
[self addObject:newVideo];
NSLog(#"Inserting video: %#",[newVideo valueForKey:#"name"]);
}
}
}
Well, I had my bindings all wrong an the array controller was not feeding my table correctly. You cannot sneak objects behind the array controller, if you implement the array controller you must let him do his job and that includes adding and removing objects. He will take care of letting the tableview know when things have changed.

Check for completion within NSMutableArray of custom objects populated from NSURLConnection

First off, a big thank you to the SO community. I have learned a great deal. However, I am still an extreme novice w/re to Objective C and thus have a question. Apologies in advance if this is an ignorant question.
I have subclassed NSURLConnection to fetch my custom objects (myObject) from my web API. Each object requires 2 calls to the API for completion. The first call is to grab an id property from a list of my Objects. The second call is the to use that id to construct a different URL and populate the rest of the myObject properties. All is working well but I have a question as to the correctness of my approach for reloading a tableViewsection based on a completion of all of themyObjectobjects within anNSMutableArray`.
Here is the method I call after successfully instantiating and fetching all of the incomplete myObjects and adding them to an NSMutableArray. messageStringis a property of myObject that is only available/set on the second network call for each of the instances of myObject. Thus, I thought I would use it to check for completeness. arrayOfMyObjects is mutable and contains all of the incomplete myObjects. MyStoreClass is just that. A store that handles the creation of the subclassed NSURLConnections.
- (void)fetchDetails {
void (^completionBlock)(myObject *obj, NSError *err, int statusCode) = ^(myObject *obj, NSError *err, int statusCode) {
if (!err && statusCode == 200) {
NSArray *completionCheckArray = [arrayOfMyObjects filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"messageString = %#", [NSNull null]]];
if ([completionCheckArray count] == 0) {
[[self tableView] reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
}
} else if (!err && statusCode != 200) {
[self statusCodeError:statusCode];
} else {
[self generalError:err];
}
};
for (myObject *mobj in arrayOfMyObjects) {
[[MyStoreClass sharedStore] fetchDetails:mobj withCompletion:completionBlock];
}
}
While this works, it seems inefficient to me to have to create an array through the completion block for every single one of myObjects. If so, what would be an alternative approach to checking completion of all of myObjects?
A possible and feasible approach would be to go wit KVO (Key Value Observing). Check out the appropriate Key-Value Observing Programming Guide.
For example: If you know how many objects (i.e. object count) you're expecting from a given service call,
you could hook up an observer on the array object holding your objects and be notified whenever an element is added etc.
See Observing an NSMutableArray for insertion/removal.

NSArrayController and the exception "CoreData could not fulfill a fault"

I have a list of items, instances of a Item class saved in a Core Data model.
This items are showed in a NSTableView using an NSArrayController and Cocoa Bindings. It works very well.
However, when I remove some items using these instructions:
// Removes selected items
for (Item *item in self.itemsArrayController.selectedObjects) {
[self.managedObjectContext deleteObject:item];
}
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
[[NSApplication sharedApplication] presentError:error];
}
after some times, I obtain the exception CoreData could not fulfill a fault.
I read all the documentation that I found (including the Troubleshooting Core Data), but I did not find anything useful.
I'm using the new ARC (Automatic Reference Counting), so I'm pretty sure I'm not trying to access, after the save on the managed object context, the managed object which was deleted.
UPDATE: My app is single thread, so I'm not trying to access the managedObjectContext from multiple threads.
Are you accessing the same managedObjectContext on multiple threads? This feels like a race condition where you delete an object that the MOC expects to be around. A given NSManagedObjectContext can only be accessed from a single thread.
You are enumerating the selected items of the array controller, and deleting the objects while enumerating. Try:
NSArray *selectedObjects = [[self.itemsArrayController selectedObjects] copy];
for (Item *item in selectedObjects) {
[self.managedObjectContext deleteObject:item];
}
[selectedObjects release];

How do I persist data managed by NSArrayController without Core Data or NSKeyedArchiver?

I hope you'll excuse the seemingly broad nature of this question, but it gets quite specific.
I'm building a document-based Cocoa application that works like most others except that I am using SQLCipher for my data store (a variant of SQLite), because you don't get to set your own persistent data store in Core Data, and also I really need to use this one.
In my document sub-class, I've got an NSMutableArray property named categories. In the document nib I've got an NSArrayController bound to categories, and I've got an NSCollectionView bound to the array controller.
Each of my model objects in the array (each is a Category) is bound to a record in the underlying data store, so when some property of a Category changes, I want to call [category save], when a Category is added to the set, I want to call, again, [category save], and finally, when a category is removed, [category destroy].
I've wired up a partial solution, but it falls apart on the removal requirement, and everything about it seems to me as though I'm barking up the wrong tree. Anyway, here's what's going on:
Once the document and nib are all loaded up, I start observing the categories property, and assign it some data:
[self addObserver:self
forKeyPath:#"categories"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:MyCategoriesContext];
self.categories = [Category getCategories];
I've implemented the observation method in such a way as that I am informed of changes so that the document can respond and update the data store.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSNumber *changeKind = (NSNumber *)[change objectForKey:#"NSKeyValueChangeKind"];
if (context == MyCategoriesContext)
{
switch ([changeKind intValue])
{
case NSKeyValueChangeInsertion:
{
Category *c = (Category *)[change objectForKey:NSKeyValueChangeNewKey];
NSLog(#"saving new category: %#", c);
[c save];
break;
}
case NSKeyValueChangeRemoval:
{
Category *c = (Category *)[change objectForKey:NSKeyValueChangeOldKey];
NSLog(#"deleting removed category: %#", c);
[c destroy];
break;
}
case NSKeyValueChangeReplacement:
{
// not a scenario we're interested in right now...
NSLog(#"category replaced with: %#", (Category *)[change objectForKey:NSKeyValueChangeNewKey]);
break;
}
default: // gets hit when categories is set directly to a new array
{
NSLog(#"categories changed, observing each");
NSMutableArray *categories = (NSMutableArray *)[object valueForKey:keyPath];
NSIndexSet *allIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [categories count])];
[self observeCategoriesAtIndexes:allIndexes];
break;
}
}
}
else if (context == MyCategoryContext)
{
NSLog(#"saving category for change to %#", keyPath);
[(Category *)object save];
}
else
{
// pass it on to NSObject/super since we're not interested
NSLog(#"ignoring change to %#:#%#", object, keyPath);
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
As you can see from that listing (and as you might already be aware), it's not enough to observe the categories property, I need to observe each individual category so that the document is notified when it's attributes have been changed (like the name) so that I can save that change immediately:
- (void)observeCategoriesAtIndexes:(NSIndexSet *)indexes {
[categories addObserver:self
toObjectsAtIndexes:indexes
forKeyPath:#"dirty"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:MyCategoryContext];
}
This looks to me like a big kludge, and I suspect I'm working against Cocoa here, but for the most part it works.
Except for removal. When you add a button to your interface, and assign it to the array controller's remove: action, it will properly remove the category from the categories property on my document.
In doing so, the category is deallocated while it is still under observation:
2010-09-03 13:51:14.289 MyApp[7207:a0f] An instance 0x52db80 of class Category was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x52e100> (
<NSKeyValueObservance 0x2f1a480: Observer: 0x2f0fa00, Key path: dirty, Options: <New: YES, Old: YES, Prior: NO> Context: 0x1a67b4, Property: 0x2f1a3d0>
...
)
In addition, because the object has been deallocated before I've been notified, I don't have the opportunity to call [category destroy] from my observer.
How is one supposed to properly integrate with NSArrayController to persist changes to the data model pre-Core Data? How would one work-around the remove problem here (or is this the wrong approach entirely?)
Thanks in advance for any advice!
It would seem, based on some initial hacking, that subclassing NSArrayController is the way to go here. Over-riding the various insertObject(s) and removeObject(s) methods in that API gives me the perfect place to add in this logic for messing with the data model.
And from there I can also begin to observe the individual items in the content array for changes, etc, stop observation before destroying/deallocating them, etc, and let the parent class handle the rest.
Thanks for this solution is due to Bill Garrison who suggested it on the cocoa-unbound list.
I would observe changes to categories list, and when the list changes, store the array of categories away in a secondary NSArray, 'known categories', using mutableCopy. Next time the list changes, compare that 'known' list to the new list; you can tell which categories are missing, which are new, etc. For each removed category, stop observing it and release it.
Then take a new mutable copy for the 'known' list of categories, ready for the next call.
Since you have an additional array holding the categories, they aren't released before you're ready.

Cocoa Threadsafe Mutable Collection Access

I'm creating a KVC/KVO-compliant mutable array on one of my objects the recommended way:
#interface Factory {
NSMutableArray *widgets;
}
- (NSArray *)widgets;
- (void)insertObject:(id)obj inWidgetsAtIndex:(NSUInteger)idx;
- (void)removeObjectFromWidgetsAtIndex:(NSUInteger)idx;
#end
Clearly this is a tricky thread-safety issue. In the insert and remove methods I'm locking around array access to prevent concurrent modification, as recommended.
My question is, what is the proper way to implement the widgets accessor? Here's my implementation:
- (NSArray *)widgets {
[widgetLock lock];
NSArray *a = [[widgets copy] autorelease];
[widgetLock unlock];
return a;
}
Is it threadsafe?
Your widgets accessor should be fine, although you should be aware that none of the objects in that array are locked. So, you could run into problems trying to concurrently run code like
[[[myFactory widgets] objectAtIndex:7] setName:#"mildred"];
and
[myTextField setStringValue:[[[myFactory widgets] objectAtIndex:7] name]]; // mildred? or something else?
Since the objects in your array are not locked, you could run into race conditions or readers/writers-type problems. Isn't multithreading a joy?
On a different note, for KVC-compliance, I'd advise implementing objectInWidgetsAtIndex: and countOfWidgets instead of a widgets accessor. Remember, KVC models relationships, not array properties. So you would call something like [myFactory mutableArrayValueForKey:#"widgets"] to get an array representing the widgets property.
Rather than creating your own lock, you could also use the locking built into the language:
i.e
- (NSArray *)widgets {
#synchronized(widgets)
{
NSArray *a = [[widgets copy] autorelease];
return a;
}
}
and use similar locking in all other methods that access widgets. (The parameter widgets passed into #synchronized refers to the instance variable, not the method.)
alex's comment about access to contained objects still apply.
You will need locking on all reading and writing methods. If your insert and remove are also locking (like you said) then the accessor method should be fine like that.