Objective c 为什么在实现自己的KVC setter/getter方法时会出现未捕获的异常

Objective c 为什么在实现自己的KVC setter/getter方法时会出现未捕获的异常,objective-c,macos,cocoa,key-value-observing,Objective C,Macos,Cocoa,Key Value Observing,我的模型类大部分是用合成的setter/getter方法实现的,一切都很好。一切都很好地连接到用户界面。后来我意识到更改一个属性会导致其他属性的更改(更改type可能会导致minA和maxA中的更改),因此我手动为type属性编写了setter/getter方法。代码如下: QBElementType @interface QBElementType : NSObject @property NSRange minRange; @property NSRange maxRange;

我的模型类大部分是用合成的setter/getter方法实现的,一切都很好。一切都很好地连接到用户界面。后来我意识到更改一个属性会导致其他属性的更改(更改
type
可能会导致
minA
maxA
中的更改),因此我手动为
type
属性编写了setter/getter方法。代码如下:

QBElementType

@interface QBElementType : NSObject
   @property NSRange minRange;
   @property NSRange maxRange;
@end

@implementation QBElementType
   @synthesize minRange;
   @synthesize maxRange;
@end
QBElement

@interface QBElement : QBElementType{
    QBElementType *_type;
}
  @property (weak) QBElementType *type;
  @property NSUInteger minA;
  @property NSUInteger maxA;
@end


@implementation QBElement
  @synthesize minA = _minA;
  @synthesize maxA = _maxA;

- (QBElementType*)type
{
    return _type;
}
- (void)setType:(QBElementType*)newType
{
    [self willChangeValueForKey:@"type"];
    _type = newType;   // no need to bother with retain/release due to ARC.
    [self didChangeValueForKey:@"type"];

    /* Having the following 4 lines commented out or not changes nothing to the error
    if (!NSLocationInRange(_minA, newType.minRange))
        self.minA = newType.minRange.location;
    if (!NSLocationInRange(_maxA, newType.maxRange))
        self.maxA = newType.maxRange.location;
    */
}
@end
问题:从那时起,每当我更改元素的类型时,都会得到一个未捕获的异常:

无法更新密钥路径的观察者 “type.minRange”从,很可能是因为 在没有适当的KVO通知的情况下更改了密钥“类型” 正在发送。检查QBElement类的KVO合规性


我有什么明显的遗漏吗?为什么会出现此错误?

实现手动设置器并不意味着您必须实现手动KVO通知。只有在更新属性的支持ivar而不通过setter时,才绝对需要这些。KVO将自动用调用“真实”setter前后调用will/didChange方法的版本替换setter。如果您想自己在setter中调用它们(例如,您需要更精确地控制调用它们的时间),则需要重写方法以返回
类型
键的


我假设您现在出现错误的原因是因为KVO机器看到您连续调用了两次
[self willChangeValueForKey:@“type”]
(一次通过KVO“magic”,一次手动),而没有调用
[self didChangeValueForKey:@“type”]

您不需要显式调用
-willChangeValueForKey:
-didChangeValueForKey:
,因为您的setter命名正确。对它们的调用将通过KVC/KVO机器的魔力自动添加。问题可能是他们实际上被呼叫了两次

此外,由于
minA
maxA
似乎只是从该类型派生而来,因此您可以将它们设置为只读,并告诉KVO在
类型更改时自动通知观察者minA和maxA已更改:

@interface QBElement : QBElementType{
    QBElementType *_type;
}
  @property (weak) QBElementType *type;
  @property (readonly) NSUInteger minA;
  @property (readonly) NSUInteger maxA;
@end

@implementation QBElement

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];

    if ([key isEqualToString:@"minA"] ||
        [key isEqualToString:@"maxA"]) {
        keyPaths = [keyPaths setByAddingObject:@"type"];
    }

    return keyPaths;
}

@synthesize type;

- (NSUInteger)minA
{
    return self.type.minRange.location;
}

- (NSUInteger)maxA
{
    return self.type.maxRange.location;
}

@end

正如其他人所指出的,您可以放弃
willChangeValueForKey:
didChangeValueForKey:
调用并实现:

+ (NSSet *) keyPathsForValuesAffectingMinA
{
    return [NSSet setWithObject:@"type"];
}
当然,
minB
也一样

然后设置
minA
minB
只读属性


不同的方法主要是风格偏好。

我尝试过在没有
willChangeValueForKey:
didChange的情况下运行应用程序。
但用户界面从未收到更改通知!(旁注:minA本身就是一个变量,它可能不会以这种方式出现,因为我简化了这个问题的代码)在这种情况下,还有其他错误。您真的不需要在这样的setter中调用
willChange…
didChange…
。至于
minA
本身就是一个变量,我理解,在这种情况下,我的建议行不通。但是,也许您应该发布完整的代码,以防简化更改或消除问题。