Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MR_inContext: Thread Safety #448

Closed
gbasile opened this issue Apr 8, 2013 · 9 comments
Closed

MR_inContext: Thread Safety #448

gbasile opened this issue Apr 8, 2013 · 9 comments

Comments

@gbasile
Copy link

gbasile commented Apr 8, 2013

The recommended way to use NSManagedObject from one thread to another is to use thread confinement and pass the objectID. This is what Apple said in its documentation and what I read in almost all the internet guide about Core Data and thread confinement.

The method MR_inContext: assume that you can always access the objectID of a NSManagedObject without any problem.
I understand that this method can create problem with temporaryIds, but is it really safe to access the objectID of an NSManagedObject from a different thread?
Is there any documentation page talking about this argument?
Is it safe to use this approach also without context parenting, for example on 10.6?

@wolffan
Copy link

wolffan commented Apr 10, 2013

What I do is pass along the NSManagedObjectContext and create different ManagedObjects for each thread...

@gbasile
Copy link
Author

gbasile commented Apr 10, 2013

When you move NSManagedObject from one context to another one what do you pass? NSManagedObject or objectID?

@wolffan
Copy link

wolffan commented Apr 10, 2013

You cant pass the NSManagedObject because it's not thread safe (its tied)
Theoretically you can or: pass the ObjectContext and create new NSManagedObject or pass the ID (but I think it's a fixed id -> i never tried this approach).

@gbasile
Copy link
Author

gbasile commented Apr 10, 2013

This is what the documentation says, take a look at MagicalRecord method MR_fetchInContext: they assume that you can pass the NSManagedObject (not the objectID) in another context and only in that moment do the fetch.

@casademora
Copy link
Member

Thread confinement or not, you cannot pass NSMOs across threads. The MR_inContext: method is how you transfer one object to another thread or queue. Now, technically, this method leads you to an incorrect access pattern in that your previous NSMO is from a different thread AND context. However, when you look at what's going on "under the covers" we're not accessing any Core Data properties at this point, we're accessing a ObjectID. Accessing the properties will lead to a crash, but asking for the objectID has been a safe operation. Now, if you want to do this truly in a thread safe manner, then I suggest doing something like this:

NSManagedObjectID *objectID = [managedObject objectID];
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
  NSManagedObject *localObject = [localContext objectWithID:objectID];
  //...do your other stuff here.
}];

@ketzusaka
Copy link

@casademora @tonyarnold Do either of you have a source as to why objectID is safe? The Core Data documentation leads me to believe use of the instance itself between multiple threads is unsafe. Is it just properties that could trigger a fault if necessary?

From core data documentation:

NSManagedObject instances are not intended to be passed between queues. Doing so can result in corruption of the data and termination of the application. When it is necessary to hand off a managed object reference from one queue to another, it must be done through NSManagedObjectID instances.

@tonyarnold
Copy link
Contributor

The documentation you've quoted is the the source you're looking for. NSManagedObject instances are not safe to use across threads. You can use the NSManagedObjectID/objectID to re-retrieve a managed object in a different thread, but you must never use the managed object instance itself.

I'm not quite sure what you're asking - NSManagedObjectContexts are tied to a specific thread, thus the objects that those contexts retrieve are also tied to that thread.

I'm sure some of the older WWDC sessions around Core Data cover this in more detail, but I'm just not sure which ones now.

@ketzusaka
Copy link

@tonyarnold

The documentation says that NSManagedObject instances are not safe to use across threads, yes. That implies that accessing NSManagedObject.objectID on a different thread is not safe. For example:

let foo = Foo.mr_createEntity()
foo.managedObjectContext?.mr_saveToPersistentStore()

let otherContext = NSManagedObjectContext.MR_context()

otherContext.performBlock {
	let otherFoo = foo.mr_(in: otherContext)
}

foo has now crossed thread boundaries.

Here's another seemingly broken order:

let foo = Foo.mr_createEntity()
foo.managedObjectContext?.mr_saveToPersistentStore()

let otherContext = NSManagedObjectContext.MR_context()
let otherFoo = foo.mr_(in: otherContext)
otherContext.performBlock {
	// stuff with otherFoo
}

otherFoo is being instantiated outside of the private queue that all things in otherContext should be operated on.

The correct way to do this would be:

let foo = Foo.mr_createEntity()
foo.managedObjectContext?.mr_saveToPersistentStore()
let fooObjectID = foo.objectID

let otherContext = NSManagedObjectContext.MR_context()

otherContext.performBlock {
	let otherFoo = try! otherContext.existingObject(with: fooObjectID) as! Foo
}

@tonyarnold
Copy link
Contributor

That's correct 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants