Search code examples
iosobjective-cnsdictionarywritetofile

-[__NSDictionaryI isNSString__]: message sent to deallocated instance


I am stuck on this for over a week now, and apparently nobody has reported this issue before on stackoverflow. Please read my description carefully before referring me to other postings, because I have read them all and none of them has my answer.

I have an NSDictionary containing an NSNumber and an NSArray of NSStrings, with both keys being NSStrings.

Now NSDictionary writeToFile crashes with error: -[NSDictionaryI isNSString]: message sent to deallocated instance

writeToFile is called on a class method as such:

@implementation AppDataStore

+ (void) saveData:(NSDictionary*)dataDictionary {

    NSArray *directories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documents = [directories firstObject];
    NSString *appDataFilePath = [documents stringByAppendingPathComponent:@"AppDataStore.plist"];
    [dataDictionary writeToFile:appDataFilePath atomically:YES];
}

Of note, I am passing an NSMutableDictionary to the method, but that's not the issue because it writes other key-value NSMutableDictionaries just fine, but not this one that contains and NSArray. Even when I take out the NSNumber element, so the dictionary only contains an NSArray, writeToFile still crashes the same error. Crashes both on simulator and on iPhone.

What is going on?!

EDIT 1 (in response to Adam's question):

Stack trace up until right before the crash:

Stack trace : (
    0   MedList                             0x00009b39 +[AppDataStore saveData:] + 217
    1   MedList                             0x0000811d -[ViewController tableView:didSelectRowAtIndexPath:] + 1101
    2   UIKit                               0x010c894c -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1559
    3   UIKit                               0x010c8af7 -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 285
    4   UIKit                               0x010cddf3 __38-[UITableView touchesEnded:withEvent:]_block_invoke + 43
    5   UIKit                               0x00fe20ce ___afterCACommitHandler_block_invoke + 15
    6   UIKit                               0x00fe2079 _applyBlockToCFArrayCopiedToStack + 415
    7   UIKit                               0x00fe1e8e _afterCACommitHandler + 545
    8   CoreFoundation                      0x00b289de __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
    9   CoreFoundation                      0x00b28920 __CFRunLoopDoObservers + 400
    10  CoreFoundation                      0x00b1e35a __CFRunLoopRun + 1226
    11  CoreFoundation                      0x00b1dbcb CFRunLoopRunSpecific + 443
    12  CoreFoundation                      0x00b1d9fb CFRunLoopRunInMode + 123
    13  GraphicsServices                    0x03efd24f GSEventRunModal + 192
    14  GraphicsServices                    0x03efd08c GSEventRun + 104
    15  UIKit                               0x00fb88b6 UIApplicationMain + 1526
    16  MedList                             0x0000681d main + 141
    17  libdyld.dylib                       0x02f6bac9 start + 1
    18  ???                                 0x00000001 0x0 + 1
)
2015-02-16 21:39:37.762 MedList[14029:496685] *** -[__NSDictionaryI isNSString__]: message sent to deallocated instance 0x7974e910

EDIT 2: I did a few more tests. The NSDictionary inside of my NSArray gets deallocated BEFORE the class method call (before writeToFile) and only AFTER didSelectRowAtIndexPath. Why would selecting the table row automatically deallocate the dictionary contained inside an array? This is bizarre behavior. Any thoughts?


Solution

  • First off, thank you all, especially Duncan and Hot Licks for helping me narrow down the bug and finally catching it. So, I noted that an NSDictionary inside my NSArray was being deallocated as soon as I moved out of the scope of viewDidLoad, meaning that I didn't actually own that NSDictionary, although I thought I did.

    Problem was caused by me creating that NSDictionary by another class method call, which I had naively named init, hence concealing my actual non-ownership of the returned NSDictionary, fooling me into thinking I owned it just by calling init.

    Problem solved after I renamed init into "create," then properly alloc-init-ed my NSDictionary in my viewDidLoad before calling create. The dictionary is now retained, and the bug is gone.

    For those facing similar issues, I highly recommend this resource, which is what actually made me finally find the bug:

    https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.pdf