I'm trying to use sub context so I can use the subclass of NSManagedObject as a normal class to just hold information. Like a scratch pad...
Target: NSManagedObject
+ (Target*)initWithCoreData{
NSManagedObjectContext * context = [[CoreDataWrapper sharedInstance] childManagedObjectContext];
NSEntityDescription * ent= [NSEntityDescription insertNewObjectForEntityForName:#"Target"
inManagedObjectContext:context];
Target * target = [[Target alloc] initWithEntity:ent insertIntoManagedObjectContext:context];
return target;
}
pragma mark - Core Data
/**
* Path for Core Data Store Files
*
* #return Path for Core Data Store Files
*/
- (NSURL *)applicationDocumentsDirectory {
// The directory the application uses to store the Core Data store file. This code uses a directory named "com.aigptc.MSA" in the application's documents directory.
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
/**
* Create ManagedObjectModel if it doesn't exist already
*
* #return ManagedObjectModel used with CoreData
*/
- (NSManagedObjectModel *)managedObjectModel {
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"MSA" withExtension:#"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
/**
* Return the NSPersistenceStoreCoordinator used in CoreData if it does'nt exist
* with the options: dictionaryWithObjectsAndKeys and NSMigratePersistentStoresAutomaticallyOption set to YES
*
* #return PersistanceStoreCoordinator used by CoreData
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it.
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Create the coordinator and store
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"MSA.sqlite"];
NSError *error = nil;
NSString *failureReason = #"There was an error creating or loading the application's saved data.";
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = #"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:#"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
/**
* Creates NSManagedObjectContext for CoreData
*
* #return NSManagedObjectContext for CoreData
*/
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
/**
* Creates NSManagedObjectContext for CoreData
*
* #return NSManagedObjectContext for CoreData
*/
- (NSManagedObjectContext *)childManagedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_childManagedObjectContext != nil) {
return _childManagedObjectContext;
}
_childManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
NSManagedObjectContext * cont = [self managedObjectContext];
[_childManagedObjectContext setParentContext:cont];
return _childManagedObjectContext;
}
The program crashes in the line:
Target * target = [[Target alloc] initWithEntity:ent insertIntoManagedObjectContext:context];
with the following error:
2015-08-13 15:52:51.720 MSA[3262:71338] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Target managedObjectModel]: unrecognized selector sent to instance 0x7ffa28e506a0'
*** First throw call stack:
(
0 CoreFoundation 0x000000010c751c65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010c02ebb7 objc_exception_throw + 45
2 CoreFoundation 0x000000010c7590ad -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x000000010c6af13c ___forwarding___ + 988
4 CoreFoundation 0x000000010c6aecd8 _CF_forwarding_prep_0 + 120
5 CoreData 0x000000010c27fea3 -[NSManagedObject initWithEntity:insertIntoManagedObjectContext:] + 83
6 MSA 0x00000001090db227 +[Target initWithCoreData] + 183
7 MSA 0x00000001090db32a +[Target setTarget:new:targetISO:lastUpdate:] + 154
8 MSA 0x00000001090c53bb -[MSASetTargetView applyTargetButton:] + 651
9 UIKit 0x0000000109bfbd62 -[UIApplication sendAction:to:from:forEvent:] + 75
10 UIKit 0x0000000109d0d50a -[UIControl _sendActionsForEvents:withEvent:] + 467
11 UIKit 0x0000000109d0c499 -[UIControl touchesBegan:withEvent:] + 224
12 UIKit 0x0000000109c487be -[UIWindow _sendTouchesForEvent:] + 325
13 UIKit 0x0000000109c49282 -[UIWindow sendEvent:] + 682
14 UIKit 0x0000000109c0f541 -[UIApplication sendEvent:] + 246
15 UIKit 0x0000000109c1ccdc _UIApplicationHandleEventFromQueueEvent + 18265
16 UIKit 0x0000000109bf759c _UIApplicationHandleEventQueue + 2066
17 CoreFoundation 0x000000010c685431 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
18 CoreFoundation 0x000000010c67b2fd __CFRunLoopDoSources0 + 269
19 CoreFoundation 0x000000010c67a934 __CFRunLoopRun + 868
20 CoreFoundation 0x000000010c67a366 CFRunLoopRunSpecific + 470
21 GraphicsServices 0x000000010f581a3e GSEventRunModal + 161
22 UIKit 0x0000000109bfa8c0 UIApplicationMain + 1282
23 MSA 0x00000001090e798f main + 111
24 libdyld.dylib 0x000000010d6da145 start + 1
25 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
I have no idea what I'm doing wrong... Everything I find on the net is related to the creation of the context in it self.
Many thanks!
I found the answer! I'm creating the Target wrong, it should be:
+ (Target*)initWithCoreData{
NSManagedObjectContext * context = [[CoreDataWrapper sharedInstance] childManagedObjectContext];
Target * target = [NSEntityDescription insertNewObjectForEntityForName:#"Target"
inManagedObjectContext:context];
return target;
}
Related
I have a table view backed up by core data with NSFetchedResultsController instance.
This table has a search bar that displays filtered data. The search bar has a separate NSFetchedResultsController (FRC from now) instance.
So far so good, the data is fetched and shown as expected in the table view and also shown correctly in the search bar's table view (when searching for data).
My problem is that if I try to delete a cell in the search bar's table view then I get a coredata exception :
error: Serious application error. An exception was caught from the
delegate of NSFetchedResultsController during a call to
-controllerDidChangeContent:. attempt to delete row 0 from section 0 which only contains 0 rows before the update with userInfo (null)
Further examination shows that the FRC's controllerWillChangeContent method is called twice on one cell deletion!. This causes deleteRowsAtIndexPaths to be called twice for the same cell (thus the coredata exception).
Playing with it some more I have found out that this problem happens from the first time the search bar's table view is shown after a search. (Even when I go back and delete the cell in the regular table view the problem occurs)
I've made sure that the deleteRowsAtIndexPaths method is called only once in the code (in the FRC's didChangeObject delegate method).
Now I am not sure which code to show so I won't just spill it all. let me know which one you need to look at if you have an idea on what the problem is.
This is the code where the table is instructed to delete a row :
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
DLog(#"didChangeObject type %lu with object %#", (unsigned long)type, anObject);
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[self.tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath forTable:self.tableView];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:[NSArray
arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray
arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
This method is called when I save the context after I delete the object for the selected cell.
The method where I save the context:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
[_managedObjectContext deleteObject:[_notesFetcher objectAtIndexPath:indexPath]];
NSError *error;
if (![_managedObjectContext save:&error]) {
DLog(#"Failed deleting object : %#", [error localizedDescription]);
}
}
}
And for some reason this method is called once but invokes didChangeObject twice with the same change type (2-Delete) for the same cell.
UPDATE
When I log for the stack trace in the method didChangeObject I get this:
[UITableView animateDeletionOfRowWithCell:] + 107 12 UIKit
0x01316a35 -[UITableViewCell _swipeDeleteButtonPushed] + 70 13
libobjc.A.dylib 0x02346874 -[NSObject
performSelector:withObject:withObject:] + 77 14 UIKit
0x010a8c8c -[UIApplication sendAction:to:from:forEvent:] + 108 15
UIKit 0x010a8c18 -[UIApplication
sendAction:toTarget:fromSender:forEvent:] + 61 16 UIKit
0x011a06d9 -[UIControl sendAction:to:forEvent:] + 66 17 UIKit
0x011a0a9c -[UIControl _sendActionsForEvents:withEvent:] + 577 18
UIKit 0x0119fd4b -[UIControl
touchesEnded:withEvent:] + 641 19 UIKit
0x0141ad7f _UIGestureRecognizerUpdate + 7166 20 UIKit
0x010e5d4a -[UIWindow _sendGesturesForEvent:] + 1291 21 UIKit
0x010e6c6a -[UIWindow sendEvent:] + 1030 22 UIKit
0x010baa36 -[UIApplication sendEvent:] + 242 23 UIKit
0x010a4d9f _UIApplicationHandleEventQueue + 11421
As you can see the table's swipeDeleteButtonPushed method is called twice on one delete operation as I described above. How can this be triggered twice when I only pushed the delete button once ? any ideas ?
You need to always check which controller is calling your delegate method and act accordingly. Similarly you need to check which tableView is calling your delegate method. Assuming that your viewController is the delegate for both the search fetchedresultscontroller and the normal fetchedresultscontroller as well as the search tableView and the normal tableView.
So you need something,like
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
If (tableView == searchTableView) {
// do stuff to searchTableView
} else {
// do stuff to normalTableView
}
}
Or in the fetchedResultsController delegate methods (and I am pretty sure this will be where you are getting two calls acting on the same tableView) something like
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
If (controller == searchFetchedResultsController) {
// do stuff to searchTableview
} else {
// do stuff to normaltableview
}
}
What follows is everything I can possibly think of as background to try and identify the source of the error. I will, of course, provide any other information that may be helpful. Thanks in advance for your help.
I am new to Core Data, and I have an entity named GroceryItem. It has a to-many relationship named hasLocations, which I am trying to query with the following code:
itemObject = (GroceryItem *)[GroceryItem itemNameToObject:itemName];
if (itemObject != nil)
{
NSLog(#"%#", [NSString stringWithFormat:#"initAndFillItemLocationsTable got an item named %#", itemObject.name]);
}
if (itemLocations != nil)
{
[itemLocations removeAllObjects];
}
if (itemObject != nil)
{
NSLog(#"Before mutable set assignment");
NSMutableSet *mutableLocationsSet = [itemObject mutableSetValueForKeyPath:#"hasLocations"];
I see the "Before mutable set assignment" message in the output, followed by
2013-01-23 03:27:14.898 Grocery Manager[6431:11603] initAndFillItemLocationsTable got an item named Cream Cheese
2013-01-23 03:27:14.899 Grocery Manager[6431:11603] Before mutable set assignment
2013-01-23 03:27:14.901 Grocery Manager[6431:11603] * Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: the entity GroceryItems is not key value coding-compliant for the key "hasLocations".'
* First throw call stack:
(0x15eb012 0x1410e7e 0x1673fb1 0x11c304 0xe298db 0xb8374 0xe298db 0xec3180 0xec31de 0x17450 0xaa5c 0x439817 0x439882 0x439b2a 0x450ef5 0x450fdb 0x451286 0x451381 0x451eab 0x4524a3 0x452098 0x7adda3 0x79fad9 0x79fb54 0x407899 0x407b3d 0xe0ee83 0x15aa376 0x15a9e06 0x1591a82 0x1590f44 0x1590e1b 0x24377e3 0x2437668 0x35865c 0x27bd 0x26e5)
libc++abi.dylib: terminate called throwing an exception
The contents of the GroceryItem class definition are:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface GroceryItem : NSManagedObject
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSSet *hasLocations;
#property (nonatomic, retain) NSSet *containedInIngredients;
#end
#interface GroceryItem (CoreDataGeneratedAccessors)
- (void)addHasLocationsObject:(NSManagedObject *)value;
- (void)removeHasLocationsObject:(NSManagedObject *)value;
- (void)addHasLocations:(NSSet *)values;
- (void)removeHasLocations:(NSSet *)values;
- (void)addContainedInIngredientsObject:(NSManagedObject *)value;
- (void)removeContainedInIngredientsObject:(NSManagedObject *)value;
- (void)addContainedInIngredients:(NSSet *)values;
- (void)removeContainedInIngredients:(NSSet *)values;
+(GroceryItem *)itemNameToObject:(NSString *)itemName;
I have verified in the database editor that the class GroceryItem is assigned to the entity GroceryItem.
I read the Key-Value Coding Programming Guide and implemented the following code in GroceryItem.m:
- (void)addHasLocationsObject:(NSManagedObject *)value
{
[self.hasLocations setByAddingObject:value];
}
- (void)removeHasLocationsObject:(NSManagedObject *)value
{
NSMutableSet *mutable = [NSMutableSet setWithSet:self.hasLocations];
[mutable removeObject:value];
self.hasLocations = mutable;
}
- (void)addHasLocations:(NSSet *)values
{
[self.hasLocations setByAddingObjectsFromSet:values];
}
- (void)removeHasLocations:(NSSet *)values
{
NSMutableSet *mutable = [NSMutableSet setWithSet:self.hasLocations];
for (id obj in [mutable allObjects])
{
if ([values containsObject:obj])
{
[mutable removeObject: obj];
}
}
self.hasLocations = mutable;
}
- (NSUInteger)countOfHasLocations
{
return [self.hasLocations count];
}
- (NSEnumerator *)enumeratorOfHasLocations
{
return [self.hasLocations objectEnumerator];
}
- (GroceryLocation *)memberOfHasLocations:(GroceryLocation *)anObject
{
return [self.hasLocations member:anObject];
}
The static itemNameToObject function looks like this:
+(GroceryItem *)itemNameToObject:(NSString *)itemName
{
GroceryItem *groceryItem;
groceryItem = nil;
if (itemName != nil)
{
GroceryManagerAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"GroceryItems" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"name == %#", itemName]];
NSError *error;
NSArray *groceryItemObjects = [context executeFetchRequest:request error:&error];
NSInteger countOfGroceryItems = [groceryItemObjects count];
if (countOfGroceryItems == 1)
{
groceryItem = (GroceryItem *)[groceryItemObjects objectAtIndex:0];
}
}
return groceryItem;
}
(From the comments:) The crash is caused by the fact that the entity is declared as "GroceryItems", but "hasLocations" is a property of the class "GroceryItem". The fetch request returns an array of "GroceryItems" objects, which do not respond to the "hasLocations" method.
Also it is not normally necessary to implement the Core Data accessor methods, they are dynamically created at runtime.
I'm trying to implement a simple table, where I can put an object parent into a user definable order. I bound a parentTableView to a parentArrayController and set the sortDescriptor to the key "order" the table shows the parents entities in the order of the "order" key and everything works fine.
If I try to drag and drop a row to a new position a crash occurs at the line, where the dragged objects are inserted or added to the new position:
[self.parentArrayController insertObjects:draggedObjects atArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row, [draggedObjects count])]];
or
[self.parentArrayController addObjects:draggedObjects];
What am I doing wrong?
Here is the code of the tableviewdelegate:
#implementation parentTableViewDelegate
#synthesize parentTableView;
#synthesize parentArrayController;
-(id)init{
if (self = [super init]) {
self.parentSortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES]];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
// user interface preparation code
[parentTableView registerForDraggedTypes:[NSArray arrayWithObject:[self.parentArrayController entityName]]];
[parentTableView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES];
}
#pragma mark -
#pragma mark NSTableViewDataSource
- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pasteboard {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
[pasteboard declareTypes:[NSArray arrayWithObject:[self.parentArrayController entityName]] owner:self];
[pasteboard setData:data forType:[self.parentArrayController entityName]];
return YES;
}
- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op {
if ([info draggingSource] == parentTableView) {
if (op == NSTableViewDropOn)
[tv setDropRow:row dropOperation:NSTableViewDropAbove];
return NSDragOperationMove;
} else {
return NSDragOperationNone;
}
}
- (BOOL)tableView:(NSTableView *)aTableView
acceptDrop:(id <NSDraggingInfo>)info
row:(int)row
dropOperation:(NSTableViewDropOperation)operation {
// Get object indexes from paste board
NSData *data = [[info draggingPasteboard] dataForType:[self.parentArrayController entityName]];
NSIndexSet *rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if (!rowIndexes) return NO;
if (aTableView == self.parentTableView) {
if ([info draggingSource] == self.parentTableView) {
NSMutableArray *draggedObjects = [NSMutableArray array];
[draggedObjects addObject:[[self.parentArrayController arrangedObjects] objectsAtIndexes:rowIndexes]];
[self.parentArrayController removeObjectsAtArrangedObjectIndexes:rowIndexes];
// Insert dragged objects at row
if (row < [[self.parentArrayController arrangedObjects]count]) {
[self.parentArrayController insertObjects:draggedObjects atArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row, [draggedObjects count])]];
}
else {
[self.parentArrayController addObjects:draggedObjects];
}
// Re-order objects
int i;
for (i = 0; i < [[self.parentArrayController arrangedObjects] count]; i++) {
[[[self.parentArrayController arrangedObjects] objectAtIndex:i] setValue:[NSNumber numberWithInt:i] forKey:#"order"];
}
[self.parentArrayController rearrangeObjects];
return YES;
}
}
return NO;
}
#end
And here is the crash:
[<__NSArrayI 0x101b197b0> addObserver:forKeyPath:options:context:] is not supported. Key path: order
2012-10-22 08:52:40.630 OrderedSetTest[29950:303] (
0 CoreFoundation 0x00007fff89e570a6 __exceptionPreprocess + 198
1 libobjc.A.dylib 0x00007fff8dfa33f0 objc_exception_throw + 43
2 CoreFoundation 0x00007fff89e56e7c +[NSException raise:format:] + 204
3 Foundation 0x00007fff8f0f7287 -[NSArray(NSKeyValueObserverRegistration) addObserver:forKeyPath:options:context:] + 76
4 AppKit 0x00007fff8c622eea -[_NSModelObservingTracker _registerOrUnregister:observerNotificationsForModelObject:] + 200
5 AppKit 0x00007fff8c622c18 -[_NSModelObservingTracker startObservingModelObjectAtReferenceIndex:] + 177
6 AppKit 0x00007fff8c70df7d -[NSArrayController _insertObject:atArrangedObjectIndex:objectHandler:] + 593
7 OrderedSetTest 0x0000000100004bbf -[parentTableViewDelegate tableView:acceptDrop:row:dropOperation:] + 1263
8 AppKit 0x00007fff8c9baa0e -[NSTableView performDragOperation:] + 215
9 AppKit 0x00007fff8c8079bf NSCoreDragReceiveMessageProc + 1651
10 HIServices 0x00007fff8b82046a DoMultipartDropMessage + 301
11 HIServices 0x00007fff8b820135 DoDropMessage + 49
12 HIServices 0x00007fff8b8200d7 SendDropMessage + 41
13 HIServices 0x00007fff8b8237c8 DragInApplication + 654
14 HIServices 0x00007fff8b822c69 CoreDragStartDragging + 519
15 AppKit 0x00007fff8c808190 -[NSCoreDragManager _dragUntilMouseUp:accepted:] + 881
16 AppKit 0x00007fff8c8094ba -[NSCoreDragManager dragImage:fromWindow:at:offset:event:pasteboard:source:slideBack:] + 1455
17 AppKit 0x00007fff8cab9040 -[NSWindow(NSDrag) dragImage:at:offset:event:pasteboard:source:slideBack:] + 133
18 AppKit 0x00007fff8c9b91e0 -[NSTableView _doImageDragUsingRowsWithIndexes:event:pasteboard:source:slideBack:startRow:] + 570
19 AppKit 0x00007fff8c9ba11d -[NSTableView _performClassicDragOfIndexes:hitRow:event:] + 358
20 AppKit 0x00007fff8c9ba391 -[NSTableView _performDragFromMouseDown:] + 509
21 AppKit 0x00007fff8c9ac4b2 -[NSTableView mouseDown:] + 707
22 AppKit 0x00007fff8c59b60e -[NSWindow sendEvent:] + 6853
23 AppKit 0x00007fff8c597744 -[NSApplication sendEvent:] + 5761
24 AppKit 0x00007fff8c4ad2fa -[NSApplication run] + 636
25 AppKit 0x00007fff8c451cb6 NSApplicationMain + 869
26 OrderedSetTest 0x0000000100001da2 main + 34
27 OrderedSetTest 0x0000000100001d74 start + 52
The problem was, that the arrangedObjects of the arrayController are proxy objects and I had to retrieve the managed objects from the context
the working code follows here:
#import "GFParentTableViewDelegate.h"
#implementation GFParentTableViewDelegate
-(id)init{
if (self = [super init]) {
self.parentSortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES]];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self.parentTableView registerForDraggedTypes:[NSArray arrayWithObject:[self.parentArrayController entityName]]];
[self.parentTableView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES];
}
#pragma mark -
#pragma mark NSTableViewDataSource
- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard{
// Get array controller
NSDictionary *bindingInfo = [tableView infoForBinding:NSContentBinding];
NSArrayController *arrayController = [bindingInfo valueForKey:NSObservedObjectKey];
if (!arrayController) {
return NO;
}
[pboard declareTypes:[NSArray arrayWithObject:[arrayController entityName]] owner:self];
// Collect URI representation of managed objects
NSMutableArray *objectURIs = [NSMutableArray array];
for (id objProxy in [[arrayController arrangedObjects] objectsAtIndexes:rowIndexes]) {
[objectURIs addObject:[[objProxy objectID] URIRepresentation]];
}
// Set them to paste board
[pboard setData:[NSArchiver archivedDataWithRootObject:objectURIs] forType:[arrayController entityName]];
NSLog(#"Wrote %ld objects of type: %# to Pasteboard",[objectURIs count],[arrayController entityName]);
return YES;
}
- (NSDragOperation)tableView:(NSTableView*)tableView
validateDrop:(id <NSDraggingInfo>)info
proposedRow:(NSInteger)row
proposedDropOperation:(NSTableViewDropOperation)dropOperation {
if ([info draggingSource] == self.parentTableView) {
if (dropOperation == NSTableViewDropOn){
[tableView setDropRow:row dropOperation:NSTableViewDropAbove];
}
return NSDragOperationMove;
} else {
return NSDragOperationNone;
}
}
- (BOOL)tableView:(NSTableView *)tableView
acceptDrop:(id <NSDraggingInfo>)info
row:(NSInteger)row
dropOperation:(NSTableViewDropOperation)operation {
// Get array controller
NSDictionary *bindingInfo = [tableView infoForBinding:#"content"];
NSArrayController *arrayController = [bindingInfo valueForKey:NSObservedObjectKey];
// Get object URIs from paste board
NSData *data = [[info draggingPasteboard] dataForType:[arrayController entityName]];
NSArray *objectURIs = [NSUnarchiver unarchiveObjectWithData:data];
if (!objectURIs) return NO;
// Get managed object context and persistent store coordinator
NSManagedObjectContext *context = [[NSApp delegate] managedObjectContext];
NSPersistentStoreCoordinator *coordinator = [context persistentStoreCoordinator];
// Collect manged objects with URIs
NSMutableArray *draggedObjects = [NSMutableArray array];
for (NSURL* objectURI in objectURIs) {
// Get managed object
NSManagedObjectID* objectID;
NSManagedObject* object;
objectID = [coordinator managedObjectIDForURIRepresentation:objectURI];
object = [context objectWithID:objectID];
if (!object) continue;
[draggedObjects addObject:object];
}
// Get managed objects
NSMutableArray *allObjects = [NSMutableArray arrayWithArray:[arrayController arrangedObjects]];;
if (!allObjects || [allObjects count] == 0) return NO;
// Replace dragged objects with null objects as placeholder to prevent old order
for (NSManagedObject *obj in draggedObjects) {
NSUInteger index = [allOobjects indexOfObject:obj];
if (index == NSNotFound) {
continue;
}
[allObjects replaceObjectAtIndex:index withObject:[NSNull null]];
}
// Insert dragged objects at row
if (row < [allObjects count]) {
[allObjects insertObjects:draggedObjects atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row, [draggedObjects count])]];
}
else {
[allObjects addObjectsFromArray:draggedObjects];
}
// Remove old null objects
[allObjects removeObject:[NSNull null]];
// Re-order objects
int i;
for (i = 0; i < [allObjects count]; i++) {
NSManagedObject *object = [allObjects objectAtIndex:i];
[object setValue:[NSNumber numberWithInt:i] forKey:#"order"];
}
// Reload data
[arrayController rearrangeObjects];
return YES;
}
#end
I'm having trouble dealing with object IDs in CoreData. I'm using MagicalRecord for convenience and have 3 contexts: a private queue working context, a main queue context for UI and parent to the working context, and a private queue saving context that is the parent of the main context.
My goal is to create an object in the working context, save to the persistent store, save it's objectID URL to NSUserDefaults, and then be able to pull out that MO using the objectID later on. However, what I'm finding is that after saving the permanent ID of the object is changing.
In the console output below you can see that after I request the permanent ID the value I get back is "F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p1" but later when I list all the objects in CD the only object there has the ID "F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2". p1 vs p2, what happened?
Code:
NSManagedObjectContext *c = [NSManagedObjectContext MR_contextThatPushesChangesToDefaultContext];
[c performBlockAndWait:^{
NSArray *all = [CDBaseAccount MR_findAllInContext:c];
NSLog(#"count: %d", all.count);
NSLog(#"all accounts = %#", all);
CDBaseAccount *a = [CDBaseAccount MR_createInContext:c];
a.accountName = #"foo";
[c MR_saveNestedContexts];
NSLog(#"temp a.objectID = %#", a.objectID);
NSError *error;
if (![c obtainPermanentIDsForObjects:#[a] error:&error]) {
NSLog(#"perm id error: %#", error);
return;
}
NSLog(#"perm a.objectID = %#", a.objectID);
NSURL *u = a.objectID.URIRepresentation;
dispatch_async(dispatch_get_main_queue(), ^{
NSManagedObjectContext *d = [NSManagedObjectContext MR_defaultContext];
NSArray *all = [CDBaseAccount MR_findAllInContext:d];
NSLog(#"count: %d", all.count);
NSLog(#"all accounts = %#", all);
NSManagedObjectID *i = [d.persistentStoreCoordinator managedObjectIDForURIRepresentation:u];
NSError *objWithIdError = nil;
NSManagedObject *o = [d existingObjectWithID:i error:&objWithIdError];
if (objWithIdError != nil) {
NSLog(#"existing object error: %#", objWithIdError);
return;
}
NSLog(#"o = %#", o);
NSLog(#"o.objectID = %#", o.objectID);
});
}];
Console output:
> +[NSManagedObjectContext(MagicalRecord) MR_contextWithStoreCoordinator:](0xa7c9b0) -> Created <NSManagedObjectContext: 0x83522a0>: Context *** MAIN THREAD ***
> count: 0
> all accounts = (
> )
> -[NSManagedObjectContext(MagicalSaves) MR_saveWithErrorCallback:](0x8353de0) -> Saving <NSManagedObjectContext: 0x8353de0>: Context *** MAIN THREAD ***
> -[NSManagedObjectContext(MagicalSaves) MR_saveWithErrorCallback:](0x8195450) -> Saving <NSManagedObjectContext: 0x8195450>: *** DEFAULT *** Context *** MAIN THREAD ***
> -[NSManagedObjectContext(MagicalSaves) MR_saveWithErrorCallback:](0x83522a0) -> Saving <NSManagedObjectContext: 0x83522a0>: *** BACKGROUND SAVE *** Context *** MAIN THREAD ***
> temp a.objectID = 0x8187ee0 <x-coredata:///CDBaseAccount/tF392AC6A-3539-4F39-AC53-35F9E5B3C9322>
> perm a.objectID = 0x8355800 <x-coredata://F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2>
> count: 1
> all accounts = (
"<CDBaseAccount: 0x844ca60> (entity: CDBaseAccount; id: 0x844a4c0 <x-coredata://F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p1> ; data: <fault>)"
)
> existing object error: Error Domain=NSCocoaErrorDomain Code=133000 "The operation couldn’t be completed. (Cocoa error 133000.)" UserInfo=0x864d8c0 {NSAffectedObjectsErrorKey=(
"<CDBaseAccount: 0x864b8c0> (entity: CDBaseAccount; id: 0x86405c0 <x-coredata://F474F6EE-A225-456B-92EF-AB1407336F15/CDBaseAccount/p2> ; data: <fault>)"
)}
Short answer is, don't do that :)
-objectID is not reliable between launches of your application. It is guaranteed to be unique and reliable under the following conditions:
Within a single lifecycle of the application
In its original object form (not in URL or NSString form)
Treating the -objectID as a permanent unique identifier to be stored outside of the persistent store is going to fail you fairly often. Core Data changes the underlying details of the -objectID many times during the life of the object.
If you need an externally reliable unique then you need to create one yourself. I generally recommend adding a [[NSProcessInfo processInfo] globallyUniqueString] to any entity that needs an externally reference-able unique. -awakeFromInsert is a great place to do that.
This may be because you're using nested context.
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
static NSArray *fetchAllPersons(NSManagedObjectContext *moc);
static NSManagedObjectModel *managedObjectModel();
static NSManagedObjectContext *createManagedObjectContext();
static NSURL *desktopDirectoryURL(void);
static void testObjectID(void);
int main(int argc, const char * argv[])
{
#autoreleasepool {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
testObjectID();
});
}
dispatch_main();
return 0;
}
static void testObjectID(void)
{
NSManagedObjectContext *c = createManagedObjectContext();
[c performBlock:^{
NSArray *all = fetchAllPersons(c);
NSLog(#"count: %lu", all.count);
NSLog(#"all accounts = %#", all);
NSManagedObject *a = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:c];
[a setValue:#"foo" forKey:#"name"];
NSLog(#"temp a.objectID = %#", a.objectID);
NSError *error = nil;
NSCAssert([c obtainPermanentIDsForObjects:#[a] error:&error], #"perm id error: %#", error);
NSLog(#"perm a.objectID = %#", a.objectID);
NSCAssert([c save:&error], #"Save failed: %#", error);
NSURL *u = a.objectID.URIRepresentation;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSManagedObjectContext *d = createManagedObjectContext();
NSArray *all = fetchAllPersons(c);
NSLog(#"count: %lu", all.count);
NSLog(#"all accounts = %#", all);
NSManagedObjectID *i = [d.persistentStoreCoordinator managedObjectIDForURIRepresentation:u];
NSError *objWithIdError = nil;
NSManagedObject *o = [d existingObjectWithID:i error:&objWithIdError];
NSCAssert(o != nil, #"existing object error: %#", objWithIdError);
NSLog(#"o = %#", o);
NSLog(#"o.objectID = %#", o.objectID);
});
}];
}
static NSArray *fetchAllPersons(NSManagedObjectContext *moc)
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
NSError *error = nil;
NSArray *result = [moc executeFetchRequest:request error:&error];
NSCAssert(result != nil, #"Fetch failed: %#", error);
return result;
}
static NSManagedObjectModel *managedObjectModel()
{
static NSManagedObjectModel *mom = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSEntityDescription *personEntity = [[NSEntityDescription alloc] init];
[personEntity setName:#"Person"];
NSAttributeDescription *nameAttribute = [[NSAttributeDescription alloc] init];
[nameAttribute setName:#"name"];
[nameAttribute setAttributeType:NSStringAttributeType];
[personEntity setProperties:#[nameAttribute]];
mom = [[NSManagedObjectModel alloc] init];
[mom setEntities:#[personEntity]];
});
return mom;
}
static NSManagedObjectContext *createManagedObjectContext()
{
static NSPersistentStoreCoordinator *coordinator;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel()];
NSString *STORE_TYPE = NSSQLiteStoreType;
NSString *STORE_FILENAME = #"foobar.db";
NSError *error;
NSURL *url = [desktopDirectoryURL() URLByAppendingPathComponent:STORE_FILENAME];
NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE
configuration:nil URL:url options:nil
error:&error];
if (newStore == nil) {
NSLog(#"Store Configuration Failure\n%#",
([error localizedDescription] != nil) ?
[error localizedDescription] : #"Unknown Error");
}
});
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[moc setPersistentStoreCoordinator:coordinator];
return moc;
}
static NSURL *desktopDirectoryURL(void)
{
static NSURL *URL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSError *error;
URL = [fileManager URLForDirectory:NSDesktopDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
NSCAssert(URL != nil, #"Could not access Desktop directory: %#", [error localizedDescription]);
});
return URL;
}
Outputs:
count: 0
all accounts = (
)
temp a.objectID = 0x10180e640 <x-coredata:///Person/tB1D48677-0152-4DA9-8573-7C7532863B4E2>
perm a.objectID = 0x101901bb0 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1>
count: 1
all accounts = (
"<NSManagedObject: 0x10180e5b0> (entity: Person; id: 0x101901bb0 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1> ; data: {\n name = foo;\n})"
)
o = <NSManagedObject: 0x100416530> (entity: Person; id: 0x100415b60 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1> ; data: {
name = foo;
})
o.objectID = 0x100415b60 <x-coredata://275C90E5-2598-4DFA-BF4C-E60A336E8BE4/Person/p1>
i want to insert data on secondary thread and then track changes in main thread.
I have two Entitys,and they was set Inverse.
#interface Entity1 : NSManagedObject
#property (nonatomic, retain) NSString * data;
#property (nonatomic, retain) Entity2 * entity2;
#end
#interface Entity2 : NSManagedObject
#property (nonatomic, retain) Entity1 * entity1;
#end
I register context save notificaton in main thread.
//this managedObjectContext run in main thread
-(NSManagedObjectContext *)managedObjectContext_mainThread {
......
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
return managedObjectContext_mainThread ;
}
//pass notification
- (void)contextDidSave:(NSNotification *)notification
{
......
[managedObjectContext_mainThread
mergeChangesFromContextDidSaveNotification:notification];
}
fetch from coredata,it will run in main thread
-(NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController == nil) {
NSManagedObjectContext *moc = [self managedObjectContext_mainThread];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Entity2"
inManagedObjectContext:moc];
NSSortDescriptor *sd = [[NSSortDescriptor alloc] initWithKey:#"entity1"
ascending:YES];
.....
}
return fetchedResultsController;
}
//NSFetchedResultsControllerDelegate, in this functions updata my UI
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
NSLog(#"controllerDidChangeContent start!");
}
this is the app start.
-(void)loadView {
myQueue = dispatch_queue_create("myQueue", NULL);
// this context is managedObjectContext_mainThread and run in main thread
NSArray *results = [self fetchedResultsController];
//insert Data oparation in managedObjectContext_otherThread and myQueueu
dispatch_async(myQueue, ^{
......
Entity1 *entity1 =
[NSEntityDescription insertNewObjectForEntityForName:#"Entity1"
inManagedObjectContext:managedObjectContext_otherThread];
Entity2 *entity2 =
[NSEntityDescription
insertNewObjectForEntityForName:#"Entity2"
inManagedObjectContext:managedObjectContext_otherThread];
entity1.data = #"myData";
entity1.entity2 = entity2;
[[self managedObjectContext_otherThread] save:nil];
});
}
when i build i got an error
-[Entity1 compare:]: unrecognized selector sent to instance 0x4d3ec90
and the error occur in NSFetchedResultsController handle context notification,this is the call stack:
__exceptionPreprocess + 185
objc_exception_throw + 47
-[NSObject(NSObject) doesNotRecognizeSelector:] + 187
___forwarding___ + 966
CF_forwarding_prep_0 + 50
_NSCompareObject + 76
+[NSFetchedResultsController(PrivateMethods)
_insertIndexForObject:inArray:lowIdx:highIdx:sortDescriptors:] + 286
-[NSFetchedResultsController(PrivateMethods) _postprocessInsertedObjects:] + 402
-[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] + 1804
if i don't fetch the Entity2 but Entity1 in fetchedResultsController,my app run ok.but I want to fetch entity2,and then use entity2.entity1.data to access entity1.who can help me.
I have found my mistake, I used the relationship to be sort descriptor in fetchrequest.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Entity2"
inManagedObjectContext:moc];
NSSortDescriptor *sd = [[NSSortDescriptor alloc] initWithKey:#"entity1"
ascending:YES];
if I use other attribute to be sortdescriptor, the app will be OK.