CoreData: concurrency and relationship strong reference cycles - ios

I'm importing a chunk of data into CoreData, using a subclass of NSOperation to do so. The operation creates its own NSManagedObjectContext (with concurrency type NSPrivateQueueConcurrencyType) specific to the run method of the operation. During import, I need to create a relationship between an instance of NSManagedObject in the NSOperation object context (we'll call it Object A) and another object in another NSManagedObjectContext (Object B).
The relationship establishes successfully, but I'm running into a relationship strong reference cycle (Apple docs here) that I can't seem to break without compromising the integrity of the relationship. Not breaking the cycle isn't fatal, but it causes a balloon in memory footprint during import that is unacceptable. Using refreshObject:mergeChanges: and setting mergeChanges: to NO (as is typically recommended for breaking relationship strong ref cycles) screws with the data; it doesn't save correctly, and in some cases doesn't save at all.
How do you break relationship strong reference cycles when working with CoreData concurrently?

Related

Why is there a Weak References Between Managed Objects and the Context?

When i was learning how to use child contexts :
let childContext =
NSManagedObjectContext(
concurrencyType: .mainQueueConcurrencyType)
childContext.parent = coreDataStack.mainContext
let childEntry =
childContext.object(with: surfJournalEntry.objectID)
as? JournalEntry
// 3
detailViewController.journalEntry = childEntry
detailViewController.context = childContext
detailViewController.delegate = self
Author made some remark about passing both managed object and the managed object context to the detailViewController:
Note: You might be wondering why you need to pass both the managed
object and the managed object context to the detailViewController,
since managed objects already have a context variable. This is because
managed objects only have a weak reference to the context. If you
don’t pass the context, ARC will remove the context from memory (since
nothing else is retaining it) and the app will not behave as you
expect.
Well, ok, so then a read some official doc:
This means that in general you cannot rely on a context to ensure the
longevity of a managed object instance, and you cannot rely on the
existence of a managed object to ensure the longevity of a context.
Put another way, just because you fetched an object doesn’t mean it
will stay around.
But yet, i don't get what is true intention of making weak references between managed objects and the context? What is the goal do they pursue?
Managed object contexts usually use weak references to fetched objects to avoid the potential for excessive memory use. If it used strong references, and you did one or more fetches that found a large number of results, all of them would remain in memory for as long as the context existed. In many apps that would mean they'd never go away, because the context exists until the app exits. That could cause the app to use a lot of memory for objects it wasn't using anymore. Weak references mean that the managed objects are deallocated as soon as the app stops using them.
But you might want strong references in some cases, so there's a boolean property called retainsRegisteredObjects that makes the context use strong references instead. Use it if you like, but be careful of memory use.
Managed objects don't keep strong references to their contexts to avoid reference cycles. If they were strong references and you set retainsRegisteredObjects to true, you'd get a reference cycle. Each object would hold a strong reference to the other, so neither could be released from memory unless you set one of the references to nil.
You can think of managedObject as small and stupid object. They have a pointer to their context and know their objectId. When they need to know something they query the context. This has a lot of really neat advantages. If an entity was already queried in the context, a second instance of it will hit the row cache in the context and not hit the store at all.
Generally there are two kinds of context (for most core data setups): long lived main queue context that are always in memory, and short lived background contexts. For the main queue context you generally don't need to worry about the contexts leaving memory. They stay in memory for the lifetime of the application. The short lived context have a problem of leaving memory and also have a problem that they are not thread-safe. So generally they should be created in a block that is on the correct thread - used and then discarded and not pass out of the block.
I hope that explains it.

Realm and iOS Retain Cycles

