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

发布于 2025-01-05 19:41:56 字数 1810 浏览 5 评论 0原文

我的模型类主要是用合成的 setter/getter 方法实现的,一切都很好。一切都很好地连接到了用户界面。后来我意识到更改一个属性应该会导致其他属性发生更改(更改 type 可能会导致 minAmaxA 发生变化),所以我手动编写了 setter type 属性的 /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

QUESTION:从那时起,每当我更改元素的类型时,我都会遇到未捕获的异常

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

中是否缺少一些明显的内容KVO 合规性?为什么我会收到此错误?

My model classes are mostly implemented with synthesized setter/getter methods and everything was fine. Everything was nicely hooked up to the user interface. I later realized that changing one property should cause other properties to change (changing type might cause changes in minA and maxA) so I manually wrote setter/getter methods for the type property. Code follows:

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

QUESTION: Since then, whenever I change an Element's Type I get an uncaught exception:

Cannot update for observer <NSTableBinder...> for the key path
"type.minRange" from <QBElement...>, most likely because the value for
the key "type" was changed without an appropriate KVO notification
being sent. Check KVO-Compliance of the QBElement class.

Is there something obvious I'm missing from KVO-Compliance? Why do I get this error?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

萌辣 2025-01-12 19:41:56

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

另外,由于 minAmaxA 似乎只是从类型派生而来,因此您可以将它们设置为只读,并告诉 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

You don't need to explicitly call -willChangeValueForKey: and -didChangeValueForKey: because your setter is named correctly. The calls to them will be added automatically through the magic of KVC/KVO's machinery. The problem may be that they're effectively being called twice.

Also, since it appears that minA and maxA are simply derived from the type, you can make them readonly and tell KVO to automatically notify observers that minA and maxA have changed any time type changes:

@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
黑白记忆 2025-01-12 19:41:56

实现手动设置器并不意味着您一定需要实现手动 KVO 通知。仅当您要更新属性的支持 ivar 而无需通过 setter 时,您才绝对需要这些。 KVO 将自动将您的 setter 替换为在调用“真实”setter 之前和之后调用 will/didChange 方法的版本。如果你想在你的设置器中自己调用这些(例如,你需要更精确地控制它们何时被调用),你需要重写方法 +automaticallyNotizesObserversForKey: 返回您的type 键为NO

我认为您现在收到错误的原因是因为 KVO 机器发现您连续两次调用 [self willChangeValueForKey:@"type"] (一次通过 KVO“magic ",一次手动),无需调用 [self didChangeValueForKey:@"type"]

Implementing a manual setter does not mean you necessarily need to implement manual KVO notifications. You only absolutely need those if you're updating the backing ivar for a property without going through the setter. KVO will automatically replace your setters with versions that call the will/didChange methods before and after the "real" setter is called. If you want to call these yourself in your setter (e.g. you need more precise control over when they're called) you need to override the method +automaticallyNotifiesObserversForKey: to return NO for your type key.

I assume the reason why you're getting an error right now is because the KVO machinery sees that you've called [self willChangeValueForKey:@"type"] twice in a row (once via KVO "magic", once manually) without calling [self didChangeValueForKey:@"type"].

时光无声 2025-01-12 19:41:56

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

+ (NSSet *) keyPathsForValuesAffectingMinA
{
    return [NSSet setWithObject:@"type"];
}

当然,对于 minB 也是如此。

然后将 minAminB 设为只读属性。

不过,不同的做法很大程度上取决于风格偏好。

As others have noted, you can ditch the willChangeValueForKey:, didChangeValueForKey: calls and implement:

+ (NSSet *) keyPathsForValuesAffectingMinA
{
    return [NSSet setWithObject:@"type"];
}

Same for minB, of course.

Then make minA and minB read-only properties.

The different ways to do it are largely stylistic preferences, though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文