开发者

NSManagedObject subclass causes NSInvalidArgumentException

I am trying to subclass my NSManagedObject classes to encapsulate my get, set, save routines. The class uses its own managedObjectContext with a shared persistentStoreCoordinator since this will need to be thread safe.

All the method call without issue, but when I try to execute the save: method, I get the following error:

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

Attached is a simplified version that gives the same error.

Here's the code for the subclass:

@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];
        [_managedObjec开发者_JS百科tContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

- (NSPersistentStoreCoordinator *) persistentStoreCoordinator 
{
    newCoreDataAppDelegate * appDelegate = (newCoreDataAppDelegate *)[[UIApplication sharedApplication] delegate];
    return appDelegate.persistentStoreCoordinator;
}

- (void) dealloc
{
    [_managedObjectContext release];

    [super dealloc];
}
@end

The implementation:

- (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;
}

I also tried changing the method signature to something else like saveEntity assuming that maybe I was interfering with an inherited method with no success.

Any help is greatly appreciated.


I think your major problem is that your init method requires that the object already have a managed object context even though you never assign it one. Of course, you can't assign it one because prior to init self doesn't exist. Bit of paradox. As Joe pointed out, you are using the wrong entity anyway.

You should not initialize managed object subclasses in this manner. Just insert them into the context as you would a generic managed object and the context will be smart of enough to return the right subclass. If you want to do customization, do so in the awakeFromInsert method.


It is poor memory management using the init function the way you are as a call to alloc precedes init. Also although you are changing the value self it does not make it the type of XXMyEntity, it is still MyEntity and that is why you are getting the error.

Update

To get XXMyEntity to work you need open up your xcdatamodel file and set your MyEntity class to XXMyEntity. Also read through the Subclassing Notes of NSManagedObject.


Let me find a few things wrong with it...

  • I'm assuming MyEntity is a subclass of NSManagedObject. What's the point of XXXMyEntity?

  • - (NSManagedObjectContext *) managedObjectContext;

    • NSManagedObject already has a method -managedObjectContext. Overriding it is likely to mess things up.
    • You're returning a different NSManagedObjectContext for every instance of XXXMyEntity. Managed objects in different contexts cannot form relationships between each other (well, they can, but Bad Things happen).
  • self = [NSEntityDescription insertNewObjectForEntityForName:@"MyEntity" inManagedObjectContext:[self managedObjectContext]];

    • You're leaking the old self (A).
    • You're not retaining the new self (B).
    • You're calling [self managedObjectContext] (A), but inserting the new object (B) into A's managedObjectContext. However, since you've overridden -managedObjectContext, [b managedObjectContext] will return a different context. This means any changes to b might not be picked up correctly; it's hard to tell.
    • The call to +insertNewObjectForEntityForName:inManagedObjectContext: does not return an instance of XXXMyEntity (as identified by TechZen).
    • Even if it did, +insertNewObjectForEntityForName:inManagedObjectContext: constructs the managed object using +alloc and -init. You've overridden -init, so it'll result in infinite recursion.
  • newCoreDataAppDelegate * appDelegate = (newCoreDataAppDelegate *)[[UIApplication sharedApplication] delegate];

    • UIKit is mostly not thread-safe and should only be accessed from the main thread. Calling -[UIApplication delegate] from a thread that is not the main thread is potentially silly. If the thread is the main thread, then you probably want to be using the same NSManagedObject context anyway.

If you're attempting to use Core Data to magically persist things between app launches, and you do most of your processing on the main thread, I have some suggestions:

  • Allocate a single NSManagedObjectContext for the whole app. You probably want to store this in the app delegate or a "singleton" or something.
  • Use "convenience constructors" instead of overriding -init:

    +(MyEntity)entity { NSManagedObjectContext * context = ...; MyEntity * entity = [NSEntityDescription insert...]; return entity; }

With caveats:

  • You still need a way to fetch entities (you might do this with more convenience constructors)
  • You still need to delete objects to avoid the database becoming huge.
  • Saving is pretty slow.
  • If you decide to do the saving in a background thread so it doesn't block the UI, you need to carefully go over every piece of code that touches MyEntity because NSManagedObject and NSManagedObjectContext are not thread-safe.

Also note that yhere are cases where having multiple MOCs in the same thread makes sense: If you have an "edit" view with a cancel button, you can have a separate MOC for the edit screen and not save if the user cancels. Alternatively, I think you can use a single MOC and NSUndoManager.

The Core Data Programming Guide: Technology Overview gives a decent list of what core data is and isn't; I suspect you're not using it as it's intended.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