Crash on Redo or Undo


#1

After typing in the code for chapter 9, up to, but not including page 156, and running the code, I get the Exception below on undo or redo.

It takes a few seconds for the Exception to occur. The cursor goes to a stub method in the .m file.

2011-11-30 08:13:20.451 RaiseMan[1074:707] *** Terminating app due to uncaught exception ‘UnimplementedMethod’, reason: ‘dataOfType:error: is unimplemented’
*** First throw call stack:

Below is my complete (up to, but not yet including page 156) RMDocument.m file.

Thanks in advance for suggestions.

[code]#import “RMDocument.h”

@implementation RMDocument

// RMDocumentDVOContext enables this class to differentiate
// between its KVO messages and those for a super class
static void *RMDocumentKVOContext;

-(void)startObservingPerson:(Person*) person
{
[person addObserver:self
forKeyPath:@"personName"
options:NSKeyValueObservingOptionOld
context:&RMDocumentKVOContext];

[person addObserver:self 
         forKeyPath:@"expectedRaise" 
            options:NSKeyValueObservingOptionOld 
            context:&RMDocumentKVOContext];

}

-(void)stopObservingPerson:(Person*)person
{
[person removeObserver:self
forKeyPath:@"personName"
context:&RMDocumentKVOContext];

[person removeObserver:self
            forKeyPath:@"expectedRaise"
               context:&RMDocumentKVOContext];

}

  • (id)init
    {
    self = [super init];
    if (self)
    {
    employees = [[NSMutableArray alloc] init];
    }
    return self;
    }
    -(void) setEmployees:(NSMutableArray *)a
    {
    for (Person *person in employees)
    {
    [self stopObservingPerson:person];
    }
    employees = a;

    for (Person *person in employees)
    {
    [self startObservingPerson:person];
    }
    }

// method that does edits and is its own inverse
-(void)changeKeyPath:(NSString *)keyPath ofObject:(id)obj toValue:(id)newValue
{
// setValue:forKeyPath: will call the key-value observing method which takes care of the undoing stuff.

[obj setValue:newValue forKeyPath:keyPath];

}

// Method that will be called whenever a Person object is edited by either the user or the
// changeKeyPath:ofObject:toValue: method. This method puts a call to
//changeKeyPath:ofObject:toValue: on the undo stack with the old value of the changed key.

-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context != &RMDocumentKVOContext)
{
// if the context does not match, this message must be intended for our superclass…
[super observeValueForKeyPath: keyPath
ofObject:object
change:change
context:context];
return;
}
NSUndoManager *undo = [self undoManager];
id oldValue = [change objectForKey:NSKeyValueChangeOldKey];

// NSNull objects represent nil in a dictionary
if (oldValue == [NSNull null])
{
    oldValue = nil;
}
NSLog(@"oldValue = %@", oldValue);
[[undo prepareWithInvocationTarget:self] changeKeyPath:keyPath ofObject:object toValue];
[undo setActionName:@"Edit"];

}

-(void) insertObject:(Person *)p inEmployeesAtIndex:(NSUInteger)index
{
NSLog(@“Adding %@ to %@”, p, employees);
// Add the inverse of this operation to the undo stack
NSUndoManager *undo = [self undoManager];
[[undo prepareWithInvocationTarget:self] removeObjectFromEmployeesAtIndex:index];

if (![undo isUndoing])
    {
        [undo setActionName:@"Add Person"];
    }
  // add the Person to the array
[self startObservingPerson:p];
[employees insertObject:p atIndex:index];

}

-(void)removeObjectFromEmployeesAtIndex:(NSUInteger)index
{
Person *p =[employees objectAtIndex:index];
NSLog(@“Removing %@ to %@”, p, employees);
// Add the inverse of this operation to the undo stack
NSUndoManager *undo = [self undoManager];
[[undo prepareWithInvocationTarget:self] insertObject:p
inEmployeesAtIndex:index];

if (![undo isUndoing])
{
    [self stopObservingPerson:p];
    [undo setActionName:@"Remove Person"];
}
[employees removeObjectAtIndex:index];    

}

