9

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: crash

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)

stacktrace

Machine code symbols:

arm code

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.

openfrog
  • 40,201
  • 65
  • 225
  • 373

3 Answers3

10

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
bbum
  • 162,346
  • 23
  • 271
  • 359
  • 2
    The NSUserDefaults string could also get overreleased by a dangling pointer. If an autoreleased object is being assigned to a place that's supposed to be retained, and the NSUserDefaults string is being allocated in its place, when the "owner" of the deallocated object tries to release it, it will release this string instead. – Chuck Jul 06 '11 at 20:43
  • Please check out my updated question. I do work a lot with autoreleased strings, but it makes not much sense that this is happening with EVERY string I put in NSUserDefaults and only after releasing and reloading my view two times. – openfrog Jul 06 '11 at 21:27
  • Also, another thing I tried was retaining the strings explicitly before adding to NSUserDefaults. Helped nothing. – openfrog Jul 06 '11 at 21:28
  • Yeah, indeed this was the cause. I had an retain ivar which got such a string from NSUserDefaults, but forgot to put self in front of ivarProperty = theNSUserDefaultsString. Then in -dealloc that got over-released. Almost-heart-attack because forgetting a "self." - crazy business – openfrog Jul 07 '11 at 11:35
  • 1
    Easy fix for that; delete all your iVar declarations (that are also @properties) and then use `@synthesize propName = propName_;` That'll make any `propName = ...` lines break, forcing you to consciously decide between direct access or via the setter/getter methods. – bbum Jul 07 '11 at 16:21
2

If your application needs to maintain a reference to val beyond the scope of the current method, you need to retain it also. If you don't, it may get autoreleased before you try to access it.

highlycaffeinated
  • 19,729
  • 9
  • 60
  • 91
  • No, I read val from NSUserDefaults and it already returns a deallocated instance! That's the problem. – openfrog Jul 06 '11 at 19:25
  • @openfrog Does the line of code that generates the console message immediately follow the `objectForKey:` call? I'd be very surprised if it was already deallocated, rather than being deallocated between you getting the reference and you attempting to use the reference. – highlycaffeinated Jul 06 '11 at 19:29
  • Seriously, it is dealloced immediately after receiving it from objectForKey:! I cannot even inspect the value of val with a breakpoint. After calling objectForKey the crash occurs already in the second where the debugger wants to read the returned value. – openfrog Jul 06 '11 at 20:44
0

Where are you attempting to retain the string? NSUserDefaults returns an autoreleased NSString. Is it possible that you don't do anything with it until the Autorelease Pool has cleaned it up?

MarkPowell
  • 16,482
  • 7
  • 61
  • 77
  • No, I read val from NSUserDefaults and it already returns a deallocated instance! That's the problem. The NSLog is directly following in the next line. The string returned is deallocated. – openfrog Jul 06 '11 at 19:26
  • 1
    @openfrog - The NSLog line doesn't prove that returned object is deallocated, it only proves that it occupies the same address. The memory could have been reused for another object. – highlycaffeinated Jul 06 '11 at 19:50
  • Seriously, it is dealloced immediately after receiving it from objectForKey:! I cannot even inspect the value of val with a breakpoint. After calling objectForKey the crash occurs already in the second where the debugger wants to read the returned value, directly from that method call! – openfrog Jul 06 '11 at 20:45