Cocoa KVO与NSUndoManager

Cocoa KVO与NSUndoManager,cocoa,key-value-observing,Cocoa,Key Value Observing,我的文档类中有一个名为passDescription的属性,类型为NSMutableDictionary。调用setPassDescription:时,使用NSJSONSerialization将属性的当前值归档到NSData实例中。更新属性的支持ivar,passDescription,然后注册撤消操作。该操作调用的选择器重新构造给定给它的NSData,并调用setPassDescription: - (void)setPassDescription:(NSDictionary *)param

我的文档类中有一个名为
passDescription
的属性,类型为
NSMutableDictionary
。调用
setPassDescription:
时,使用
NSJSONSerialization
将属性的当前值归档到
NSData
实例中。更新属性的支持ivar,
passDescription
,然后注册撤消操作。该操作调用的选择器重新构造给定给它的
NSData
,并调用
setPassDescription:

- (void)setPassDescription:(NSDictionary *)param
{
    NSLog(@"passDescription (before) = \n%@", passDescription);

    NSError *error;
    NSData *archivedOldValue = [NSJSONSerialization dataWithJSONObject:passDescription options:0 error:&error];
    NSAssert(archivedOldValue != nil, @"Could not archive old pass description: %@", error);

    NSData *blob = [NSJSONSerialization dataWithJSONObject:param options:NSJSONWritingPrettyPrinted error:&error];

    if (blob == nil) @throw [NSException exceptionWithName:@"PBJSONException" reason:@"Could not serialize pass.json" userInfo:@{ @"error": error }];

    [self.fileBrowserRoot fileWrapperWithName:@"pass.json"].fileContents = blob;

    [passDescriptionLock lock];
    [[self.undoManager prepareWithInvocationTarget:self] setPassDescriptionFromData:archivedOldValue];
    passDescription = param;
    [passDescriptionLock unlock];

    NSLog(@"passDescription (after) = \n%@", passDescription);

    // After the pass description has been set, refresh the list of build issues.
    [self recheckForIssues];
}
现在,笑话来了:
passDescription
正在使用键值观察进行观察。在Xcode调试器中进行的大量实验和检查表明,旧值和新值是相同的。(我知道这不是指针别名问题,因为这就是我使用
NSData
实例的原因。
NSData
是在我记录新值之前创建的,使其独立于从何创建的。)因此,当我按Command-Z撤消时,由于刚刚恢复的值与被撤消覆盖的值相同,因此不会发生任何情况

我能想到的唯一可能导致这种情况的原因是,KVO在调用
setPassDescription:
之前,正在为我设置
passDescription
ivar。为什么会这样,我如何防止KVO这样做?(我已经确认setter没有被调用两次。如果是,我会在调试器控制台中看到两次输出。)

以下是
setPassDescription:
的源代码

- (void)setPassDescription:(NSDictionary *)param
{
    NSLog(@"passDescription (before) = \n%@", passDescription);

    NSError *error;
    NSData *archivedOldValue = [NSJSONSerialization dataWithJSONObject:passDescription options:0 error:&error];
    NSAssert(archivedOldValue != nil, @"Could not archive old pass description: %@", error);

    NSData *blob = [NSJSONSerialization dataWithJSONObject:param options:NSJSONWritingPrettyPrinted error:&error];

    if (blob == nil) @throw [NSException exceptionWithName:@"PBJSONException" reason:@"Could not serialize pass.json" userInfo:@{ @"error": error }];

    [self.fileBrowserRoot fileWrapperWithName:@"pass.json"].fileContents = blob;

    [passDescriptionLock lock];
    [[self.undoManager prepareWithInvocationTarget:self] setPassDescriptionFromData:archivedOldValue];
    passDescription = param;
    [passDescriptionLock unlock];

    NSLog(@"passDescription (after) = \n%@", passDescription);

    // After the pass description has been set, refresh the list of build issues.
    [self recheckForIssues];
}

通常它不应该为您设置值。这就是二传手的作用。你能发布一些代码(也许还有更多的上下文)吗?您确定以后不会使用相同的值调用setPassDescription:吗?这可能会使用相同的数据创建一个额外的撤消操作,并且看起来旧数据是相同的。@user2311023–更新的信息