-1

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?

Kaveh Vejdani
  • 224
  • 1
  • 6
  • 4
    Do you have the stack trace? – AdamPro13 Feb 16 '15 at 23:45
  • 1
    We're reasonably sure that it's an NSDictionary that's failing, since that's the class that appears in the message. (It could have been deallocated as something else then reallocated as NSDicitonary then deallocated again, but that seems unlikely.) However, we don't know whether it's the outermost dictionary or an "internal" one, inside `dataDictionary`. If one could stop when the error occurs and position the debugger in the stack frame of `saveData` and do a `po dataDictionary` then that should dump the object and you'd have a clue as to whether it was gone or something in it. – Hot Licks Feb 17 '15 at 02:59
  • I admit I have never done this kind of debugging before. Can you please post a couple of screenshots that shows how to do this? I have done my research on po (print-object), so far to no avail. – Kaveh Vejdani Feb 17 '15 at 16:53
  • 1
    For `po`, just type `help` in the console window. And become familiar with the stack display in Xcode's left-hand window -- press the button that looks like a sandwich, then find your thread and your code's stack frame in the thread. – Hot Licks Feb 17 '15 at 21:20

2 Answers2

0

ozgur's theory is a decent guess as to what's going on. Another possibility is that there is something funky contained in the array that you are writing. writeToFile:atomically: and its relatives all require that the entire "object graph" you're writing must be "property list" objects (a small list of classes: NSString, NSData, NSDate, NSNumber, NSArray, or NSDictionary). Any object that's not one of those in any of the containers or sub-containers in your object graph would cause the write to fail.

However, what I'm used to seeing is just that no file is written, not a crash. You might have a zombie somewhere in your array, although that's much less likely under ARC than it was under manual reference counting. You would have had to add an __unsafe_unretained object to the array or a container that's inside the array.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • What's Ozgur's theory? Or who's Ozgur, I should ask? Duncan, I assure you the entire object graph is a property list. All containers and subcontainers are either NSStrings, or NSDictionaries of NSStrings, with the exception of that NSNumber I mentioned, which is the value of another NSString key of the root NSDictionary. The app totally crashes with the error I mentioned. The initial crash was a bad access crash, so I enabled zombies so I can catch the culprit, which seems to be a message sent to a deallocated instance, when writeToFile is checking that the NSDictionary is a property list. – Kaveh Vejdani Feb 17 '15 at 02:52
  • 1
    ozgur is another responder to your question. See his answer below. It sounds like one of the objects in your object graph is a zombie. You need to figure out which one, and how it's getting there. Search your project for unsafe_unretained and track those down. Or write code that walks your object graph logging the class of every object in it. It will crash on the offending object. – Duncan C Feb 17 '15 at 12:28
  • Thank you Duncan. Ozgur's answer must have either been deleted or somehow hidden from my view. I never saw any answer in his name, and still can't see any. In any case, regarding my question, I logged the whole object graph with a bunch of nested for loops. When it gets to the only dictionary contained in my nsarray inside the root nsdictionary, [dic class] crashes with "message sent to deallocated instance." Why is my dictionary being deallocated?!!! It's being created in a class method, using proper alloc init, and so is the root dictionary. What's going on? – Kaveh Vejdani Feb 18 '15 at 00:27
  • Alright, so I found the culprit. An NSDictionary inside my private NSArray gets deallocated as soon as I move outside the scope of viewDidLoad. Now what? How can I make the NSArray retain its objects? I tried declaring it as a property instead of a private instance variable, didn't solve the issue. Any guides please? – Kaveh Vejdani Feb 19 '15 at 04:49
  • Post your code that creates the data structure. Run the analyze tool on your code. One thought: Do you have any method with names that start with "new" or "create" that return some of these objects? Those method names have special meaning to the compiler, and will cause memory management problems if used incorrectly. – Duncan C Feb 19 '15 at 13:52
  • Once I changed my class method name from init to create (and added proper alloc-init in the requesting class, so it owns the object) the problem got resolved. Is "create" a reserved word for the compiler too? What does it do/imply to the compiler? – Kaveh Vejdani Feb 20 '15 at 18:26
0

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

Kaveh Vejdani
  • 224
  • 1
  • 6