避免对从属密钥进行不必要的KVO更新

时间:2013-01-19 07:16:38

标签: objective-c core-data key-value-observing

我有一个Core Data实体,我想存储一个CGRect值。因为没有CGRect支持核心数据所以我只是将它分成四个浮动属性:x,y,width,height。

为了使我更方便,我添加了一个自定义属性frame,它从四个float属性返回组合的CGRect。顺便说一下,我正在使用发电机。

@interface ViewableData : _ViewableData

@property (nonatomic) CGRect frame;

@end

@implementation ViewableData

- (void)setFrame:(CGRect)frame {
    if (frame.origin.x != self.xValue)
        self.xValue = frame.origin.x;
    if (frame.origin.y != self.yValue)
        self.yValue = frame.origin.y;
    if (frame.size.width != self.widthValue)
        self.widthValue = frame.size.width;
    if (frame.size.height != self.heightValue)
        self.heightValue = frame.size.height;
}

- (CGRect)frame {
    return CGRectMake(self.xValue, self.yValue, self.widthValue, self.heightValue);
}

+ (NSSet *)keyPathsForValuesAffectingFrame {
    return [NSSet setWithObjects:ViewableDataAttributes.height, ViewableDataAttributes.width, ViewableDataAttributes.x, ViewableDataAttributes.y, nil];
}

@end

它按照我的预期工作,但我发现KVO观察者得到的通知次数超过了它的需要。设置一个帧可以通知观察者4次。无论如何要避免不必要的KVO更新通知?


修改

看起来Transformable类型是一个很好的解决方案,但我仍然想知道更新依赖键路径的正确方法。另一个示例是依赖于fullNamefirstName的属性lastName。尽管如此,如果我更改fullName,我只希望观察者只收到一次而不是两次通知(一次更改firstName,一次更改lastName。另一个问题是,在第一次通知时,状态不一致,因为只执行了部分更新。

我认为我原来的例子不够好。怎么样:

@interface Person : NSManagedObject

@property (copy, nonatomic) NSString fullName;
@property (copy, nonatomic) NSString firstName;
@property (copy, nonatomic) NSString lastName;    

@end

@implementation Person
@dynamic firstName;
@dynamic lastName;

- (void)setFullName:(NSString *)fullName {
    NSArray *names = [fullName componentsSeparatedByString:@" "];
    self.firstName = names[0];
    self.lastName = names[1];
}

- (CGRect)fullName {
    return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}

+ (NSSet *)keyPathsForValuesAffectingFrame {
    return [NSSet setWithObjects:@"firstName", @"lastName", nil];
}

@end

然后,如果我做

person = // get Person object
person.firstName = @"A";
person.lastName = @"B";
// person.fullName is now "A B"

person.fullName = @"C D";

在最后一行,观察者将看到更新的拳头名称(全名为“CB”,但这样的全名根本不存在,这可能会使我的应用程序崩溃)比姓氏更新(“CD”)< / p>

1 个答案:

答案 0 :(得分:1)

关于你的第二个问题......你需要更仔细地考虑它。如果您只在firstName上有一个观察者并且更新了fullName,该怎么办?你想要通知观察者吗?

BTW:我认为您应该使用setPrimitiveValue:forKey:primitiveValueForKey:并手动触发通知..

E.g。

- (void)setFullName:(NSString *)fullName {
    NSArray *names = [fullName componentsSeparatedByString:@" "];
    [self willChangeValueForKey:@"fullName"];
    [self setPrimitiveValue:names[0] forKey:@"firstName"];
    [self setPrimitiveValue:names[1] forKey:@"lastName"];
    [self didChangeValueForKey:@"fullName"];
}

这样,您只会触发fullName的通知,但不会触发firstName和lastName的通知

编辑:好的..关于你的第一个问题(CGRect),如果你不想使用可转换的(即使这是他们常用的一个),你可以“强制”对rect的修改是作为一个整体(你永远不会调用xValue,等等..直接,你可以写你的setFrame为:

- (void)setFrame:(CGRect)frame {
    [self willChangeValueForKey:@"frame"];
    if (frame.origin.x != self.xValue)
        [self setPrimitiveValue:frame.origin.x forKey:@"xValue"];
    if (frame.origin.y != self.yValue)
        [self setPrimitiveValue:frame.origin.y forKey:@"yValue"];
    if (frame.size.width != self.widthValue)
        [self setPrimitiveValue:frame.size.width forKey:@"widthValue"];
    if (frame.size.height != self.heightValue)
        [self setPrimitiveValue:frame.size.height forKey:@"heightValue"];
    [self didChangeValueForKey:@"frame"];
}

EDIT2: 关于你的“fullName”问题...我会创建一个只有firstName和lastName的Person对象,然后是一个类别

#import "Person.h"

@interface Person (FullName)
@property (nonatomic, strong) NSString* fullName;
@end

//.m
#import "Person+FullName.h"

@implementation Person (FullName)
- (NSString*)fullName
{
   return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}

- (void)setFullName:(NSString*)fullName
{
    NSArray *names = [fullName componentsSeparatedByString:@" "];
    [self willChangeValueForKey:@"fullName"];
    [self willChangeValueForKey:@"firstName"];
    [self willChangeValueForKey:@"lastName"];
    [self setPrimitiveValue:names[0] forKey:@"firstName"];
    [self setPrimitiveValue:names[1] forKey:@"lastName"];
    [self didChangeValueForKey:@"lastName"];
    [self didChangeValueForKey:@"firstName"];
    [self didChangeValueForKey:@"fullName"];
} 
@end