【问题标题】:Explain alloc/init occurring twice解释 alloc/init 发生两次
【发布时间】:2011-12-15 02:12:33
【问题描述】:

我知道这个问题可能听起来很愚蠢,但请耐心等待。我构建了一个应用程序来帮助新开发人员了解 iPhone 上的内存保留(还没有 ARC)。它简单明了,4 个按钮,init、access、retain 和 release。很不言自明。我正在显示作为我们戳和刺激目标的字符串对象的保留计数。 (请不要讲[myVar retainCount]的使用,我已经知道了)

这些东西永远不会变成实际的应用程序,只是为了好玩而玩弄它,希望能帮助人们了解记忆的工作原理。我的保留和释放都很好。我的问题是,如果我调用 myString = [[NSMutableString alloc] init]; 为什么我的保留计数会回落到 1?再次。我可以将保留计数提高到 40,但在调用 alloc/init 后我又回到零。我没有在任何地方泄漏,只是好奇如果/当再次调用 alloc/init 时 myString 会发生什么。

【问题讨论】:

  • 显示保留计数,即使在这种有限的上下文中,也会产生误导。如果您确实将使用限制在保留计数准确且易于理解的程度,那么您的范围已经如此有限,以至于它不再代表现实世界的内存管理。在使用真正的应用程序时,您的学生可能从这个应用程序中学到的任何东西都将毫无用处。
  • 显然您没有阅读问题,“请不要使用 [myVar retainCount]。”
  • @morningstar - 在 bbum 附近使用 -retainCount 就像在公牛面前挥舞红旗。
  • @morningstar 这个问题本身与retainCount 没有任何关系,而将retainCount 纳入其中的事实表明,Bill 对一些非常基本的东西缺乏了解(没有批评的意图——我们都从那里开始)。如果您注意到,我的回答实际上并没有提到retainCount,直到新部分的最后一段。至于评论,嗯,这是评论……不是答案。
  • 即使完全理解retainCount以及它为什么会产生误导,用它来教你的学生也会让他们知道retainCount是对学习和/或调试有用。最好删除应用程序中的 retainCount 显示,并在您按下按钮时使用 Instruments 实时查看对象的历史记录:然后您教他们一种有用的调试技术,而不是含蓄地教他们使用 retainCount

标签: iphone objective-c ios memory-management


【解决方案1】:

我的问题是,如果我打电话,为什么我的保留计数会回落到 1 再次myString = [[NSMutableString alloc] init];

因为你没有理解 Objective-C 的一个非常基本的概念; myString 不是NSMutableString 的实例,而是对实例的引用。如果你要:

myString = [[NSMutableString alloc] init];
myString = [[NSMutableString alloc] init];

你现在有两个 NSMutableString 实例,一个泄露了。

如果你:

myString = [[NSMutableString alloc] init];
otherString = myString;

您现在拥有一个带有两个引用的 NSMutableString 实例。

在所有三个分配中,NSMutableString 实例将具有 +1 保留计数,因此,您必须使用单个 release 平衡每个实例,否则您会泄漏。

将保留计数视为绝对计数是一种疯狂的做法。或者,充其量,绝对保留计数的有用范围非常有限,以至于学习它不适用于现实世界的 iOS 编程。


这值得重复:

对象的retainCount 是一件棘手的事情。

如果您要继续沿着这条路走下去,您应该注意以下细节:

  • retainCount 永远不能返回 0
  • 不保证向悬空指针发送消息会崩溃
  • 由于实现细节,一旦您通过任何系统 API 传递对象,就无法知道保留计数
  • 由于实现细节,任何系统类的任何子类都可能具有未知的保留计数
  • 保留计数从不反映对象是否自动释放
  • autoreleases 实际上是线程特定的,而保留计数是线程全局的
  • 某些类有时使用单例实现(NSString,NSNumber 的某些值)
  • 实现细节因平台和版本而异
  • 尝试调配retain/release/autorelease 将不起作用,因为某些类实际上并未使用这些方法来维护保留计数(实现细节、每个平台/版本的更改等)

如果您要教授保留/释放,则应将保留计数视为增量,并完全专注于“如果增加 RC,则必须减少它”。

【讨论】:

    【解决方案2】:

    当您调用 myString = [[NSMutableString alloc] init]; 时,您不是“再次调用 alloc/init”。您没有在您拥有的同一实例上调用方法。您正在分配和初始化一个新实例,这是一个与之前完全不同的对象。

    如果您使用具有您保留的对象的变量来执行此操作,那么是的,您正在泄漏它。

    【讨论】:

    • 如果您在 Instruments 中使用泄漏配置文件运行应用程序,您可以看到泄漏。这是经典的 C 指针混淆 - 您的变量是指向已分配内存的指针。如果您分配新内存并更新指向新内存的指针,而没有首先向系统标记您已完成先前的分配,则会泄漏(如 filipe 所说)。
    【解决方案3】:

    试试这个。

    NSString *myString = [[NSMutableString alloc] init];
    NSLog(@"%d", [myString retainCount]); // "1"
    for (int i = 1; i < 40; i++)
        [myString retain];
    NSLog(@"%d", [myString retainCount]); // "40"
    
    NSString *backup = myString;
    myString = [[NSMutableString alloc] init];
    NSLog(@"%d", [myString retainCount]); // "1"
    NSLog(@"%d", [backup retainCount]); // "40"
    

    您看,您有一个具有新保留计数的不同对象。您的原始对象仍然存在并且仍然具有相同的保留计数。赋值改变变量引用的对象。变量没有保留计数,对象有。

    myString = someOtherString;
    NSLog(@"%d", [myString retainCount]); // who knows?
    

    保留属性:

    self.iString = backup;
    NSLog(@"%d", [self.iString retainCount]); // "41" - 1 more because property retained
    NSString *newString = [[NSMutableString alloc] init];
    NSLog(@"%d", [newString retainCount]); // "1"
    self.iString = newString;
    // 1 for alloc 1 for retain (in real code you should release newString next)
    NSLog(@"%d", [self.iString retainCount]); // "2"
    NSLog(@"%d", [backup retainCount]); // "40" - self.iString released it
    

    【讨论】:

    • 这个想法会改变(非原子,保留)属性吗?还是同一个校长?
    • 它会导致发送一些保留和释放消息,但它仍然是一个变量。保留属性将导致计数以一种或另一种方式相差 1,但不会改变 变量 的保留计数一次可以更改 40 的事实。我正在添加一个示例。
    • 非常感谢。这真的很有帮助。我可能会将其中的一些内容构建到我的应用程序中以帮助传达这一点。非常感谢。
    • 如果没有retainCount 可以更有效地证明这一点,并且使用retainCount 作为绝对计数会产生误导。
    猜你喜欢
    • 1970-01-01
    • 2015-06-17
    • 1970-01-01
    • 2014-02-20
    • 2012-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-12
    相关资源
    最近更新 更多