【问题标题】:why did unmodeled property fire the fault?为什么未建模的属性会引发故障?
【发布时间】:2013-04-29 18:41:19
【问题描述】:

我们知道 NSFetchRequest 的 propertiesToFetch 可以限制我们想要的属性,它可以减少内存的占用。 如果我们这样做:

request.propertiesToFetch = [NSArray arrayWithObjects:@"nID", nil];

和:

    NSString *strID = [NSString stringWithFormat:@"%@", aPerson.nID];
cell.textLabel.text = strID;

然后调试输出(而不是触发错误):

CoreData: sql: SELECT t0.Z_ENT, t0.Z_PK, t0.ZNID FROM ZPERSON t0 ORDER BY t0.ZNID

然而,当我向 NSManagedObject Person 的子类添加一个新的未建模属性时,发生了一件奇怪的事情。 就像这样:

@interface Person (Ex)
@property (nonatomic, retain) NSString *strTempName;
@end

@implementation Person (Ex)
@dynamic strTempName;

-(void)setStrTempName:(NSString *)strTempName
{
    [self setPrimitiveValue:strTempName forKey:@"strTempName"];
}

-(NSString*)strTempName
{
    return [self primitiveValueForKey:@"strTempName"];
}

这是访问新的未建模属性的地方:

NSString *strTT = aPerson.strTempName;

然后调试输出:

2013-05-06 10:43:07.789 TestCoreData[988:c07] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZBISFORTEST, t0.ZBISINTRASH, t0.ZNID, t0.ZSTRNAME, t0.ZSTROK, t0.ZSTROK2, t0.ZSTRTEST1, t0.ZSTRTEST2, t0.ZADEPART, t0.ZAMIDDLETHUMNAIL, t0.ZANORMALPIC, t0.ZASMALLTHUMNAIL FROM ZPERSON t0 WHERE  t0.Z_PK = ? 
2013-05-06 10:43:07.790 TestCoreData[988:c07] CoreData: annotation: sql connection fetch time: 0.0010s
2013-05-06 10:43:07.791 TestCoreData[988:c07] CoreData: annotation: total fetch execution time: 0.0018s for 1 rows.
2013-05-06 10:43:07.792 TestCoreData[988:c07] CoreData: annotation: fault fulfilled from database for : 0x898a030 <x-coredata://CAD35D43-F6B2-4463-B59B-C9A3CD488935/Person/p51846>

从上面的输出消息中,我们可以找到未建模的属性,而不是 SELECT t0.Z_ENT, t0.Z_PK, t0.ZNID,生成 SELECT all properties 语句并触发错误!

但是,我已经阅读了一些关于未建模属性的消息(here is link):

因为未建模的属性只是自定义的属性 NSManagedObject 子类而不是实体,错误对象知道 什么都没有。故障对象从数据模型初始化 这样他们响应的所有键都必须在数据模型中。这 意味着故障将无法可靠地响应未建模的请求 属性。

为什么会发生奇怪的事情?

提前致谢。


@匿名,

选择 1): 应用程序崩溃,调试输出显示:

2013-05-06 14:03:05.665 TestCoreData[1794:c07] -[Person strTempName]: unrecognized selector sent to instance 0x6ba77b0
2013-05-06 14:03:30.395 TestCoreData[1794:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person strTempName]: unrecognized selector sent to instance 0x6ba77b0'

选择 2): 起初,willAccessValueForKey 等方法是发送 KVO 更改通知,就像 NSManagedObject.h 中的 cmets:

- (void)willAccessValueForKey:(NSString *)key;      // read notification
- (void)didAccessValueForKey:(NSString *)key;       // read notification (together with willAccessValueForKey used to maintain inverse relationships, to fire faults, etc.) 

其次,在选择 2) 代码之后,奇怪的事情(触发故障)在我的应用程序中仍然存在。

也感谢您的回复。

【问题讨论】:

  • 您正在开发 Mac OS X 应用程序或 iOS 应用程序?
  • @Shivan Raptor 它在 iOS 应用模拟器上。
  • 为什么在定义getter和setter的时候都使用@dynamic
  • 因为在类别的实现中不允许使用@synthesize。
  • @jerrium 如果同时定义 getter 和 setter,则不需要 @dynamic@dynamic 告诉编译器:“你看不到 getter 和 setter,但它们被定义在你看不到它们的其他地方,不要用警告来打扰我”。如果您手动定义方法,则无需使用@dynamic

标签: ios objective-c core-data


【解决方案1】:

我认为你编码太多了。 @dynamic 为您生成 getter 和 setter。你有两个选择。

1) 移除 setter/getter 调用。

