【问题标题】:-[__NSDictionaryI isNSString__]: message sent to deallocated instance-[__NSDictionaryI isNSString__]:消息发送到释放的实例
【发布时间】:2015-02-16 23:43:16
【问题描述】:

我已经在这个问题上停留了一个多星期,显然之前没有人在 stackoverflow 上报告过这个问题。在将我推荐给其他帖子之前,请仔细阅读我的描述,因为我已经阅读了所有帖子,但没有一个有我的答案。

我有一个 NSDictionary,其中包含一个 NSNumber 和一个 NSString 的 NSArray,两个键都是 NSString。

现在 NSDictionary writeToFile 崩溃并出现错误: -[NSDictionaryI isNSString]:消息发送到解除分配的实例

writeToFile 在类方法上被调用:

@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];
}

值得注意的是,我将 NSMutableDictionary 传递给该方法,但这不是问题,因为它可以很好地写入其他键值 NSMutableDictionaries,但不是包含 NSArray 的这个。即使我取出 NSNumber 元素,所以字典只包含一个 NSArray,writeToFile 仍然会崩溃同样的错误。在模拟器和 iPhone 上都会崩溃。

这是怎么回事?!

编辑 1(针对亚当的问题):

直到崩溃前的堆栈跟踪:

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

编辑 2:我又做了一些测试。我的 NSArray 中的 NSDictionary 在类方法调用之前(在 writeToFile 之前)并且仅在 didSelectRowAtIndexPath 之后被释放。为什么选择表行会自动释放数组中包含的字典?这是一种奇怪的行为。有什么想法吗?

【问题讨论】:

  • 你有堆栈跟踪吗?
  • 我们有理由确定它是一个失败的 NSDictionary,因为那是出现在消息中的类。 (它可以作为其他东西被释放,然后作为 NSDicitonary 重新分配,然后再次释放,但这似乎不太可能。)但是,我们不知道它是最外面的字典还是“内部”字典,在 dataDictionary 中。如果在错误发生时可以停止并将调试器定位在saveData 的堆栈帧中并执行po dataDictionary 那么应该转储对象并且您将知道它是否已经消失或其中的某些东西。
  • 我承认我以前从未做过这种调试。您能否发布一些屏幕截图来说明如何执行此操作?我已经对 po(打印对象)进行了研究,到目前为止无济于事。
  • 对于po,只需在控制台窗口中输入help。并熟悉 Xcode 左侧窗口中的堆栈显示——按下看起来像三明治的按钮,然后在线程中找到你的线程和代码的堆栈框架。

标签: ios objective-c nsdictionary writetofile


【解决方案1】:

ozgur 的理论是对正在发生的事情的一个不错的猜测。另一种可能性是您正在编写的数组中包含一些时髦的东西。 writeToFile:atomically: 及其亲属都要求您编写的整个“对象图”必须是“属性列表”对象(一小部分类:NSString、NSData、NSDate、NSNumber、NSArray 或 NSDictionary)。任何不是对象图中任何容器或子容器中的对象之一都会导致写入失败。

但是,我习惯看到的只是没有写入文件,而不是崩溃。您的阵列中可能有一个僵尸,尽管在 ARC 下这比在手动引用计数下的可能性要小得多。您必须将 __unsafe_unretained 对象添加到数组或数组内的容器中。

【讨论】:

  • Ozgur 的理论是什么?或者我应该问谁是 Ozgur? Duncan,我向你保证,整个对象图是一个属性列表。所有容器和子容器要么是 NSStrings,要么是 NSStrings 的 NSDictionaries,除了我提到的那个 NSNumber,它是根 NSDictionary 的另一个 NSString 键的值。该应用程序因我提到的错误而完全崩溃。最初的崩溃是一次糟糕的访问崩溃,所以我启用了僵尸,这样我就可以抓住罪魁祸首,当 writeToFile 检查 NSDictionary 是一个属性列表时,这似乎是一条发送到已释放实例的消息。
  • ozgur 是您问题的另一位回复者。请看下面他的回答。听起来您的对象图中的一个对象是僵尸。你需要弄清楚是哪一个,以及它是如何到达那里的。在你的项目中搜索 unsafe_unretained 并追踪它们。或者编写遍历对象图的代码,记录其中每个对象的类。它会在有问题的对象上崩溃。
  • 谢谢邓肯。 Ozgur 的答案一定已经被删除或以某种方式隐藏在我的视野之外。我从来没有在他的名字中看到任何答案,仍然看不到任何答案。无论如何,关于我的问题,我用一堆嵌套的 for 循环记录了整个对象图。当它到达根 nsdictionary 内我的 nsarray 中包含的唯一字典时,[dic 类] 崩溃并出现“发送到已释放实例的消息”。为什么我的字典被释放了?!!!它是在类方法中创建的,使用适当的 alloc init,根字典也是如此。怎么回事?
  • 好吧,所以我找到了罪魁祸首。一旦我移出 viewDidLoad 的范围,我的私有 NSArray 中的 NSDictionary 就会被释放。怎么办?如何使 NSArray 保留其对象?我尝试将其声明为属性而不是私有实例变量,但没有解决问题。请问有什么指南吗?
  • 发布创建数据结构的代码。在您的代码上运行分析工具。一个想法:您是否有任何名称以“new”或“create”开头的方法返回其中一些对象?这些方法名对编译器有特殊意义,如果使用不当会导致内存管理问题。
【解决方案2】:

首先,感谢大家,尤其是 Duncan 和 Hot Licks,他们帮助我缩小了错误范围并最终解决了它。所以,我注意到,当我离开 viewDidLoad 的范围时,我的 NSArray 中的一个 NSDictionary 正在被释放,这意味着我实际上并不拥有那个 NSDictionary,尽管我认为我拥有。

问题是由于我通过另一个类方法调用创建了该 NSDictionary,我天真地将其命名为 init,因此隐藏了我对返回的 NSDictionary 的实际非所有权,愚弄我以为我只是通过调用 init 就拥有它。

在我将 init 重命名为“create”之后问题解决了,然后在调用 create 之前在 viewDidLoad 中正确分配初始化我的 NSDictionary。字典现在被保留了,bug 消失了。

对于那些面临类似问题的人,我强烈推荐这个资源,这实际上是让我最终找到错误的原因:

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

【讨论】:

    猜你喜欢
    • 2012-08-22
    • 1970-01-01
    • 2016-08-28
    • 2011-06-16
    • 2013-08-30
    相关资源
    最近更新 更多