Let's say we have two entities in a Core Data model: Departments and Employees.
The Department has a one-to-many relationship to Employees.
I have the following ManagedObjectContexts:
- Root: connected to the Persistent Store Coordinator
- Main: context with parent Root
When I want to create an Employee I do the following:
- I have a Department in the Main context
- I create an Employee in the Main context
- I assign the Department to the Employee's department property
- I save the Main context
- I save the Root context
This creates a retain cycle both in the Main context and in the Root context.
If I did this without a child context (all in the Root context), then I could break the retain cycle by calling refreshObject:mergeChanges on Employee. In my situation with the two contexts I could still use that method to break the cycle on the Main context, but how am I going to break the cycle on the Root context?
Side note: this is a simple example to describe my problem. In Instruments I can clearly see the number of allocations growing. In my app I have contexts that go deeper than one level, causing an even greater problem, because I get a new entity allocation with retain cycle per context I'm saving.
Update 15/04: NSPrivateQueueConcurrencyType vs NSMainQueueConcurrencyType
After saving both contexts I can perform refreshObject:mergeChanges on the Main context with the Department object. This will, as expected, re-fault the Department object, break the retain cycle and deallocate the Department and Employee entities in that context.
The next step is to break the retain cycle that exists in the Root context (saving the Main context has propagated the entities to the Root context). I can do the same trick here and use refreshObject:mergeChanges on the Root context with the Department object.
Weird thing is: this works fine when my Root context is created with NSMainQueueConcurrencyType (all allocations are re-faulted and dealloced), but doesn't work when my Root context is created with NSPrivateQueueConcurrencyType (all allocations are re-faulted, but not dealloced).
Side note: all operations for the Root context are done in a performBlock(AndWait) call
Update 15/04: Part 2
When I do another (useless, because there are no changes) save or rollback on the Root context with NSPrivateQueueConcurrencyType, the objects seem to be deallocated. I don't understand why this doesn't behave the same as NSMainQueueConcurrencyType.
Update 16/04: Demo project
I've created a demo project: http://codegazer.com/code/CoreDataTest.zip
Update 21/04: Getting there
Thank you Jody Hagings for your help!
I'm trying to move the refreshObject:mergeChanges out of my ManagedObject didSave methods.
Could you explain to me the difference between:
[rootContext performBlock:^{
[rootContext save:nil];
for (NSManagedObject *mo in rootContext.registeredObjects)
[rootContext refreshObject:mo mergeChanges:NO];
}];
and
[rootContext performBlock:^{
[rootContext save:nil];
[rootContext performBlock:^{
for (NSManagedObject *mo in rootContext.registeredObjects)
[rootContext refreshObject:mo mergeChanges:NO];
}];
}];
The top one doesn't deallocate the objects, the bottom one does.
I looked at your sample project. Kudos for posting.
First, the behavior you are seeing is not a bug... at least not in Core Data. As you know, relationships cause retain cycles, that must be broken manually (documented here: https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdMemory.html).
Your code is doing this in didSave:. There may be better places to break the cycle, but that's a different matter.
Note that you can easily see what objects are registered in a MOC by looking at the registeredObjects property.
Your example, however, will never release the references in the root context, because processPendingEvents is never called on that MOC. Thus, the registered objects in the MOC will never be released.
Core Data has a concept called a "User Event." By default, a "User Event" is properly wrapped in the main run loop.
However, for MOCs not on the main thread, you are responsible for making sure user events are properly processed. See this documentation: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html, specifically the last paragraph of the section titled Track Changes in Other Threads Using Notifications.
When you call performBlock the block you give it is wrapped inside a complete user-event. However, this is not the case for performBlockAndWait. Thus, the private-context MOC will keep those objects in its registeredObjects collection until processPendingChanges is called.
In your example, you can see the objects released if you either call processPendingChanges inside the performBlockAndWait or change it to performBlock. Either of these will make sure that the MOC completes the current user-event and removes the objects from the registeredObjects collection.
Edit
In response to your edit... It is not that the first one does not dealloc the objects. It's that the MOC still has the objects registered as faults. That happened after the save, during the same event. If you simply issue a no-op block [context performBlock:^{}] you will see the objects removed from the MOC.
Thus, you don't need to worry about it because on the next operation for that MOC, the objects will be cleared. You should not have a long-running background MOC that is doing nothing anyway, so this really should not be a big deal to you.
In general, you do not want to just refresh all objects. However, if you do you want to remove all objects after being saved, then your original concept, of doing it in didSave: is reasonable, as that happens during the save process. However, that will fault objects in all contexts (which you probably don't want). You probably only want this draconian approach for the background MOC. You could check object.managedObjectContext in the didSave: but that's not a good idea. Better would be to install a handler for the DidSave notification...
id observer = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:rootContext
queue:nil
usingBlock:^(NSNotification *note) {
for (NSManagedObject *mo in rootContext.registeredObjects) {
[rootContext refreshObject:mo mergeChanges:NO];
}
}];
You will see that this probably gives you what you want... though only you can determine what you are really trying to accomplish.
The steps you describe above are common tasks you perform in Core Data. And the side effects are clearly documented by Apple in Core Data Programming Guide: Object Lifetime Management.
When you have relationships between managed objects, each object
maintains a strong reference to the object or objects to which it is
related. This can cause strong reference cycles. To ensure that
reference cycles are broken, when you're finished with an object you
can use the managed object context method refreshObject:mergeChanges:
to turn it into a fault.
The objects maintain strong references to each other only when they are not faults, but live instances NSManagedObject. With nested contexts, saving objects in your main context, those changes should be propagated to your root context. However, unless you fetch them in your root context, no retain cycles should be created. After you're done saving your main context, refreshing those objects should be all that is needed.
Regarding memory footprint in general:
If you feel the allocations are growing out of hand, you could try structure your code so that you perform tasks, that cause firing faults to a large amount of objects, in separate context which you discard when you're done with the task.
Also, if you are using undo manager,
The undo manager associated with a context keeps strong references to
any changed managed objects. By default, in OS X the context’s undo
manager keeps an unlimited undo/redo stack. To limit your
application's memory footprint, you should make sure that you scrub
(using removeAllActions) the context’s undo stack as and when
appropriate. Unless you keep a strong reference to a context’s undo
manager, it is deallocated with its context.
Update #1:
After experimenting with allocations instruments and piece of code written especially to test this, I can confirm that root context does not free up memory. Either this is a framework bug or it is intended by design. I found a post here describing the same issue.
Invoking [context reset] after [context save:] did release the memory as it should. Also I noticed, that prior to save, the root context had all the objects I inserted via child context in its [context insertedObjects] set. Iterating over them and doing [context refreshObject:mergeChanges:NO] did re-fault the objects.
So there seem to be few workarounds, but whether this is a bug and will be fixed in some upcoming release, or it will stay as it is by design, I do not know.
When saving to the root context, the only one holding a strong reference to the objects is the root context itself, so, if you just reset it, the objects will be deallocated in the root context.
You save flow should be:
-save main
-save root
-reset root
I did not reset or refreshed the objects in the main context, and even though, no leaks or zombies were found. memory seems to be allocated and deallocated after the save and reset of the parent context.
Related
I have a strange problem here. I am not getting the callbacks for "Update" operations on my NSManagedObject, but where as any objects inserted into or removed from the collection of that entity type would trigger the delegate callbacks.
Before I proceed with the question further, I would like to inform about my setup:
NSFetchedResultsController is properly configured. Made sure that
the property which is being modified externally is not any of the
sort keys for the fetchedResultsController as required by this Apple
documentation:
An update is reported when an object’s state changes, but the changed
attributes aren’t part of the sort keys.
There is only single managed object context in which these modifications are happening.
Since insert and delete operations are being reported to the
delegate, I presume there is something fishy about the Update
operations
I was drilling down the Restkit code with help of RKLogs to see where exactly the mapping happens and where the coredata object is being updated to find out the reason why am not getting the update delegate callbacks.
In the class RKManagedObjectMappingOperation -performMapping method, Mr. Blake Watters has documented the reason why MOC callbacks are not triggered upon updates:
- (BOOL)performMapping:(NSError **)error
{
BOOL success = [super performMapping:error];
if ([self.objectMapping isKindOfClass:[RKManagedObjectMapping class]]) {
/**
NOTE: Processing the pending changes here ensures that the managed object context generates observable
callbacks that are important for maintaining any sort of cache that is consistent within a single
object mapping operation. As the MOC is only saved when the aggregate operation is processed, we must
manually invoke processPendingChanges to prevent recreating objects with the same primary key.
See https://github.com/RestKit/RestKit/issues/661
*/
[self connectRelationships];
}
return success;
}
But I cannot for the life of myself figure out how to fix this? Coz it was done purposefully?
Has anyone faced same problem? How do I fix it?
Thanks,
Raj Pawan
There was 1 mistake from my part and 1 other thing (Ignorance) which I was not aware of and due to which I faced this problem:
Mistake:
Despite many discussion all over the forums, I was wrong to state my second listed item:
There is only single managed object context in which these
modifications are happening.
I had wrongly logged and found out that I was in the same context! So dumb of me!
Ignorance:
I did some digging through the RK code thinking something fishy is going on there, checked Blake Watters' comments and his commit 4b394f8c1e1f to see if the earlier code there (call to -processPendingChanges) which is now removed was causing any issue and not letting the delegate to be informed about updates.
Found out that this was indeed on a separate thread and yes it had its own MOC which I had missed out. Next thing to do was simple, I implemented the mechanism to merge changes of a MOC from different thread into main MOC. But this did not work either!
The reason turns out that I am in a very initial stages of the development of application. I am just mapping the json response with coredata objects using RestKit and I am nowhere utilising it! I was just logging the coredata objects in GDB and they remained in their fault state always! I was relying upon the -objectLoader callbacks and the NSNotification object to see that there was indeed an Update available. At some point while testing I happened to log a property of the managed object which is changed in the Notification callback before it was merged back to the main MOC. This faulted the managed object and loaded all properties of the managed object. Now when the secondary thread MOC changes were merged with the main thread MOC, my NSFetchedResultsControllerDelegate callbacks started triggering.
thanks in advance for any help. I've spent today battling this and I think that there is something seriously wrong with my understanding of how the framework works.
I'm working on a core data application where the entities have a parent/child relationship. The application creates an NSManagedObjectContext (MOC) on startup. When the application is run for the first time it uses an async block to import the contents of a plist into a second MOC (the root node is obtained from the main MOC using the URI and -managedObjectIDForURIRepresentation:), just before the block finishes it saves the second context.
In my data controller I subscribe to NSManagedObjectContextDidSaveNotification and the following code is run when the notification is sent:
- (void)backgroundContextDidSave:(NSNotification *)notification {
if(![notification.object isEqual:self.managedObjectContext]){
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification]; ;
}
}
I've done a sanity check on this code and sure enough, when the second MOC saves, this is called from the thread executing the block, it is then deferred, and run from the main thread. The notification object contains all objects imported in the second MOC, including the two we're going to be dealing with next.
When this has finished I run the following code which is in a method of the NSManagedObject subclass the objects belong to, it is simply meant to remove a child from its parent:
TreeEntry *oldParent=self.parent; //keep a pointer to the old parent around so we can delete self from the children
// These next four lines are a sanity check to make sure that both objects are on the same MOC we're saving
NSManagedObjectContext *selfContext=self.managedObjectContext;
NSManagedObjectContext *parentContext=self.parent.managedObjectContext;
NSManagedObjectContext *sharedContext=[[DataController sharedDataController] managedObjectContext];
assert([selfContext isEqual:parentContext] && [selfContext isEqual:sharedContext]);
// now we fault the two objects to make sure we can not possibly have them or any changes
// to them in the state of the main MOC, by this time the second MOC is long gone
[sharedContext refreshObject:self.parent mergeChanges:NO];
[sharedContext refreshObject:self mergeChanges:NO];
// up to this point, sharedContex.insertedObjects, sharedContext.updatedObects and sharedContext.deletedObjects
// have all contained no objects at all. None of the above was necessary as the MOC held no changes at all
[sharedContext saveChanges]; // we save it to, well, just to make sure I guess, I may be going crazy
// Now we carry out two changes to the objects, problem occurs if only one change is carried out,
// I'm showing both to show that there relationship is being kept consistent and valid
self.parent=nil;
[oldParent removeChild:self];
// When the next line is run the save fails with a merge conflict
[sharedContext saveChanges];
The last save fails with a Cocoa error 133020, which is a merge fail. The two NSMergeConflicts in the error relate to the entries we're dealing with (self and self.parent).
I just don't understand how that can be. The objects have no state when they are modified so they MUST be getting loaded from the store. Two simple changes are made and then when they are saved straight afterwards a merge conflict occurs. How can that be? nothing else has messed with the store and we've just loaded the objects from it.
I know I can change the merge policy but I don't want to do it without understanding what's going on.
Any ideas? I'm sure it's just that my mental model if what's going on is wrong, but I've not been able to set it right all day!
Ok, it WAS a fundamental misunderstanding on my part of how the framework works, or to be more accurate, the NSManagedStoreCoordinator cache.
When I save the background context the changes go to disk, but apparently the NSManagedStoreCoordinator (which both contexts share) doesn't update or invalidate its cache.
When I refresh the objects in the main MOC the data used to re-populate them comes from the cache, which still has the old data. It doesn't re-load from the disk. The solution was to use [MOC setStalenessInterval:0.0] to force re-loading from disk.
I've created a program that uses core data and it works beautifully.
I've since attempted to move all my core data methods calls and fetch routines into a class that is self contained. My main program then instantiates that class and makes some basic method calls into that class, and the class then does all the core data stuff behind the scenes. What I'm running into, is that sometimes I'll find that when I grab a managed object from the context, I'll have a valid object, but its properties have been deallocated, and I'll cause a crash. I've played with the zombies and looked for memory leaks, and what I have gathered is it seems that the run loop is probably responsible for deallocating the memory, but I'm not sure.
Is there a way to determine if that memory has been deallocated and force the core data to get it back if I need to access it? My managedObjectContext never gets deallocated, and the fetchedResultsController never does, either.
I thought maybe I needed to use the [managedObjectContext refreshObject:mergeData:] method, or the [managedObjectContext setRetainsRegisteredObjects:] method. Although, I'm under the impression that last one may not be the best bet since it will be more memory intensive (from what I understand).
These errors only popped up when I moved the core data calls into another class file, and they are random when they show up.
Any insight would be appreciated.
-Ryan
Sounds to me like you are not retaining objects you want to keep hanging around. If you are doing something like this:
NSArray *array = [moc executeFetchRequest:request error:&error];
you do not own the returned array and it will most likely disappear when the current autorelease pool is drained. This will occur when the run loop finishes processing the current event.
All this is speculation. If you want a proper answer, you need to post your code.
It's hard to know what the problem is based on your description, but you might want to look at the Core Data memory management guide. You shouldn't have to worry about memory management for managed objects and their entities (they're fetched and faulted automatically). When you talk about "properties," do you mean custom properties backed by ivars? If so, these should be released in didTurnIntoFault and allocd as needed (probably in the accessor).
I was struggling with a similar issue. I'm using a managed object class and want to set its properties dependent on user input. But the sometimes the properties and sometimes the whole managed object were deallocated.
After reading the Apple documentation http://developer.apple.com/library/IOs/#documentation/Cocoa/Conceptual/CoreData/Articles/cdMemory.html the chapter "The Role of the Managed Object Context" I learned that managed objects are released each run loop completes.
And there is the golden advice to set
[myMangedObjectContext setRetainsRegisteredObjects:YES];
(I had to set it in the init method (initWithNibName for me) of my view controller.)
You should also regard to retain only the objects you need to as explained in the documentation. But read it yourself.
If I'm not right please correct me.
I also made a class that handles all my CoreData fetching and stuff. I ran into a couple of gotcha's, so here are some tips. (If I am making any memory management errors in these examples, please let me know.)
Two things:
1) Made a "fetchFiredObject" method in the CoreData handler class. So when I want to get a managedObject that has all its variables and is a "fully feathered bird" so to speak, instead of doing:
aManagedObject *myManagedObject = [myCoreDataHandler.managedObjectStorageArray objectAtIndex:1];
int x = myManagedObject.someVariable.intValue;
instead I do:
aManagedObject *myManagedObject = [myCoreDataHandler fetchFiredObjectAtIndex:1];
int x = myManagedObject.someVariable.intValue;
And in myCoreDataHandler's fetchFiredObjectAtIndex:i method, we're going into the array, finding the object key at index i, then doing a fetchRequest for that object key, and returning the freshly-fetched managedObject so that it won't have been faulted or deallocated, etc. :D
2) When I create a new child viewController, I populate its "myCoreDataHandler" value from the parent upon creation. However, this happens on a subsequent line of code after the line of code that creates the new viewController. Therefore, any code in the child's viewDidLoad that tries to use myCoreDataHandler's methods will return empty objects because viewDidLoad completes before the parent's next line of code where it sets the values of globals in the child object. So make sure you are not accessing your "Core Data handling object" from within viewDidLoad or anything local methods called by viewDidLoad! Instead call them from the parent after creating the new viewController.
I have an iPhone app which deals with a subset of 25,000 places at any given time.
I'd like to maintain a cache of places so that I know that if one part of my application updates a place, every other part that knows about that place sees the update.
My naive implementation is create an NSMutableSet to store references to the cached places.
Methods that find new places will first check the cache and return the cached object or if the place isn't in the cache, they will create a new place object and add it to the cache.
The problem is how do I release objects that are no longer needed?
The NSMutableSet will retain the place so the retainCount will never go to zero and dealloc will never be called.
Is there a kosher method to handle the release scenario? Is there some other pattern for doing this that I'm not aware of.
(and CoreData is not an option at this point, but I understand that it handles this).
Thank you,
On the desktop you can do this with NSPointerSet, on the iPhone it is a bit more difficult.
You can use CoreFoundation to create a non-retaining set if you really want to:
//Default callbacks
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
//Disable retain and release
callbacks.retain = NULL;
callbacks.release = NULL;
cachedPlaces = (NSMutableSet *)CFSetCreateMutable(kCFAllocatorDefault,
0,
&callbacks);
That makes a non-retaining set. Note that you still need to remove the objects from the set when they are released, otherwise you will have stale pointers in your set that will cause you to crash on a deref. So in the objects you are adding to the set you need a dealloc something like this:
- (void)dealloc {
[cachedPlaces removeObject:self];
[super dealloc];
}
This is only really suitable for a purely in memory cache of extant references, if you need to also move stuff to and from the disk then CoreData basically takes care of all of this for you.
You could use NSMutableSet as cache and rely on the fact that any object it contains with a retain count of 1 is only owned by the cache. Thus any object with a retain count of 1 should be removed, this is easily done:
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"retainCount > 1"];
[cachedPlaces filterUsingPredicate:predicate];
Do this on a timer, or whenever a a place is added and/or removed if that is not too often. You could also make the predicate a static to avoid generating anew instance every time.
Use Core Data if you can deploy to iPhoneOS 3.0 or greater, or use SQLite for iPhoneOS 2.x. Either way you'll be able to use a database to store your data, and you'll be able to do queries to get fresh data sets.
As of iOS 4.0, the proper way to do this is to use an NSCache. It can automatically purge objects when the system sends a low-memory warning. You can also set limits on the cache size.
NSCache Class Reference
This question is old, but I recently came across a similar issue. I believe using NSHashTable can fit the requirements of this situation.
NSHashTable works better than NSCache or NSSet because it can hold weak references to your instances, so that once all references are dropped the instance is automatically removed from the NSHashTable thanks to ARC. This works as a kind of 'Just-in-Time' caching method, only retaining objects held elsewhere by strong references.
Considering that you have multiple parts of the application that could be adding references, using the NSHashTable as the Flyweight Pool of the Flyweight Pattern could be useful. The second part of the Flyweight pattern requires a factory, the factory would be responsible for checking for the instance in the pool, adding it to the pool if it's not found, then returning the pooled instance.
I have a method that returns a NSMutableArray:
//implementation of class Students
-(NSMutableArray *)listOfStudents {
NSMutableArray *students = [[NSMutableArray alloc] init];
//add objects to students and other operations here
return students;
}
The problem here is, when and where do I release the object students? If it was an instance variable, I would add a [students release] in the dealloc method, but it's not. The class Students allocated the object, so it owns the new NSMutableArray and it must release it, right?
Since I can't release it before return it, the only option I see here is... return it as:
return [students autorelease];
But it doesn't feel right to use autorelease objects on the iPhone. This method will be called many times... and I would like to release the memory as soon as possible. Also, the autorelease pool is in the main function and it looks like it will take a while to clean the mess.
How would you do it?
I tend to avoid using autorelease wherever I can within my iPhone applications, but there are some cases where it doesn't hurt you that much. If the array that you're generating via this method will be retained for a reasonably long duration, then you won't be sacrificing any performance or memory usage by returning it as an autoreleased result.
However, if you will be using and discarding the returned object quite frequently, you may wish to do something different. I have a naming convention with my methods that if something is prefixed by "generate", like generateStudentsArray, it returns an object that is owned by the caller and must be manually released (like with copy). This helps to avoid using autoreleased objects, but it adds another thing to remember when doing memory management.
Additionally, because memory allocation / deallocation is costly (especially on the iPhone), you may wish to avoid the frequent allocations and deallocations within a method called quite a lot and instead recycle the mutable array by creating it ahead of time and passing it into the method, which only adds to the array's contents.
You're right, autorelease is the standard idiom to use in a situation like this.
UIKit actually creates an autorelease pool at the start of each event cycle and releases it at the end, so your autoreleased objects will get cleared up then; they won't be hanging around forever.
If you had a loop that was calling this method many times within a single event cycle, you might want to create your own autorelease pool inside that loop so that these objects get released at the end of each iteration, rather than building up loads of objects to be released at the end of the current event cycle.
But unless you're doing something like that, or you are encountering another specific out-of-memory situation, UIKit's standard autorelease pools should handle it fine.