In what method should I recreate resources after didReceiveMemoryWarning? - iphone

I have a view controller that has a private NSArray variable. The variable is initialised in the viewDidLoad method. A few questions arise for the case when the didReceiveMemoryWarning is called:
Should I set the private variable to nil?
If I set it to nil in what method must it be recreated? Does the view controller call the viewDidLoad method to recreate it?
I'm asking because other methods of the view need this variable and won't work if it's nil.
Thank you!

Typically you unload a private property by assigning nil via the setter (e.g. self.propertyName = nil). Or you could set the ivar to nil after calling release, e.g. [_propertyName release]; _propertyName = nil;, but the former is preferable.
The didReceiveMemoryWarning method is called when there is a low memory situation. It is called on every view controller, including the one(s) responsible for the currently visible UI!
Therefore, you can't just unload data arbitrarily when you get a call to didReceiveMemoryWarning -- the view controller might need that data if it is currently visible on the display.
The general principle is that didReceiveMemoryWarning can get rid of any resources it can to assist freeing up memory, but only those that aren't immediately needed. For example, in an OpenGL game you wouldn't unload textures that are currently visible on the display. However, see my last paragraph.
Typically you reload the resources by checking they are loaded when you need them, and if not, loading them.
It's not worth niling/releasing tiny resources such as a single normally sized string. You should concentrate on items taking up significant amounts of memory.
Recent advances in behind the scenes memory management mean you're less likely to need to actually unload data these days - the operating system can unload and reload uncompressed image data and the like behind the scenes.
As Hot Licks mentions, the simulator has an option for simulating a memory warning. It's worth triggering this memory warning at various points in your app to see how it behaves.

Create custom getter that load data lazily. Something such as this snippet is good for non-mutithreading eviroment:
- (NSArray*) dataArray {
if(_dataArray) return _dataArray;
_dataArray = [self lordata];
return _dataArray;
}
In this way data are always reloaded if you "release" them in memory warnings

The ViewDidLoad method is called only once, when your ViewController is initialized. If you must reload some data to your NSArray, you should call your own methods to do that when needed.
If this array is used by various parts of the code, maybe you should think about redesigning your code structure to avoid a huge concentration of data inside only one object.
Edit: As pointed by #occulus in the comments below, it is not called when the View is initialized, but when the View is loaded by the ViewController.. my mistake

As an example, I had an app that downloaded data to a very long table view (potentially 1000s of records). To support this I implemented a "sparse" array, allowing empty elements that would be "faulted in" over the net when referenced (with a "downloading" indicator in the table cell while downloading).
This was rigged so that when didReceiveMemoryWarning occurred the array would be purged, using a least-recently-used algorithm to delete the oldest N% of the array. Recovery would be automatic -- the emptied cells would reload when they were referenced.
Not that I recommend this specific scheme, but note the general features of having a lot of data, having a way to "prioritize" what should be deleted, and having a "soft" way to reload the data (ideally only reloading the parts that are needed in the near future).

Its better to set your variable to nil. I mean release the memory it is holding in didReceiveMemoryWarning and set a dirty flag.
You can always check the dirty flag in array's getter(you can write your own) and repopulate it. It is probably not the best way. It entirely depends on the array's usage.

Related

Accessing an instance variable from a background thread

Say I have an instance variable MyObject that has been allocated and initialized. Then say I do this:
[backgroundThread performBlock:^{
//do something with MyObject that might take some time
}];
[self dismissModalViewController]; //this releases all instance variables, right?
So what happens is I have an NSManagedObjectContext called backgroundThread that does some work on an object in the background. This returns immediately and does the work in the background, and then dismissModalViewController is called, which deallocates all instance variables. So what if the modal view has now been dismissed, but the backgroundThread still needs to use the object? Is this an issue? What is the workaround?
And another thing: This MyObject is inserted into the managed object context backgroundThread. Does this mean that this NSManagedObjectContext will retain the object, even after dismissing the view?
I'm using ARC.
There are several things you need to think about here. First keep in mind that the block will capture whatever it refers to. So you might not need to do anything special and your code will work fine, depending on exactly what you are doing in your block. The rules for block capture are described in Apple's Block Programming Topics documentation and how each variable is treated depends on its type. In particular,
In a manually reference-counted environment, local variables used within the block are retained when the block is copied. Use of instance variables within the block will cause the object itself to be retained. If you wish to override this behavior for a particular object variable, you can mark it with the __block storage type modifier.
If you are using ARC, object variables are retained and released automatically as the block is copied and later released.
Another thing to consider is that access to the instance variables may or may not be thread safe. Accessing the instance variables through properties declared as atomic is a step in the right direction, but you may need to use mutex locks or other techniques to synchronize access depending on the specifics on the situation.
If you want to reference ivars or other properties of your (modal) view controller, you need to insure that the modal view controller still exists.
Here's a potentially useful hint from Apple's documentation on dismissModalViewControllerAnimated::
If you want to retain a reference to the receiver’s presented view
controller, get the value in the modalViewController property before
calling this method.
Another idea that might work would be to create & instantiate a separate object that encapsulates the data / objects you want to access from either the view controller, or any other thread.

