Search code examples
iosobjective-cmodel-view-controllernsmanagedobjectnsmanagedobjectid

Passing NSManagedObject


I'm in the process of moving some code from my Controller to my Model in a core data application.

I've written a method that returns an NSManagedObjectID for a specific fetch request that I make regularly.

+ (NSManagedObjectID*)teamProjectWithProjectId:(NSNumber *)projectId inContext:(NSManagedObjectContext*)context
{
    NSFetchRequest *teamRequest = [[NSFetchRequest alloc] initWithEntityName:@"TeamProject"];
    NSPredicate *teamPredicate = [NSPredicate predicateWithFormat:@"teamProjectId == %@",projectId];
    teamRequest.predicate = teamPredicate;
    TeamProject *teamProject = [[context executeFetchRequest:teamRequest error:nil] lastObject];
    if (teamProject)
    {
      return [teamProject objectID];
    }
    else
    {
      return nil;
    }
    return nil;
}

And now everywhere I need to use the method I do this :

NSManagedObjectID *teamProjectMoId = [TeamProject teamProjectWithProjectId:projectID];
TeamProject *currentProject = nil;
if (teamProjectMoId)
{
    currentProject = (TeamProject*)[self.managedObjectContext objectWithID:teamProjectMoId];
}

Now this isn't too bad, it works and it respects NSManagedObject not being thread safe, while NSManagedObjectID is thread safe. And I'm happy to be able to move quite a bit of code out of my controller class into my model categories.

However, this isn't good enough. I really want to access an NSManagedObject with just one line of code in a way that respects thread safety. My ideal would be to be able to write something like this :

TeamProject *currentProject = [TeamProject magicalMethodThatHandlesThreadSafety];

Is this even possible? Or does anyone have any strategies for writing clean accessor methods for NSManagedObject's?


Solution

  • You could create a temporary MOC everywhere you need to make a lookup in the background. This is certainly thread-dafe and it's faster than using IDs (actually, it is fast, since creating a MOC in itself is fast).

    One way of doing it is through a child to your main context specifying the NSPrivateQueueConcurrencyType concurrency type:

    + (TeamProject*)teamProjectWithProjectId:(NSNumber *)projectId inContext:(NSManagedObjectContext*)context
    {
    
        NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        temporaryContext.parentContext = context;
    
        NSFetchRequest *teamRequest = [[NSFetchRequest alloc] initWithEntityName:@"TeamProject"];
        NSPredicate *teamPredicate = [NSPredicate predicateWithFormat:@"teamProjectId == %@",projectId];
        teamRequest.predicate = teamPredicate;
        TeamProject *teamProject = [[temporaryContext executeFetchRequest:teamRequest error:nil] lastObject];
    
        return teamProject;
    }
    

    Have also a look at this post for more scenarios and solutions.

    EDIT:

    To make things clearer, the above method is meant to be called from the same thread that uses the returned MO. It is just an "extension" of your approach above, in that it wraps the fetch operation using a locally-created MOC which is private to that thread, so you can use it safely.

    In other words, you can call the method above from any threads and use the returned MO in the same thread safely. The trick of using a child MOC allows you to do the fetch in any thread through the creation of a temporary private MOC.

    It is safe to refer a MOC created in another thread to create a child MOC in your current thread.

    It is still true that passing a managed object from one thread to another is not allowed.