NSManagedObject 子类导致 NSInvalidArgumentException

Posted

技术标签:

【中文标题】NSManagedObject 子类导致 NSInvalidArgumentException【英文标题】:NSManagedObject subclass causes NSInvalidArgumentException 【发布时间】:2011-04-05 20:24:16 【问题描述】:

我正在尝试子类化我的 NSManagedObject 类来封装我的获取、设置、保存例程。该类使用它自己的 managedObjectContext 和一个共享的 persistentStoreCoordinator,因为这需要是线程安全的。

所有方法调用都没有问题,但是当我尝试执行 save: 方法时,我收到以下错误:

NSInvalidArgumentException', reason: '**-[MyEntity save:]: unrecognized selector sent to instance**'

附件是给出相同错误的简化版本。

这是子类的代码:

@interface XXMyEntity : MyEntity 
@private
    NSManagedObjectContext * _managedObjectContext;


- (XXMyEntity *) init;
- (BOOL) save:(NSError **)error;

- (NSManagedObjectContext *) managedObjectContext;
- (NSPersistentStoreCoordinator *) persistentStoreCoordinator;

@end

@implementation XXMyEntity

- (XXMyEntity *) init 

    self = [NSEntityDescription insertNewObjectForEntityForName:@"MyEntity" inManagedObjectContext:[self managedObjectContext]];

    return self;


- (BOOL) save:(NSError **)error

    return [[self managedObjectContext] save:error];


- (NSManagedObjectContext *)managedObjectContext 

    if (_managedObjectContext != nil) 
        return _managedObjectContext;
    

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) 
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    
    return _managedObjectContext;


- (NSPersistentStoreCoordinator *) persistentStoreCoordinator 

    newCoreDataAppDelegate * appDelegate = (newCoreDataAppDelegate *)[[UIApplication sharedApplication] delegate];
    return appDelegate.persistentStoreCoordinator;


- (void) dealloc

    [_managedObjectContext release];

    [super dealloc];

@end

实现:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

    // Override point for customization after application launch.

    XXMyEntity * myEntity = [[XXMyEntity alloc]init];
    myEntity.id = [NSNumber numberWithInt:1];
    myEntity.title = @"My Title";

    [myEntity save:nil];

    [self.window makeKeyAndVisible];
    return YES;

我还尝试将方法签名更改为 saveEntity 之类的其他内容,假设我可能干扰了继承的方法但没有成功。

非常感谢任何帮助。

【问题讨论】:

你能像 foo 方法一样向 myEntity 发送测试消息吗? 看看有没有反应?我可以试试,但它确实响应 init 所以我认为那是一样的。 有趣的是,我添加了以下方法: - (void) something 它也抛出了错误。然而 init 确实有效。 我认为 init 有效,因为它直接传递给 MyEntity。你能用一些断点检查吗?而且你没有调用 super,初始化器看起来应该有点不同。 我解释了为什么它在我的答案中不起作用...self = [NSEntityDescription insertNewObjectForEntityForName:@"MyEntity" inManagedObjectContext:[self managedObjectContext]]; 不好 mmmmmmkay 【参考方案1】:

我认为您的主要问题是您的 init 方法要求对象已经具有托管对象上下文,即使您从未为其分配一个。当然,您不能为其分配一个,因为在init self 之前不存在。有点悖论。正如乔指出的那样,无论如何您都在使用错误的实体。

您不应该以这种方式初始化托管对象子类。只需将它们插入到上下文中,就像您将通用托管对象一样,上下文将足够智能以返回正确的子类。如果要进行自定义,请在 awakeFromInsert 方法中进行。

【讨论】:

【参考方案2】:

使用 init 函数的内存管理很差,就像在 init 之前调用 alloc 一样。此外,尽管您更改了 self 值,但它并没有使其成为 XXMyEntity 的类型,它仍然是 MyEntity,这就是您收到错误的原因。

更新

要让 XXMyEntity 工作,您需要打开 xcdatamodel 文件并将 MyEntity 类设置为 XXMyEntity。还要通读 NSManagedObject 的Subclassing Notes。

【讨论】:

+1 这样的 init 函数通常注定要失败,尤其是在没有调用 super 的情况下。【参考方案3】:

让我找出它的一些问题......

我假设 MyEntity 是 NSManagedObject 的子类。 XXXMyEntity 的意义何在?

- (NSManagedObjectContext *) managedObjectContext;

NSManagedObject 已经有一个方法-managedObjectContext。覆盖它可能会搞砸。 您正在为 XXXMyEntity 的每个实例返回不同的 NSManagedObjectContext。不同上下文中的托管对象不能在彼此之间形成关系(当然可以,但是会发生坏事)。

self = [NSEntityDescription insertNewObjectForEntityForName:@"MyEntity" inManagedObjectContext:[self managedObjectContext]];

你正在泄露旧的自我 (A)。 你没有保留新的自我 (B)。 您正在调用 [self managedObjectContext] (A),但将新对象 (B) 插入 A 的 managedObjectContext。但是,由于您已覆盖 -managedObjectContext,[b managedObjectContext] 将返回不同的上下文。这意味着可能无法正确获取对 b 的任何更改;很难说。 对+insertNewObjectForEntityForName:inManagedObjectContext: 的调用不会返回 XXXMyEntity 的实例(由 TechZen 标识)。 即使是这样,+insertNewObjectForEntityForName:inManagedObjectContext: 也会使用+alloc-init 构造托管对象。你已经覆盖了-init,所以它会导致无限递归。

newCoreDataAppDelegate * appDelegate = (newCoreDataAppDelegate *)[[UIApplication sharedApplication] delegate];

UIKit 大多不是线程安全的,只能从主线程访问。从不是主线程的线程调用-[UIApplication delegate] 可能很愚蠢。如果线程 主线程,那么您可能希望使用相同的 NSManagedObject 上下文。

如果您尝试使用 Core Data 在应用程序启动之间神奇地持久化内容,并且您在主线程上进行大部分处理,我有一些建议:

为整个应用分配一个 NSManagedObjectContext。您可能希望将其存储在应用委托或“单例”或其他内容中。

使用“便利构造函数”而不是覆盖 -init:

+(MyEntity)实体 NSManagedObjectContext * 上下文 = ...; 我的实体 * 实体 = [NSEntityDescription 插入...]; 返回实体;

注意事项:

您仍然需要一种获取实体的方法(您可以使用更方便的构造函数来做到这一点) 您仍然需要删除对象以避免数据库变得庞大。 保存速度很慢。 如果您决定在后台线程中进行保存以免阻塞 UI,您需要仔细检查与 MyEntity 相关的每段代码,因为 NSManagedObject 和 NSManagedObjectContext 不是线程-安全。

另请注意,在某些情况下,在同一个线程中有多个 MOC 是有意义的:如果您有一个带有取消按钮的“编辑”视图,您可以为编辑屏幕设置一个单独的 MOC,并且在用户取消时不保存.或者,我认为您可以使用单个 MOC 和 NSUndoManager。

Core Data Programming Guide: Technology Overview 给出了一个不错的列表,列出了核心数据是什么和不是什么;我怀疑你没有按预期使用它。

【讨论】:

以上是关于NSManagedObject 子类导致 NSInvalidArgumentException的主要内容,如果未能解决你的问题,请参考以下文章

NSmanagedobject 的子类的子类

当一对多相关的 NSManagedObject 子类发生更改时,如何更新 NSManagedObject 子类?

NSManagedObject 验证和子类

新的 NSManagedObject 子类与新的 NSObject 子类?

NSManagedObject 子类属性

子类化一个 NSManagedObject 子类