saving object state in dealloc doesn't work

I noticed just now that when I save object state (#public floats converted to NSStrings) in my dealloc method, using
+(void)savePreferences:(NSString*)key :(NSString*)value{
NSMutableString* mutableString=[[NSMutableString alloc]initWithString:value];
CFPreferencesSetAppValue((CFStringRef)key, mutableString, kCFPreferencesCurrentApplication); // Set up the preference.
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);// Write out the preference data.
[mutableString release];
}
the wrong values are saved!? If I instead save the values just before releasing, the correct values are saved. Note that I am careful to call [super dealloc]; at the end. Why is this?
Several issues with that code;
saving state in -dealloc is way too late. By the time -dealloc is called, the object graph is in the midst of being torn down.
on app termination, the system will not waste cycles tearing down your application. It'll just inform it that it is about to be terminated and then terminate it; if you are relying on -dealloc to be called, that might likely never happen.
that method name isn't very good. Try something like savePreferencesValue:forKey:. However, the implication that it may be called many times would lead to a significant ineffeciency (in that it'd write the preferences plist over and over).
the mutable string copy of the incoming value is a waste of cycles and memory; no need for it
unless you need the extended features of CFPreferences*() you should just stick with NSUserDefaults; it will lead to less code and less fragility.

Memory about lazy loading

I am reading tutorial book now. When I see an example, I get some confused.
The following codes show a way for lazy loading. Does this motivations array be released after instance deallocated ? or will it occupy this memory block until application terminates.
+ (NSArray *)motivations
{
static NSArray *motivations = nil;
if (!motivations)
{
motivations = [[NSArray alloc] initWithObjects:#"Greed",#"Revenge",#"Bloodlust",#"Nihilism",#"Insanity",nil];
}
return motivations;
}
Edit 1
Thank Georg for a bug.
The example you show has a bug - +arrayWithObjects: returns an autoreleased instance which will be destroyed later. The code was probably intended to be:
motivations = [[NSArray alloc] initWithObjects:#"Greed",#"Revenge",#"Bloodlust",#"Nihilism",#"Insanity",nil];
With that, the array will live until the application terminates.
Because it is a static object, so the system will store the object pointer until the application is terminated. You can use this way to cache by let your pointer point to an object that is not release or autorelease
I recommend to use this approach when you really want to cache some data in memory (usually small or big images data) that requires a lot of CPU or IO processing time to generate. For small data like NSString, you can create new array and return every time you need.
Edit for the comment:
There are 2 things about imageNamed:
1/ You cannot control what is cached and what is not cached by imageNamed:. You may not want to cache an image with big size and only used once, for example.
2/ imageNamed: cannot be used for getting image from Network or folders in the system. It will only load from your bundle
Since this is a class method (indicated by a + instead of a - at the declaration), there is no instance that will be released. (Technically there is an instance of an object of class isa I think (? comment if I'm wrong please, I don't know the inner workings very well) but don't worry about that)
So the class, which exists in memory the whole time the program is running, owns that array. Think of static as about as close as you can get to a class variable, rather than an instance variable. Since the class exists the whole time, the array exists the whole time.
Lazy loading keeps it from being created until the first time that class method is called though, so it isn't wasting memory until you need it.

Core Data - How to check if a managed object's properties have been deallocated?

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.

How can I maintain a global cache of objects? (or a NSMutableSet w/o retaining contents)

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.