// ----- xcode generated below.

  • (NSString *)windowNibName
    {
    // Override returning the nib file name of the document
    // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
    return @“RMDocument”;
    }

  • (void)windowControllerDidLoadNib:(NSWindowController *)aController
    {
    [super windowControllerDidLoadNib:aController];
    // Add any code here that needs to be executed once the windowController has loaded the document’s window.
    }

  • (NSData *)dataOfType:(NSString *)typeName error:(NSError *)outError
    {
    /

    Insert code here to write your document to data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning nil.
    You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
    */
    NSException *exception = [NSException exceptionWithName:@“UnimplementedMethod” reason:[NSString stringWithFormat:@"%@ is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil];
    @throw exception;
    return nil;
    }

  • (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError *)outError
    {
    /

    Insert code here to read your document from the given data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning NO.
    You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead.
    If you override either of these, you should also override -isEntireFileLoaded to return NO if the contents are lazily loaded.
    */
    NSException *exception = [NSException exceptionWithName:@“UnimplementedMethod” reason:[NSString stringWithFormat:@"%@ is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil];
    @throw exception;
    return YES;
    }

  • (BOOL)autosavesInPlace
    {
    return YES;
    }

@end
[/code]


#2

Find the methods dataOfType:error: and readFromData:ofType:error: in RMDocument.m and remove everything but the return statements.

For some reason the Apple templates throw exceptions on save/load, and because of autosave in Lion these methods get called once we add the undo features. You can safely remove the exception-throwing code; we implement these methods in Chapter 10.

Adam


#3

Yes, that fixed it.

Perhaps a note would be useful in the 2nd edition or in the errata (not really an error, of course)?

I’ll make a note in the book so I’ll know to remove this code in the future from those methods.

Thanks,

– m

P.S. I bought that XCODE add-on, “Accessorizer”, you mentioned. It looks good, and may someday be helpful, but I don’t see it helping a lot while going through your book.


#4

OK, all the code for the entire chapter 9 works now. :slight_smile:

The only strange thing that is happening is that when I run RaiseMan, it starts “minimized”. I have to click the icon on the dock for it to spring up.

Thanks,

– m

P.S. Have you all thought about writing a “cookbook” for ObjectiveC/Cocoa too? That would be an awesome combo. Go to your Cocoa programming to understand what’s going on, then possibly (probably) to the Cookbook to get an applicable snippet of code for your project. I saw a Cookbook for ObjC on Amazon, but some of the reviews were not that great saying it was out of date, etc…


#5

morkus,

[quote=“morkus”]OK, all the code for the entire chapter 9 works now. :slight_smile:

The only strange thing that is happening is that when I run RaiseMan, it starts “minimized”. I have to click the icon on the dock for it to spring up.
[/quote]

My guess is that this is related to Lion’s UI preservation. Does it still happen if you use Edit Scheme (Product menu) and tell it, “Launch application without state restoration” (Options tab).

That would be a cool book. We’ll give it some thought!

Adam


#6

So much to learn…

Yes, that seemed to fix it, but due to the BOOL instantiation issue the form doesn’t come up.

I’ll send you the project in a separate email shortly so you can take a (very much appreciated) quick look at the BOOL issue with the onSpecial I posted about. :slight_smile:

Thanks Adam.

-m


#7

I also ran into this issue, and found this thread in a search. Your solution worked fine. Thanks for posting this.


#8

I am running Xcode 4.2 on OS 10.7.2

I deleted the code in the readFromData:dataOfType:error: and the dataOfType:error: methods, except for the return statements. The Remove button now does nothing. The program crashes when I try to Undo-- does not recognize the selector insertObject:atIndex: Thanks for your help in advance.

[quote]2012-01-30 11:40:39.985 9RaiseMan[1551:707] adding <Person: 0x10016f780> to (
)
2012-01-30 11:40:48.401 9RaiseMan[1551:707] adding <Person: 0x10019d210> to (
"<Person: 0x10016f780>"
)
2012-01-30 11:40:57.480 9RaiseMan[1551:707] adding <Person: 0x10019d740> to (
"<Person: 0x10016f780>",
"<Person: 0x10019d210>"
)
2012-01-30 11:40:57.705 9RaiseMan[1551:707] adding <Person: 0x1003975e0> to (
"<Person: 0x10016f780>",
"<Person: 0x10019d210>",
"<Person: 0x10019d740>"
)
2012-01-30 11:41:23.632 9RaiseMan[1551:707] removing <Person: 0x1003975e0> from (
"<Person: 0x10016f780>",
"<Person: 0x10019d210>",
"<Person: 0x10019d740>",
"<Person: 0x1003975e0>"
)
2012-01-30 11:41:23.733 9RaiseMan[1551:707] An uncaught exception was raised
2012-01-30 11:41:23.734 9RaiseMan[1551:707] *** -[NSProxy doesNotRecognizeSelector:insertObject:atIndex:] called![/quote]


#9

Where does the exception happen – can you show us the stack trace?

I’d suggest double-checking your code for insertObject:inEmployeesAtIndex: and removeObjectFromEmployeesAtIndex:. In particular make sure that your calls to prepareWithInvocationTarget: have the correct target and that the message sent (that is recorded by the undo manager) is the correct one.


#10

The problem was in removeObjectFromEmployeesAtIndex:
Autofill completed the selector (inEmployeesAtIndex:) for undo improperly as you indicated
No such thing as a free lunch.

Thank you very much


#11

i have an error message when i try to add a second Person with Add button.

I have try to see if there are differences in the code of insert and remove, but i have not find them.

[code]-(void)insertObject:(Person *)p inEmployeesAtIndex:(NSUInteger)index
{
NSLog(@“Adding %@ to %@”, p, employees);
NSUndoManager *undo = [self undoManager];
[[undo prepareWithInvocationTarget:self] removeObjectFromEmployeesAtindex:index];

if(![undo isUndoing]){
    [undo setActionName:@"Add Person"];
}

[self startObservingPerson:p];

[employees insertObject:p atIndex:index];

}
-(void)removeObjectFromEmployeesAtindex:(NSUInteger)index
{
Person *p = [employees objectAtIndex:index];
NSLog(@“Removieng %@ from %@”, p, employees);
NSUndoManager *undo = [self undoManager];
[[undo prepareWithInvocationTarget:self] insertObject:p inEmployeesAtIndex:index];

if(![undo isUndoing]){
    [undo setActionName:@"Remove Person"];
}

[self stopObservingPerson:p];

[employees removeObjectAtIndex:index];

}
[/code]

note: personaName is the name of variable that i declared in Person.h not an error (i’m italian and i’m studying on english version of the book).

Thank you in advance,

Federico


#12

I am having some trouble with this chapter… I’ve included my code below… So, the problem I’ve been having is trying to edit the values and undo them or redo them… etc… I’ve been trying to debug this for hours and found that in

-(void)observeValueForKeyPath:ofObject:change:context: when i edit entry in the table view, and press enter to call this method… it goes to…

if (context != RMKVOContext){
super… observeValue
}

problem is, context IS RMKVOContext but it still goes to [super observeValue] all the time!!, what’s going on?!?!!

Any help to fix my code would be awesome… I’m tired of going over this over and over again @_@

[code]//
// RMDocument.m
// RaiseMan
//
// Created by Choco Man on 12-03-11.
// Copyright © 2012 MyCompanyName. All rights reserved.
//

#import “RMDocument.h”
#import “Person.h”

@implementation RMDocument

  • (id)init{
    self = [super init];
    if (self) {
    employees = [[NSMutableArray alloc]init];
    }
    return self;
    }

// Undo Edits

// RMDocumentKVOContext enables this class to differentiate between its KVO messages and those intended for a superclass

static void *RMDocumentKVOContext;

-(void)startObservingPerson:(Person *)person{
[person addObserver:self forKeyPath:@“personName” options:NSKeyValueObservingOptionOld context:&RMDocumentKVOContext];
[person addObserver:self forKeyPath:@“expectedRaise” options:NSKeyValueObservingOptionOld context:&RMDocumentKVOContext];
}
-(void)stopObservingPerson:(Person *)person{
[person removeObserver:self forKeyPath:@"personName"context:&RMDocumentKVOContext];
[person removeObserver:self forKeyPath:@"expectedRaise"context:&RMDocumentKVOContext];
}
-(void)setEmployees:(NSMutableArray *)a{
for (Person *person in employees) {
[self stopObservingPerson:person];
}
employees = a;
for (Person *person in employees){
[self startObservingPerson:person];
}
}

// Undo Implementations
-(void)insertObject:(Person *)p inEmployeesAtIndex:(NSUInteger)index{
NSLog(@“adding %@ to %@”,p,employees);

// Add the inverse of this action to the undostack as an invocation package..
NSUndoManager *undo = [self undoManager];
[[undo prepareWithInvocationTarget:self]removeObjectFromEmployeesAtIndex:index];

if (![undo isUndoing]) {
    [undo setActionName:@"Add Person"];
}

//Add person to array
[self startObservingPerson:p];
[employees insertObject:p atIndex:index];

}
-(void)removeObjectFromEmployeesAtIndex:(NSUInteger)index{
Person *p = [employees objectAtIndex:index];
NSLog(@“removing %@ to %@”,p,employees);

// Add the inverse of this action to the undostack as an invocation package..
NSUndoManager *undo = [self undoManager];
[[undo prepareWithInvocationTarget:self]insertObject:p inEmployeesAtIndex:index];

if (![undo isUndoing]) {
    [undo setActionName:@"Remove Person"];
}

//Remove person to array
[self stopObservingPerson:p];
[employees removeObjectAtIndex:index];

}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{

if (context != RMDocumentKVOContext) {
// If the context does not match, this message must be inteded for our superclass
    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    return;
}

NSUndoManager *undo = [self undoManager];
id oldValue = [change objectForKey:NSKeyValueChangeOldKey];

//NSNull objects are used to represent nil in a dictionary
if (oldValue == [NSNull null]) {
    oldValue = nil;
}
NSLog(@"oldValue = %@",oldValue);
[[undo prepareWithInvocationTarget:self] changeKeyPath:keyPath ofObject:object toValue];

[undo setActionName:@"Edit"];

}

-(void)changeKeyPath:(NSString *)keyPath ofObject:(id)obj toValue:(id)newValue{
// setValue:forKeyPath: will cause the key-value observing method to be called, which takes care of the undo stuff
[obj setValue:newValue forKeyPath:keyPath];
}

-(IBAction)createEmployee:(id)sender{
NSWindow *w = [tableView window];

// Try to end eny editing that is taking place
BOOL editingEnded=[w makeFirstResponder:w];
if(!editingEnded){
    NSLog(@"Unable to end editing");
    return;
}

NSUndoManager *undo = [self undoManager];

// Has an edit already occur?
if([undo groupingLevel]>0){
    //Close last group
    [undo endUndoGrouping];
    //Open new group
    [undo beginUndoGrouping];
}

// Create the object
Person *p = [employeeController newObject];

// Add it to the content array of 'employee Controller'
[employeeController addObject:p];

// Re-sort (in case user has a sorted column)
[employeeController rearrangeObjects];

// Get the sorted array
NSArray *a = [employeeController arrangedObjects];

// Find the object just added
NSUInteger row = [a indexOfObjectIdenticalTo:p];
NSLog(@"starting edit of %@ in row %lu",p,row);

// Begin editing first column
[tableView editColumn:0 row:row withEvent:nil select:YES];

}

  • (NSString *)windowNibName{
    // Override returning the nib file name of the document
    // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.
    return @“RMDocument”;
    }
  • (void)windowControllerDidLoadNib:(NSWindowController *)aController{
    [super windowControllerDidLoadNib:aController];
    // Add any code here that needs to be executed once the windowController has loaded the document’s window.
    }
  • (BOOL)autosavesInPlace{
    return YES;
    }
  • (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError{
    // Insert code here to write your document to data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning nil.
    // You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
    //NSException *exception = [NSException exceptionWithName:@“UnimplementedMethod” reason:[NSString stringWithFormat:@"%@ is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil];
    //@throw exception;
    return nil;
    }
  • (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError{
    // Insert code here to read your document from the given data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning NO.
    // You can also choose to override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead.
    // If you override either of these, you should also override -isEntireFileLoaded to return NO if the contents are lazily loaded.
    //NSException *exception = [NSException exceptionWithName:@“UnimplementedMethod” reason:[NSString stringWithFormat:@"%@ is unimplemented", NSStringFromSelector(_cmd)] userInfo:nil];
    //@throw exception;
    return YES;
    }

@end[/code]

Using MacOS Lion 10.7.3 with Xcode 4.3.1 and all the latest SDKs

some errors include:

Exception detected while handling key input. An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.

An uncaught exception was raised dataOfType:error: is unimplemented Terminating app due to uncaught exception 'UnimplementedMethod', reason: 'dataOfType:error: is unimplemented'

Edit: YES!! I found the mistake… thank GOD. It was a simple…

if(context != &RMDocumentKVOContext){

check that I made a mistake by forgetting the ‘&’… @_@ silly me…


#13

[quote=“AdamPreble”]Find the methods dataOfType:error: and readFromData:ofType:error: in RMDocument.m and remove everything but the return statements.

For some reason the Apple templates throw exceptions on save/load, and because of autosave in Lion these methods get called once we add the undo features. You can safely remove the exception-throwing code; we implement these methods in Chapter 10.

Adam[/quote]

I have done this but still get a crash when I try to save or open. There seems to be no problem with undo, but
so far I have been unable to save anything without a crash and the error always refers to these two methods. I
am using Xcode 4.5.2 under Mac OS 10.8.2.

I now have:

- (NSData *)dataOfType:(NSString *)aType error:(NSError **)outError // p. 168
{
   return [NSKeyedArchiver archivedDataWithRootObject:employees];
}
- (BOOL)readFromData:(NSData *)data
              ofType:(NSString *)typeName
               error:(NSError **)outError
{

    return YES;
}

Here is the log:

2012-12-02 00:02:46.635 RaiseMan[33369:303] dataOfType:error: is unimplemented 2012-12-02 00:02:46.640 RaiseMan[33369:303] ( 0 CoreFoundation 0x00007fff920f70a6 __exceptionPreprocess + 198 1 libobjc.A.dylib 0x00007fff9630a3f0 objc_exception_throw + 43 2 RaiseMan 0x00000001000021e5 -[RMDocument dataOfType:error:] + 229 3 AppKit 0x00007fff8df43fd2 -[NSDocument writeToURL:ofType:error:] + 772 4 AppKit 0x00007fff8df43c72 -[NSDocument writeToURL:ofType:forSaveOperation:originalContentsURL:error:] + 453 5 AppKit 0x00007fff8df43553 -[NSDocument _writeSafelyToURL:ofType:forSaveOperation:forceTemporaryDirectory:error:] + 547 6 AppKit 0x00007fff8df43324 -[NSDocument _writeSafelyToURL:ofType:forSaveOperation:error:] + 28 7 AppKit 0x00007fff8df42ea9 -[NSDocument writeSafelyToURL:ofType:forSaveOperation:error:] + 348 8 AppKit 0x00007fff8df42647 __block_global_90 + 76 9 AppKit 0x00007fff8df4fd4f __block_global_97 + 242 10 AppKit 0x00007fff8df4fc4e __block_global_96 + 339 11 AppKit 0x00007fff8e38be10 __block_global_89 + 1533 12 Foundation 0x00007fff93b91bce -[NSFileCoordinator(NSPrivate) _invokeAccessor:orDont:thenRelinquishAccessClaimForID:] + 229 13 Foundation 0x00007fff93b74b0a -[NSFileCoordinator(NSPrivate) _coordinateWritingItemAtURL:options:error:byAccessor:] + 705 14 AppKit 0x00007fff8df42253 -[NSDocument _fileCoordinator:coordinateReadingContentsAndWritingItemAtURL:byAccessor:] + 362 15 AppKit 0x00007fff8df41fd7 __66-[NSDocument saveToURL:ofType:forSaveOperation:completionHandler:]_block_invoke_0 + 920 16 AppKit 0x00007fff8df3cb79 -[NSDocument continueFileAccessUsingBlock:] + 222 17 AppKit 0x00007fff8df41484 __block_global_18 + 125 18 AppKit 0x00007fff8e3b66d2 __block_global_44 + 175 19 CoreFoundation 0x00007fff920b4272 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 18 20 CoreFoundation 0x00007fff92074a4f __CFRunLoopDoBlocks + 255 21 CoreFoundation 0x00007fff92098db4 __CFRunLoopRun + 772 22 CoreFoundation 0x00007fff920986b2 CFRunLoopRunSpecific + 290 23 HIToolbox 0x00007fff95c7f0a4 RunCurrentEventLoopInMode + 209 24 HIToolbox 0x00007fff95c7ed84 ReceiveNextEventCommon + 166 25 HIToolbox 0x00007fff95c7ecd3 BlockUntilNextEventMatchingListInMode + 62 26 AppKit 0x00007fff8e06c613 _DPSNextEvent + 685 27 AppKit 0x00007fff8e06bed2 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128 28 AppKit 0x00007fff8e063283 -[NSApplication run] + 517 29 AppKit 0x00007fff8e007cb6 NSApplicationMain + 869 30 RaiseMan 0x00000001000019c2 main + 34 31 libdyld.dylib 0x00007fff91a687e1 start + 0 32 ??? 0x0000000000000003 0x0 + 3 )


#14

All’s well now. Somehow I had checked “Use Core Data” when creating Raiseman.
I have now recreated it from scratch and copied over the files from the
version that was giving problems. Now everything seems to be working fine.


#15

I encountered the same problem mentioned in the original post of this thread. Instead of implementing Adam’s solution (“Find the methods dataOfType:error: and readFromData:ofType:error: in RMDocument.m and remove everything but the return statements.”), I instead replaced the default provided by Xcode:

+ (BOOL)autosavesInPlace { return YES; }
with:

+ (BOOL)autosavesInPlace { return NO; }
That seemed to do the trick.


#16

@chocolatejeff - Just wanted to say - 4+ years later, you really helped me out w/ this. :smile:

I did the exact same thing. Was driving me nuts, too because I was sure I screwed up something w/ employees/setEmployees…

Thanks again!