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

时间:2021-07-10 00:03:22

I have a Core Data entity that I want to store a CGRect value. Because there is no CGRect support for Core Data so I just split it into four float attributes: x, y, width, height.

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

In order to make it more convenient for me, I add a custom property frame that return the combined CGRect from the four float attributes. BTW, I am using mogenerator.

为了使我更方便,我添加了一个自定义属性框架,它返回四个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

It works as I expected but I found that KVO observer get notified too many times than it needs. Set a frame may notifier the observer 4 times. Is anyway to avoid the unnecessary KVO update notification?

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


Edit

编辑

Looks Transformable type is a good solution, but I still want to know a proper way to update depend key path. Another example is haveinga property fullName that depend on firstName and lastName. Still, if I change fullName I only expect the observer only get notified once instead of twice (once with firstName changed and once with lastName changed). Another problem is that at the first notification, the state is inconsistent because only partially update is performed.

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

I think my original example is not good enough. How about this:

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

@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

Then if i do

然后,如果我这样做

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

person.fullName = @"C D";

On the last line, the observer will see fist name updated (with full name of "C B", but such full name does not exist at all, which may crash my app) than last name updated ("C D")

在最后一行,观察者将看到更新的拳头名称(全名为“C B”,但这样的全名根本不存在,这可能会使我的应用程序崩溃)而不是更新的姓氏(“C D”)

1 个解决方案

#1


1  

Regarding your second question... imho you need to think it more carefully. What if you have an observer on firstName only and you update fullName? Do you want that the observer is notified or not?

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

BTW: I think that you should use setPrimitiveValue:forKey: and primitiveValueForKey: and manually fire the notifications..

顺便说一句:我认为你应该使用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"];
}

In this way you fire only the notification for fullName, but not for firstName and lastName

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

EDIT: ok.. regarding your first question (CGRect), if you don't want to use a transformable (even if this is one of their common use) and you can "enforce" that the modification to the rect is made as a whole (that is you never call xValue, etc.. directl, you can write your setFrame as:

编辑:好的..关于你的第一个问题(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: Regarding your "fullName" problem... I would create a Person object with only firstName and lastName, and then a category

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

#1


1  

Regarding your second question... imho you need to think it more carefully. What if you have an observer on firstName only and you update fullName? Do you want that the observer is notified or not?

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

BTW: I think that you should use setPrimitiveValue:forKey: and primitiveValueForKey: and manually fire the notifications..

顺便说一句:我认为你应该使用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"];
}

In this way you fire only the notification for fullName, but not for firstName and lastName

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

EDIT: ok.. regarding your first question (CGRect), if you don't want to use a transformable (even if this is one of their common use) and you can "enforce" that the modification to the rect is made as a whole (that is you never call xValue, etc.. directl, you can write your setFrame as:

编辑:好的..关于你的第一个问题(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: Regarding your "fullName" problem... I would create a Person object with only firstName and lastName, and then a category

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