I have a project using Core Data. It has a Note object which has one transformable attribute called content. I am storing an NSAttributedString into this property. The property saves fine for the first save, but when I try to create a second note object, the app crashes.
Here is the code I use to save:
- (IBAction)save:(id)sender {
Note* newNote = [NSEntityDescription insertNewObjectForEntityForName:@"Note" inManagedObjectContext:[self managedObjectContext]];
NSAttributedString* string = [[self textView]attributedText];
[newNote setContent:string];
NSArray* tokens = [[self tokenField]tokens];
NSError* error = nil;
if (![[self managedObjectContext]save:&error]) {
NSLog(@"Error saving %@",[error localizedDescription]);
}
[[self navigationController]popViewControllerAnimated:YES];
}
Again this code saves with no problem when there are no objects in the persistent store.
When I attempt to save a new object (in a non empty store:) this exception is thrown first (I have a break point set for all exceptions)
-[NSConcreteMutableAttributedString compare:]: unrecognized selector sent to instance 0x1759e8b0
Then when I continue, I get this:
2013-07-18 10:17:39.011 Meta[2417:60b] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[NSConcreteMutableAttributedString compare:]: unrecognized selector sent to instance 0x1759e8b0 with userInfo (null) 2013-07-18 10:17:39.014 Meta[2417:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteMutableAttributedString compare:]: unrecognized selector sent to instance 0x1759e8b0'
I am also using an NSManagedObject subclass which is implemented as follows:
@interface Note : NSManagedObject
@property (nonatomic, strong) id content;
@end
Again content is where I am attempting to store the attributed string
I have tried using the default NSValueTransformer and my own subclass. Both cause the same problem.
Edit: Here is the implementation of my value transformer:
#import "AttributedStringValueTransformer.h"
@implementation AttributedStringValueTransformer
+(Class)transformedValueClass {
return [NSAttributedString class];
}
+(void)initialize {
[NSValueTransformer setValueTransformer:[[self alloc]init] forName:@"NSAttributedStringValueTransformer"];
}
+(BOOL)allowsReverseTransformation {
return YES;
}
-(NSData*)transformedValue:(NSAttributedString*)value {
NSData* stringAsData = [NSKeyedArchiver archivedDataWithRootObject:value];
return stringAsData;
}
-(NSAttributedString*)reverseTransformedValue:(NSData*)value {
NSAttributedString* string = [NSKeyedUnarchiver unarchiveObjectWithData:value];
return string;
}
Edit: Here is the backtrace:
(lldb) bt
* thread #1: tid = 0x5d2ef, 0x39a3d688 libobjc.A.dylib`objc_exception_throw, queue = 'com.apple.main-thread, stop reason = breakpoint 1.1
frame #0: 0x39a3d688 libobjc.A.dylib`objc_exception_throw
frame #1: 0x2f958fa2 CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 202
frame #2: 0x2f95787a CoreFoundation`___forwarding___ + 706
frame #3: 0x2f8a5528 CoreFoundation`__forwarding_prep_0___ + 24
frame #4: 0x302a5d48 Foundation`_NSCompareObject + 32
frame #5: 0x3034fe7e Foundation`-[NSSortDescriptor compareObject:toObject:] + 270
frame #6: 0x2f79e5f4 CoreData`+[NSFetchedResultsController(PrivateMethods) _insertIndexForObject:inArray:lowIdx:highIdx:sortDescriptors:] + 216
frame #7: 0x2f79ada6 CoreData`-[NSFetchedResultsController(PrivateMethods) _postprocessInsertedObjects:] + 514
frame #8: 0x2f79c842 CoreData`-[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] + 1898
frame #9: 0x2f89c836 CoreFoundation`_CFXNotificationPost + 1718
frame #10: 0x302a13b0 Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] + 76
frame #11: 0x2f72280a CoreData`-[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] + 78
frame #12: 0x2f721b0a CoreData`-[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:] + 298
frame #13: 0x2f6a0af6 CoreData`-[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 2346
frame #14: 0x2f7159ba CoreData`-[NSManagedObjectContext save:] + 190
frame #15: 0x00106f82 Meta`-[NewNoteViewController save:](self=0x15dd2030, _cmd=0x325ba35a, sender=0x15dc5a70) + 918 at NewNoteViewController.m:176
frame #16: 0x320482a2 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 90
frame #17: 0x32048330 UIKit`-[UIBarButtonItem(UIInternal) _sendAction:withEvent:] + 120
frame #18: 0x320482a2 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 90
frame #19: 0x32048242 UIKit`-[UIApplication sendAction:toTarget:fromSender:forEvent:] + 30
frame #20: 0x32048220 UIKit`-[UIControl sendAction:to:forEvent:] + 44
frame #21: 0x3217cfce UIKit`-[UIControl _sendActionsForEvents:withEvent:] + 374
frame #22: 0x32048036 UIKit`-[UIControl touchesEnded:withEvent:] + 590
frame #23: 0x31f830e0 UIKit`-[UIWindow _sendTouchesForEvent:] + 528
frame #24: 0x31f718f0 UIKit`-[UIApplication sendEvent:] + 196
frame #25: 0x32114406 UIKit`_UIApplicationHandleHIDEvent + 6262
frame #26: 0x306765ce IOKit`__IOHIDEventSystemClientQueueCallback + 222
frame #27: 0x2f911f04 CoreFoundation`__CFMachPortPerform + 136
frame #28: 0x2f91d206 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 34
frame #29: 0x2f91d1a2 CoreFoundation`__CFRunLoopDoSource1 + 346
frame #30: 0x2f91b966 CoreFoundation`__CFRunLoopRun + 1398
frame #31: 0x2f892446 CoreFoundation`CFRunLoopRunSpecific + 522
frame #32: 0x2f89222a CoreFoundation`CFRunLoopRunInMode + 106
frame #33: 0x343f06da GraphicsServices`GSEventRunModal + 138
frame #34: 0x31fbae00 UIKit`UIApplicationMain + 1136
frame #35: 0x00105374 Meta`main(argc=1, argv=0x27d07d1c) + 116 at main.m:16
Found it:
* thread #1: tid = 0x5d2ef, 0x39a3d688 libobjc.A.dylib`objc_exception_throw, queue = 'com.apple.main-thread, stop reason = breakpoint 1.1
frame #0: 0x39a3d688 libobjc.A.dylib`objc_exception_throw
frame #1: 0x2f958fa2 CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 202
frame #2: 0x2f95787a CoreFoundation`___forwarding___ + 706
frame #3: 0x2f8a5528 CoreFoundation`__forwarding_prep_0___ + 24
frame #4: 0x302a5d48 Foundation`_NSCompareObject + 32
frame #5: 0x3034fe7e Foundation`-[NSSortDescriptor compareObject:toObject:] + 270**
frame #6: 0x2f79e5f4 CoreData`+[NSFetchedResultsController(PrivateMethods) _insertIndexForObject:inArray:lowIdx:highIdx:sortDescriptors:] + 216
frame #7: 0x2f79ada6 CoreData`-[NSFetchedResultsController(PrivateMethods) _postprocessInsertedObjects:] + 514
frame #8: 0x2f79c842 CoreData`-[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] + 1898
As the save happened, the fetched results controller started to update the tableview (which was on the presenting view controller).
I have a tableview controller subclass that I use for easy setup that takes a sort descriptor key (as a string) and internally turns it into an array and sets up the fetch request.
[self setSortDescriptorKey:@"content"];
Content is my transformable attribute in the data model (Transforms a NSAttributedString). Originally it was just a normal string, but I needed to store attribute data as well. After changing it, the FRC was sending compare to it. I fixed it by changing the sort descriptor key:
[self setSortDescriptorKey:@"content.string"];