Does realm properly handle entities that have a parent-child relationship (e.g. the child holds a reference to the parent)? The documentation states that Realm ignores the property modifiers (nonatomic, weak, ...), so I basically don't have control of this.
For standalone objects (i.e. objects created with [[MyClass alloc] init] which have not been added to a Realm afterwards) the normal rules apply, and you'll need to use a weak property to break the retain cycle if you're constructing graphs of standalone objects.
For objects persisted in a Realm, accessing RLMObject-subclass properties actually creates a new object each time rather than caching the accessor object. This means that the objects never actually hold references to any other in-memory objects, so there can't be a retain cycle.

Is a property reference to an NSManagedObject unsafe?

In the book Learning Core Data for iOS, the author creates several UIViewControllers which each have a property that refers to an NSManagedObjectID.
In example,
#interface LocationAtShopViewController : UIViewController
#property (strong, nonatomic) NSManagedObjectID *selectedObjectID;
// ... other properties and methods
#end
In this manner, he is able to pass an NSManagedObjectID from one controller to another and retrieve the associated NSManagedObject object using NSManagedObjectContext's existingObjectWithID:error: method.
Further, he doesn't ever set an NSManagedObject object directly (even if he already has a variable reference to it), nor does he keep a reference to the NSManagedObject object very long (instead, he retrieves it in each method that he needs it).
Is it unsafe (i.e. will cause crashes or lead to unexpected behavior in certain circumstances) to pass an NSManagedObject directly between controllers via a property reference, or simply keep a reference to it on a controller?
In example,
#interface LocationAtShopViewController : UIViewController
#property (strong, nonatomic) LocationAtShop *locationAtShop;
// ... other properties and methods
#end
Assuming a single, shared NSManagedObjectContext is used, so disregard issues caused by passing between multiple contexts, which isn't safe in general.
There is no reason to avoid using the managed object directly, provided that:
You only use the managed object with a single managed object context, and
You either
only ever use the managed object on a single thread or queue, or
Make sure to use performBlock or performBlockAndWait when working on a different queue.
Keeping only the object ID may be less error-prone since it makes it a lot harder to accidentally mix up contexts or queues. That may make it a better idea for less experienced developers, who are therefore less likely to screw things up. But it's certainly not wrong nor even especially dangerous to keep the object itself.
Is it safe ?
Yes, it's safe, with some caveats:
Per Tom Harrington's answer, in general it's safe to store an NSManagedObject as a property on a controller.
However, there are situations in which this can cause problems. Notably,
If the referenced NSManagedObject is deleted and its context is saved, the property must explicitly be set to nil.
If you do not explicitly set the property to nil, the next time you try to access an attribute on the object, it will cause a CoreData could not fulfill a fault crash.
Why did the author of Learning Core Data for iOS pass and store the NSManagedObjectID instead of the NSManagedObject?
The likely reasons may include:
If instead of storing the NSManagedObject as a property you stored the NSManagedObjectID, you don't have to worry too much about the object being deleted.
This is because NSManagedObjectContext's method existingObjectWithID:error: will return nil in the event that the object cannot be fetched, or does not exist, or cannot be faulted.
So, it's safer to store the NSManagedObjectID as a property than it is to store the NSManagedObject, if deletion of the object is possible.

Associating a NSThread with a NSManagedObject