2) 修改您的 getter/setter 以警告系统您正在更改值:

-(void)setStrTempName:(NSString *)strTempName
{
    [self willChangeValueForKey:@"strTempName"];
    [self setPrimitiveValue:strTempName forKey:@"strTempName"];
    [self didChangeValueForKey:@"strTempName"];
}

-(NSString*)strTempName
{
    [self willAccessValueForKey:@"strTempName"];
    NSString *tmp = [self primitiveValueForKey:@"strTempName"];
    [self didAccessValueForKey:@"strTempName"];
    return tmp;
}

【讨论】:

  • 感谢您的回复。反馈已添加到帖子顶部。
  • @dynamic 不会生成任何东西。
  • 实际上,我真正的目标是使用我的自定义方法覆盖 getter 和 setter。
【解决方案2】:

有些事情出错了:

首先:这是正确的,@dynamic 是错误的。访问器在您的代码中。没有什么动态的。

第二:原始访问器用于建模属性。你有一个“ivar 属性”。只需像在其他任何地方使用它一样使用它。 (但添加 ...access... 和 ...change... 方法。)

更改后,重新检查,是否触发了故障。

+++ 在 -didAccessValueForKey: (NSManagedObject) 的文档中,您将找到正确实现的示例:

- (NSString *)firstName
{
    [self willAccessValueForKey:@"firstName"];
    NSString *rtn = firstName;
    [self didAccessValueForKey:@"firstName"];
    return rtn;
}

【讨论】:

  • 感谢您重播。你说:原始访问器用于建模属性,这是我的重要线索。并且实例变量可以放在子类中而不是放在类别中。避免我写的代码不会被子类中的xcode替换,代码必须放在类别中。因此,我认为唯一的方法是使用类别中的关联参考,以保存实例变量。
  • @synthesize 在类别中是不可能的,因为它会改变实例的内存占用。 RTE 想通过阅读类定义来了解实例的大小。类别可以稍后加载,并且不能改变原来的内存占用。 (你需要额外的间接层来解决这个问题。)但我不明白,为什么你不能继承 NSManagedObject!?
  • 类Person是NSManagedObject的子类,由xcode生成。我想我明白你的意思。您的想法是,当我更改模型(例如添加、删除、修改属性)时,我手动维护类 Person,而不是自动生成类 Person。我说的是你的想法吗?
  • 这不应该做那么多工作,尤其是。如果您从一开始就计划好您的模型。模型的变化导致在其他地方进行更多的工作。无论如何,“ivar 属性”不受模型的影响。 (除了这种情况,你有一个名字冲突。)
  • 你的回答和 Sulthan 的回答都是真正的原因。我可以接受 2 个答案吗...我进退两难
【解决方案3】:

return [self primitiveValueForKey:@"strTempName"];

这是错误的。 primitiveValueForKey 用于访问建模属性。由于您的属性未建模,因此您会遇到所有奇怪的行为 - 核心数据会触发错误,因为它正在寻找该属性,而当它没有找到它时,它会引发异常。

你想达到什么目标?也许您正在寻找一个瞬态属性,或者您应该像在任何其他对象中一样合成该属性?

【讨论】:

  • 感谢您的回复。我想要的只是有一个地方来保存每个实例的值。无需在上下文中跟踪并保存到数据库。所以,瞬态属性不是我需要的。并且不允许在类别中进行合成。似乎关联参考可能是唯一的解决方案。
  • @jerrium 您不能按类别添加数据,即使它是托管对象。无论如何,您不能将primitiveValue 用于未建模的属性。如果您想要一个 umodeled 属性,则必须在某处添加存储(ivar、综合属性、关联引用)。也许将每个对象的数据存储在其他地方会更容易,例如在共享字典中?
  • 是的。所有实例共享的字典是我几个月前的解决方案。但是,这没有被封装。现在我的代码中采用了关联引用,并且效果很好。
  • @jerrium 请注意,如果您有多个托管对象上下文(例如,从不同线程访问数据),您的解决方案将会中断。会有不同的托管对象实例代表同一个 Core Data 实体,它们不会共享关联的引用。
  • 如果子线程中的上下文是主线程中主上下文的子,一旦子上下文保存,相关数据将被推送到父上下文。但关联的引用不会被推送.是你的意思吗?在我的应用程序中,我在子线程的单独上下文(不是子线程)上预加载了一些对象,并在主线程的主上下文中手动将数据分配给对象,从而避免了您提到的风险。
猜你喜欢
  • 2012-10-25
  • 1970-01-01
  • 2014-01-14
  • 2013-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-04
相关资源
最近更新 更多