In my app I write multiple strings to the defaults database, like this:
[[NSUserDefaults standardUserDefaults] setObject:@"Hi" forKey:@"GREETING"];
When I call multiple times during my app life time this, then I end up with an error in the console after stepping over this code:
NSString *val = [[NSUserDefaults standardUserDefaults] objectForKey:@"someKey"];
Here in more detail:
Like you can see I have a breakpoint exactly on the line with the method call to -stringForKey:
of NSUserDefaults
. After stepping over, the crash happens already! No chance to even read what is in val
. It is deallocated!
This happens with ANY string I put in NSUserDefaults, from ANYWHERE, and I am not doing anything wrong with memory management. Leaks Instrument is all perfect, same as the Clang Static Analyzer results.
Console error:
*** -[CFString retain]: message sent to deallocated instance 0x610ba10
Top of stack trace in debugger after crash: (top line reads forwarding)
Machine code symbols:
Now the REALLY strange part: I have a view controller which loads a heavy UI. When I destroy this view and load it, and destroy it again and load it again, THEN NSUserDefaults is dead. Crash as soon as I want to read a string from NSUserDefaults. It's absolutely always the exact same behavior.
I thought maybe there is a memory leak that eats up all the RAM. But this happens on a big development machine as well as on the iPad. Release and reload the view two times and NSUserDefault has this defect.
Just to make sure it is not my fault, I re-wrote my NSUserDefaults writing code to something like this:
[[NSUserDefaults standardUserDefaults] setObject:[theString copy] forKey:key];
But still, I end up with getting deallocated string instances from NSUserDefaults! On the simulator and on the device! Also, some of those strings I try to access have been written as default when initializing the application in + (void)initialize
NSDictionary *preSettings = [[NSDictionary alloc] initWithObjectsAndKeys:
@"Hollywood", @"defaultCity",
nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:preSettings];
So even if I do not change this string for the key @"defaultCity and I want to access it after a while (when I reloaded that fat view twice), I end up getting a deallocated instance from NSUserDefaults.
I also tried to re-initialize the whole thing, manually calling my method in the App Delegate which registers that default dictionary with NSUserDefaults. Helps nothing.
I've made 100% sure that there are no memory leaks (tested excessively with the Leaks instrument). I don't get it why this happens. I also tried to retain those strings 5 times before I write them to NSUserDefaults (which would be stupid anyways), with no success.
Ideas?
PROBLEM SOLVED!
One of the strings returned by NSUserDefaults was assigned to a retaining property. I forgot to add a self.
in front of self.theProperty = theStringFromNSUserDefaults
which resulted in over-releasing that string in -dealloc when the view got deallocated.
Strange though, that I was able to load, destroy, load, destroy that view. And then it happened upon the first attempt of reading any string from NSUserDefaults. It's like a deallocated string propagates through the defaults dictionary or defaults database and tears everything down, driving the NSUserDefaults and NSDictionary algorithms notally nuts.
After correcting that single error everything worked fine. Including access to all other strings in NSUserDefaults as well. Works, but still a mystery how this one single forgotten self.
had such a big impact.
Thank you everyone who helped solving this issue. You saved me from a heart attack. I was so close to it. Well, must buy some new stuff now... my office looks like the Star Gate room after a Goa'uld attack.
The only way that NSUserDefaults
could return a deallocated string is if you over-released a string that happened to be in the user defaults cache in the first place. I.e. I'd bet if you do something like:
p = [NSAutoreleasePool new];
NSString *val = [[NSUserDefaults standardUserDefaults] objectForKey:@"someKey"];
[val release];
[p drain];
val = [[NSUserDefaults standardUserDefaults] objectForKey:@"someKey"];
You'd end up with a deallocated string reference in val. What are you doing with val
throughout your code?
Or, as Chuck said, this could be a dangling pointer issue, too. I.e. you have a dangling pointer that happens to then point to the object in the user defaults and it gets released.
[[NSUserDefaults standardUserDefaults] setObject:[theString copy] forKey:key];
You are leaking the copy of theString
in that code.
I've made 100% sure that there are no memory leaks (tested excessively with the Leaks instrument). I don't get it why this happens. I also tried to retain those strings 5 times before I write them to NSUserDefaults (which would be stupid anyways), with no success.
The leaks instrument is neither 100% accurate, nor can it detect all leaks. If there is a reference to an object anywhere, it isn't counted as a leak.
I thought maybe there is a memory leak that eats up all the RAM. But this happens on a big development machine as well as on the iPad. Release and reload the view two times and NSUserDefault has this defect.
Chuck's theory is most likely correct; most likely, you have something like:
id foo = ... some object ... [foo release]; ... do something with NSUserDefaults such that it happens to allocate an object where the now-dangling foo points to ... [foo release]; .... oops; now the object in the user defaults has been deallocated
Easy to figure out; turn on "Malloc Stack Logging" in the Diagnostics tab of the run panel in Xcode. Then, when the crash happens, do info malloc <address of bad thing>
and all of the malloc/free events that happened at that address will be spewed. Most likely you'll see something like:
- malloc
- free
.... repeated some # of times ....
- malloc of some object that you create
- free of same
- malloc of a string by NSUserDefaults
- [inadvertent] free of same
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With