Emptying a Core Data NSSet (multiple relationships) - objective-c

If I need to programmatically empty a NSSet automatically created by Core Data (multiple relationships), what should I do ? Something like this ?
[self willChangeValueForKey:#"MyRelationship"];
[[self MyRelationship] release];
[self MyRelationship] = [NSSet alloc] init];
[self didChangeValueForKey:#"MyRelationship"];
Not sure it is correct at all...
thanks

[[self mutableSetValueForKey:#"MyRelationship"] removeAllObjects];
For some reason, I can never get the "cascade" delete rule to work, so when I want the objects deleted as well I have to iterate over the set and call [self.managedObjectContext deleteObject:obj] or else I'll get validation errors, if the relationship is defined as required.

Patrick,
Relations are, unsurprisingly, special in Core Data. They provide some specialized methods to remove objects from those relations. Rather than trying to override the accessors, you should use those methods. As in this snippet:
[self removeMyRelationship: self.myRelationship];
I also think your should remove your overridden accessor methods.
I have no insight into your deletion problem. I recommend that you just iterate over the group and delete the objects. I think it is important that your enumerator be a copy of your relationship. As in the following ARC code:
for (Relation *r in [self.myRelationship copy]) {
[moc deleteObject: r];
}
Andrew

Related

Is mutableCopy call returns an array with a copy of the managed object context?

In a tutorial i'm learning CoreData from the preform something like this to fetch the collection of notes in a notes app:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Note"];
self.notes = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
So first of all, notes is an NSMutableArray, so tell me if I understand it right:
they creating an NSManagedObjectContext object to hold the context.
they create a request to get the "Note" entity from the database file.
they use the managedObjectContext to call executeFetchRequest with the requested request (which is fetchRequest). Now here is the part I dont completely understand (probably some of the previous ones as well, please correct me if I didn't):
The type of object i'm getting from this call [managedObjectContext executeFetchRequest:fetchRequest error:nil]; is an NSSet? and by calling mutableCopy i'm returning an array?
Thanks
[managedObjectContext executeFetchRequest:fetchRequest error:nil]
returns an (immutable) NSArray, and mutableCopy creates a - well - mutable copy
of that array. It does not copy the managed objects in the array or the context.
It just allows you to modify self.notes, e.g. to add, delete or rearrange the objects
in the mutable array.
Remark: If you display objects from a Core Data fetch request in a table view
then you should have a look at NSFetchedResultsController. It might look a bit more
complicated at the beginning, but allows (for example) automatic updates of the
table view if objects are inserted, deleted or modified.

Fastest way to find an NSManagedObject in an NSSet

I have 2 NSSets with NSManagedObjects, the objects for each set are fetched in different threads, meaning some have a matching objectID, but the objects themselves are different. Now I want to remove managedObjects in one set from the other.
NSSet* oldObjects;
NSMutableSet* currentObjects;
// I want to remove the managedObjects in oldObjects from currentObjects, all objects in oldObjects are also in currentObjects
// This doesn't work, since the objects don't match
[currentObjects removeObjectsInArray:[oldObjects allObjects]];
// But strangely enough, this doesn't add any objects to currentObjects, but if the objects don't match, shouldn't it?
//[currentObjects addObjectsFromArray:[oldObjects allObjects]];
// This does work for me but this code is running on the main thread and I can see this becoming rather slow for large data sets
NSArray* oldObjectIDs = [[oldObjects allObjects] valueForKey:#"objectID"];
[currentObjects filterUsingPredicate:[NSPredicate predicateWithFormat:#"NOT (objectID IN %#)", oldObjectIDs]];
Is there a faster way I can filter these out? Would fast enumeration be faster even in this case?
Sorry for getting back to with such a delay.
I re-read your question, and now, that I've understood the setting completly I might have a solution for you.
This is not tested, but try something like this:
//Since the current objects set has registered its objects in the current context
//lets use that registration to see which of them is contained in the old object set
NSMutableSet* oldRegisteredSet = [NSMutableSet new];
for (NSManagedObject* o in oldObjects) {
NSManagedObject* regObject = [context objectRegisteredForID:[o objectID]];
if (regObject) {
//You could do here instead: [currentObjects removeObject:regObject];
//You should optimize here after testing performance
[oldRegisteredSet addObject:regObject];
}
}
[currentObjects minusSet:oldRegisteredSet];

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

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

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];

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.