tciuro / NanoStore

NanoStore is an open source, lightweight schema-less local key-value document store written in Objective-C for Mac OS X and iOS.
Other
404 stars 39 forks source link

Exception thrown when adding objects conforming to NSFNanoObjectProtocol to a bag #83

Closed billgarrison closed 11 years ago

billgarrison commented 11 years ago

I have a model class called MyEvent that subclasses NSObject and implements NSFNanoObjectProtocol.

@interface MyEvent : NSObject <NSFNanoObjectProtocol>

When I attempt to add an instance of that class to an NSFNanoBag, I get the following exception:

2013-04-12 20:48:56.693 NanoStoreDemo[793:11303] -[MyEvent dictionaryRepresentation]: unrecognized selector sent to instance 0x758d3e0
(lldb) po 0x0758d3e0
$2 = 123261920 
(lldb) po stacktrace
* thread #1: tid = 0x1f03, 0x011f9e52 libobjc.A.dylib`objc_exception_throw, stop reason = breakpoint 1.1
    frame #0: 0x011f9e52 libobjc.A.dylib`objc_exception_throw
    frame #1: 0x021774bd CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 253
    frame #2: 0x020dbbbc CoreFoundation`___forwarding___ + 588
    frame #3: 0x020db94e CoreFoundation`_CF_forwarding_prep_0 + 14
    frame #4: 0x0000750c NanoStoreDemo`-[NSFNanoBag addObject:error:](self=0x071ca2b0, _cmd=0x0003a0eb, object=0x0758d3e0, outError=0xbfffdbc8) + 428 at NSFNanoBag.m:220
    frame #5: 0x00003bea NanoStoreDemo`-[MasterViewController insertNewObject:](self=0x07580b90, _cmd=0x00039ffb, sender=0x071bb770) + 522 at MasterViewController.m:109
    frame #6: 0x0120d705 libobjc.A.dylib`-[NSObject performSelector:withObject:withObject:] + 77
    frame #7: 0x001412c0 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 96
    frame #8: 0x0037da64 UIKit`-[UIBarButtonItem(UIInternal) _sendAction:withEvent:] + 139
    frame #9: 0x0120d705 libobjc.A.dylib`-[NSObject performSelector:withObject:withObject:] + 77
    frame #10: 0x001412c0 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 96
    frame #11: 0x00141258 UIKit`-[UIApplication sendAction:toTarget:fromSender:forEvent:] + 61
    frame #12: 0x00202021 UIKit`-[UIControl sendAction:to:forEvent:] + 66
    frame #13: 0x0020257f UIKit`-[UIControl(Internal) _sendActionsForEvents:withEvent:] + 578
    frame #14: 0x002016e8 UIKit`-[UIControl touchesEnded:withEvent:] + 546
    frame #15: 0x00170cef UIKit`-[UIWindow _sendTouchesForEvent:] + 846
    frame #16: 0x00170f02 UIKit`-[UIWindow sendEvent:] + 273
    frame #17: 0x0014ed4a UIKit`-[UIApplication sendEvent:] + 436
    frame #18: 0x00140698 UIKit`_UIApplicationHandleEvent + 9874
    frame #19: 0x02047df9 GraphicsServices`_PurpleEventCallback + 339
    frame #20: 0x02047ad0 GraphicsServices`PurpleEventCallback + 46
    frame #21: 0x02061bf5 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 53
    frame #22: 0x02061962 CoreFoundation`__CFRunLoopDoSource1 + 146
    frame #23: 0x02092bb6 CoreFoundation`__CFRunLoopRun + 2118
    frame #24: 0x02091f44 CoreFoundation`CFRunLoopRunSpecific + 276
    frame #25: 0x02091e1b CoreFoundation`CFRunLoopRunInMode + 123
    frame #26: 0x020467e3 GraphicsServices`GSEventRunModal + 88
    frame #27: 0x02046668 GraphicsServices`GSEventRun + 104
    frame #28: 0x0013dffc UIKit`UIApplicationMain + 1211
    frame #29: 0x00001eed NanoStoreDemo`main(argc=1, argv=0xbffff27c) + 141 at main.m:16

Digging into -[NSFNanoBag addObject:error:], it turns out this method is using private NSFNanoObject API to obtain the object's dictionary representation.

- (BOOL)addObject:(id <NSFNanoObjectProtocol>)object error:(out NSError **)outError
{
    ...

    NSString *objectKey = [(id)object nanoObjectKey];
    NSDictionary *info = [(id)object dictionaryRepresentation];

    ...
}

Rather than using -[NSFNanoObject dictionaryRepresentation], it should be using -[NSFNanoObjectProtocol nanoObjectDictionaryRepresentation].

- (BOOL)addObject:(id <NSFNanoObjectProtocol>)object error:(out NSError **)outError
{
    ...

    NSString *objectKey = [object nanoObjectKey];
    NSDictionary *info = [object nanoObjectDictionaryRepresentation];

    ...
}

Here's a unit test for NanoStoreBagTests.m that will demonstrate the bug.

- (void) testBagAddTwoNSObjectsConformingToProtocol
{
    id car1 = [[NanoCarTestClass alloc] initNanoObjectFromDictionaryRepresentation:@{@"kName" : @"XJ-7"} 
                    forKey:[NSFNanoEngine stringWithUUID] store:nil];
    id car2 = [[NanoCarTestClass alloc] initNanoObjectFromDictionaryRepresentation:@{@"kName" : @"Jupiter 8"} 
                    forKey:[NSFNanoEngine stringWithUUID] store:nil];

    NSArray *objects = @[car1, car2];

    NSFNanoBag *bag = [NSFNanoBag bag];
    NSError *outError = nil;
    BOOL success = [bag addObjectsFromArray:objects error:&outError];
    BOOL hasUnsavedChanges = bag.hasUnsavedChanges;

    NSDictionary *info = [bag nanoObjectDictionaryRepresentation];
    NSArray *returnedKeys = [info objectForKey:NSF_Private_NSFNanoBag_NSFObjectKeys];

    STAssertTrue (success, @"expected bag to have saved");
    STAssertTrue (hasUnsavedChanges, @"expected bag to have no unsaved changes");
    STAssertNil (outError, @"expect bag to return no error on save");
    STAssertEquals ([returnedKeys count], [objects count], @"expected saved bag to return %d object keys", [objects count]);
}