I am creating a NSManagedObject (subclass) with certain attributes. At the same time, I am executing some code/a block that does some network operation given the attributes of my NSManagedObject. Now, some times that network operation might fail or take too long, so I want to add the ability to cancel the execution of that code/block.
I was thinking of making the code/block an NSThread, and then I have the ability to call [theThread cancel]. However, how do I associate the NSThread with my NSManagedObject, given that I cannot add properties to NSManagedObject Categories? Is it OK to just add the property to the definition of the NSManagedObject itself? Seems legal, but subsequent changes to the Core Data model would overwrite my code, I guess.
But maybe there is an entirely different and better way to accomplish what I am trying to do? Any ideas?
First, new code really should prefer GCD or NSOperationQueue over NSThread. If you find yourself using NSThread it's time to slow down and revisit your design and implementation requirements.
Second, using NSManagedObject across threads is really, really bad. If you do anything but exceedingly trivial things, it can get very difficult to do right as well.
Finally, no matter how you do your threaded network access, you should prefer to grab the data from the managed object, and pass that instead of the managed object itself
If you must access the managed object, make sure your managed object context is of either NSMainQueueConcurrencyType or NSPrivateQueueConcurrencyType and access the managed object like by invoking performBlock or performBlockAndWait using the managedObjectContext property of the managed object.
EDIT
Ok, let me check this with you. What I a currently doing is spawning a
backgroundContext, create a new NSManagedObject using performBlock,
then save that background Context, switch to the parent context (using
performblock), obtain the newly created object in that context using
existingObjectWithId:. Then, I create a NSOperation subclass, tie the
NSManagedObject (from the parent context) to that NSOperation subclass
(it's a property on the subclass) and put that operation in a
NSOperationQueue. Within that NSOperation, the NSManagedObject gets
changed. It seems to work fine, does that look ok? – user1013725
Um... maybe??? I didn't follow that. Could you please post the code? That would be much more precise and more easy to understand.
#JodyHagins So I am not using performBlock, but maybe that's ok
because the managedObjectContext is the main context? – user1013725
No.
If the main context is created with either alloc] init] or alloc] initWithConcurrencyType:NSConfinementConcurrencyType then you must use it only when you know you are running on the main thread.
If it is created with alloc] initWithConcurrencyType:NSMainThreadConcurrencyType then you must use it only when you know you are running on the main thread or within one of the performBlock methods.

retain cycle with core-data

I think I have a problem, maybe linked to a retain cycle in Core-Data.
The code is a follow, where self.image is also a NSManagedObject:
- (void)setImage:(UIImage*)image1 andThumbnail:(UIImage*)image2
{
self.image.data = UIImageJPEGRepresentation(image1, 0.85); // This is autoreleased
self.thumbnail = UIImageJPEGRepresentation(image2, 0.85); // This is autoreleased
}
Apparently, the "self.image.date =" has one retain that is never released (and I think that it is between self.image and self). Because of that the self object will never be released and hence the leak.
EDIT: so basically I have the same problem as here: https://devforums.apple.com/message/246219#246219
I use exactly the same structure where the self in the previous code corresponds to the Bar in the given link. I also have the same view controller structure. However, the refreshObject doesn't help.
I tried to use the NSManagedObjectContext refreshObject method to break the retain cycle (as suggested in Apple documentation). It has no influence on the retainCount. I'm probably not using it the right way but I can't find much information about it. If I use NSManagedObjectContext:reset: I get a crash in the root view controller when I come back to it.
Thanks!
You should not interfere with the managed object context's management of the managed objects memory.
If self.image above is a managed object and you have not written custom accessors then you have no memory management concerns from it. Any attempt to manage the context's memory manually will almost always cause more problems than it solves.
Retain counts tell you nothing except in the simplest and smallest of command line apps. Once you use frameworks like Core Data, the behind the scenes retention is so complex that the retain count often bears no relationship to what happens in your own code.
Apparently, the "self.image.date ="
has one retain that is never released
(and I think that it is between
self.image and self). Because of that
the self object will never be released
and hence the leak.
This will not happen. You do not have to kill all objects in an instance's retained attributes before killing the instance itself. If that was true, you couldn't kill an instance that shared an attribute object with a 3rd object. If they were non-managedObject instances, the self.image object can exist long after the self object dies. Only the enforcement of the entity graph by the context makes them behave different and that has nothing to do with memory management.
If you see a mysterious retain count of 1 on a managed object, that is the retain put on the object by the managed object context. As long as the context believes the managed object must exist in the entity graph, it will never release the object.
If the leak is in the Core Data stack at all, your problem is most likely in the entity graph between the self entity and the self.image entity. The entity graph is preventing one or the other from being deleted most likely by a deny or a required relationship.

Resources