即使定义了依赖键,我是否也必须手动更新瞬态属性?
我开始使用瞬态属性一点不久前,我以为我很好地理解了它们,但是这个的行为并不像我想象的那样。它在数据模型中被定义为具有未定义类型的瞬态属性,并被声明为属性:
@property (nonatomic, readonly) NSSet * manufacturers;
该属性被声明为依赖于该对象上的另一个 1:M 关系:
+ (NSSet *) keyPathsForValuesAffectingManufacturers
{
return [NSSet setWithObject:@"lines"];
}
我已经为该瞬态实现了一个相当标准的 getter属性:
- (NSSet *) manufacturers
{
[self willAccessValueForKey:@"manufacturers"];
NSSet *mfrs = [self primitiveManufacturers];
[self didAccessValueForKey:@"manufacturers"];
if (mfrs == nil)
{
NSMutableSet *working = [NSMutableSet set];
/* rebuild the set of manufacturers into 'working' here */
mfrs = working;
[self setPrimitiveManufacturers:mfrs];
}
return mfrs;
}
不起作用的部分是,当从 lines
关系中添加/删除对象时,manufacturers
的缓存原始值似乎不会被清除。这个物体。当您添加/删除行时,肯定会调用manufacturers访问器,但是[selfprimitiveManufacturers]仍然返回旧的缓存值(这显然是非零),因此代码没有调用重建当前的制造商集,并且我最终得到了过时的返回值。
我是否误解了缓存瞬态属性值应该如何工作?或者,当通过某种 KVO 回调更改行时,我是否应该手动使缓存值无效,以便下次访问 manufacturers
时执行“if”内的更新代码?
非常感谢任何帮助。
编辑:我知道我基本上是在模拟与此瞬态属性的 1:M 关系(即瞬态关系,因为需要更好的词),并且我我还知道 文档警告不要使用 setPrimitiveValue:forKey:
来建立关系:
您通常应该使用此方法 仅修改属性(通常 短暂的),而不是关系。如果你 尝试设置一对多关系 新的 NSMutableSet 对象,它将 (最终)失败。在不寻常的 您需要修改的事件 使用这种方法的关系,你 首先使用现有的集合 PrimitiveValueForKey:(确保 方法不返回 nil),创建一个 可变副本,然后修改该副本
但是,由于我没有在真实 1:M 关系上设置原始值,而只是在模拟瞬态关系上设置原始值,因此我假设此警告不适用于我的用例。
编辑 #2: 因为我只是尝试跟踪何时从 lines
集中添加或删除对象(这是 manufacturers< 的预期值的唯一方法) /code> 可以更改),而不是尝试跟踪
lines
集中已有对象的属性更新,我认为我不需要像这样重量级的东西:https://github.com/mbrugger/CoreDataDependentProperties
解决方案:回答如下,我实现了以下解决方案,以在更新lines
时清空manufacturers
集。任何人都可以看到这方面的任何问题,或者建议任何更有效的实施方法吗?此操作必须快速,这一点很重要,因为它是在主线程中完成的,并且会明显延迟与 line
更新完成同时发生的 UI 更新。
- (void) awakeFromFetch
{
[super awakeFromFetch];
[self addObserver:self forKeyPath:@"lines" options:0 context:nil];
}
- (void) awakeFromInsert
{
[super awakeFromInsert];
[self addObserver:self forKeyPath:@"lines" options:0 context:nil];
}
- (void) didTurnIntoFault
{
[self removeObserver:self forKeyPath:@"lines"];
[super didTurnIntoFault];
}
- (void) observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([object isEqual:self] && [keyPath isEqualToString:@"lines"])
{
[self setPrimitiveManufacturer:nil];
}
else
{
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
I started working with transient properties a little while ago, and thought I understood them pretty well, but this one isn't behaving how I thought it would. It is defined in the data model as a transient property with undefined type, and is declared as a property thus:
@property (nonatomic, readonly) NSSet * manufacturers;
This property is declared to be dependent on another 1:M relationship on this object:
+ (NSSet *) keyPathsForValuesAffectingManufacturers
{
return [NSSet setWithObject:@"lines"];
}
I have implemented a fairly standard getter for this transient property:
- (NSSet *) manufacturers
{
[self willAccessValueForKey:@"manufacturers"];
NSSet *mfrs = [self primitiveManufacturers];
[self didAccessValueForKey:@"manufacturers"];
if (mfrs == nil)
{
NSMutableSet *working = [NSMutableSet set];
/* rebuild the set of manufacturers into 'working' here */
mfrs = working;
[self setPrimitiveManufacturers:mfrs];
}
return mfrs;
}
The part that isn't working is that the cached primitive value for manufacturers
doesn't appear to be wiped out when objects are added / removed from the lines
relationship on this object. The manufacturers
accessor is definitely called when you add / remove lines, but [self primitiveManufacturers]
still returns the old cached value (which is obviously non-nil), so the code to rebuild the current set of manufacturers isn't called and I end up with an out-of-date return value.
Am I misunderstanding something about how cached transient property values are supposed to work? Or am I supposed to manually nullify the cached value when lines are changed through some kind of KVO callback so that the update code inside the "if" is executed the next time manufacturers
is accessed?
Any help much appreciated.
Edit: I am aware that I'm basically simulating a 1:M relationship with this transient property (i.e. a transient relationship, for want of a better word), and I'm also aware that the documentation warns against using setPrimitiveValue:forKey:
for relationships:
You should typically use this method
only to modify attributes (usually
transient), not relationships. If you
try to set a to-many relationship to a
new NSMutableSet object, it will
(eventually) fail. In the unusual
event that you need to modify a
relationship using this method, you
first get the existing set using
primitiveValueForKey: (ensure the
method does not return nil), create a
mutable copy, and then modify the copy
However, since I am not setting a primitive value on a real 1:M relationship, only my simulated transient one, I had assumed that this warning did not apply to my use case.
Edit #2: Since I am only trying to track when objects are added or removed from the lines
set (this is the only way the expected value of manufacturers
can change), as opposed to trying to track updates to attributes on the objects that are already in the lines
set, I don't think I should need anything as heavyweight as this: https://github.com/mbrugger/CoreDataDependentProperties
Solution: In light of the answer below, I implemented the following solution to blank out the manufacturers
set when lines
is updated. Can anyone see any issues with this, or suggest any more efficient ways of implementing it? It is important that this operation be fast because it is done in the main thread and will visibly delay UI updates that happen at the same time as the line
updates are done.
- (void) awakeFromFetch
{
[super awakeFromFetch];
[self addObserver:self forKeyPath:@"lines" options:0 context:nil];
}
- (void) awakeFromInsert
{
[super awakeFromInsert];
[self addObserver:self forKeyPath:@"lines" options:0 context:nil];
}
- (void) didTurnIntoFault
{
[self removeObserver:self forKeyPath:@"lines"];
[super didTurnIntoFault];
}
- (void) observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([object isEqual:self] && [keyPath isEqualToString:@"lines"])
{
[self setPrimitiveManufacturer:nil];
}
else
{
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
keyPathsForValuesAffectingManufacturers并不意味着当线路改变时制造商的值会被删除,而是意味着当线路改变时观察制造商的对象应该得到通知,因为改变线路会自动改变制造商而不会触发通知。当一个属性基于另一个属性获取其值时使用此属性。针对你的情况,换线的时候需要更换厂家。
keyPathsForValuesAffectingManufacturers does not mean that the value of manufacturers will be deleted when lines is changed, it means that objects observing manufacturers should be notified when lines changes, because changing lines automatically changes manufacturers without triggering notifications. This is used when one property gets its value based on another. For your situation, you need to change manufacturers when you change lines.