【问题标题】:objective-c memory management--how long is object guaranteed to exist?Objective-C 内存管理——对象保证存在多长时间?
【发布时间】:2012-09-14 18:50:09
【问题描述】:

我有以下形式的 ARC 代码:

NSMutableData* someData = [NSMutableData dataWithLength:123]; ...

CTRunGetGlyphs(run, CGRangeMake(0, 0), someData.mutableBytes); ...

const CGGlyph *glyphs = [someData mutableBytes]; ...

...后面是从glyphs 读取内存但对someData 不执行任何操作的代码,它不再被引用。请注意,CGGlyph 不是对象类型,而是无符号整数。

我是否必须担心someData 中的内存可能会在我完成glyphs(实际上只是指向内部someData)之前被释放?

所有这些代码都在同一个范围内(即单个选择器),glyphssomeData 都同时超出范围。

PS 在这个问题的早期草稿中,我提到了“垃圾收集”,它并不真正适用于我的项目。这就是为什么下面的一些答案将其与 ARC 下发生的事情同等对待。

【问题讨论】:

  • 实际上是否在启用 GC 的情况下运行?您必须竭尽全力才能将其打开,并且已(预先)弃用。或者你说的是不是 GC 的 ARC?
  • 乔希问了一个好问题。 GC != ARC。
  • @JoshCaswell 这不仅仅是预先弃用,而是完全正式弃用。请参阅常见问题解答的最底部developer.apple.com/library/mac/#releasenotes/ObjectiveC/…
  • 谢谢,@Tommy。我没有看到那个注释,我不记得 Lion 是否已经弃用它,所以我很保守。

标签: objective-c memory-management garbage-collection automatic-ref-counting


【解决方案1】:

无论您使用 GC 还是其他人推荐的 ARC,您都可能遇到麻烦。您正在处理的是一个 internal 指针,它在 GC 或 ARC 中通常不被视为 拥有引用 - 除非实现具有特殊情况 NSData。如果没有该拥有引用,GC 或 ARC 可能会删除该对象。您面临的问题是内部指针所特有的。

当你描述你的情况时,最安全的做法是抓住真实的参考。您可以通过将NSData 引用分配给实例变量或static(如果您愿意,可以使用本地方法)变量,然后在完成内部指针后将nil 分配给该变量来完成此操作。在static的情况下要注意并发!

在实践中,您的代码可能会在 GC 和 ARC 中运行,可能更可能在 ARC 中运行,但可以想象,任何一种都可能会咬到您,尤其是在编译器发生变化时。对于一个变量声明和一个额外分配的成本,您可以避免这个问题,便宜的保险。

[参见 this 讨论,作为 ARC 下生命周期短的示例。]

【讨论】:

  • 谢谢,Nerd Ranch 的讨论提供了帮助。所以#1。有一个问题。 #2。有 3 种建议的方法来修复它:(a) 你的 ivar 建议 (b) objc_precise_lifetime 限定符 (c) 在函数结束时无偿调用 [someData self]。从清晰度和维护的角度来看,您的 ivar 建议可能是最简单的。
  • 我在下面的回答讨论了 Apple 最近处理内部指针的方法。喜欢就点个赞吧。
【解决方案2】:

在实际的、真正的垃圾收集下,该代码可能是一个问题。一旦不再有对它们的任何引用,对象就可能被释放,如果您不再使用它,编译器可能会随时丢弃该引用。出于优化目的,范围只是对这类事物设置上限的一种方式,而不是一种绝对规定的方式。

您可以使用NSAllocateCollectable 将生命周期计算附加到 C 原语指针上,尽管它很混乱且略显复杂。

垃圾收集从未在 iOS 中实现,现在在 Mac 上已弃用(在 this FAQ 的最底部引用),在这两种情况下都支持自动引用计数 (ARC)。 ARC 添加了retains 和releases,它可以看到它们是隐式需要的。遗憾的是,它可以执行一些以前不可能的巧妙技巧,例如从自动释放池中检索对象(如果它们已被用作返回结果)。所以这与垃圾收集方法具有相同的净效果——对象可以在对它的最终引用消失后的任何时候被释放。

一种解决方法是创建一个类似的类:

@interface PFDoNothing

+ (void)doNothingWith:(id)object;

@end

什么都不做。使用完内部存储器后,将自动释放的对象发布到它。 Objective-C 的动态分派意味着编译器优化调用是不安全的——它无法知道您(或 KVO 机制或任何其他参与者)没有在运行时执行诸如方法调配之类的事情。

编辑:NSData 是一个特例,因为它提供对对象持有的内存的直接 C 级访问,至少不难找到关于 GC 情况的明确讨论。请参阅 this thread on Cocoabuilder 以获得一个相当不错的方法,尽管上面的警告同样适用,即垃圾收集已被弃用,并且自动引用计数的行为不同。

【讨论】:

  • 我已经进行了一些挖掘,并回复:“假设没有手动池管理并且其他一切正常,您的 [NSMutableData dataWithLength:123] 肯定会持续与 ARC 下的当前方法一样长” ,其他来源似乎不同意,包括上面@CRD提到的讨论以及这里:clang.llvm.org/docs/AutomaticReferenceCounting.html。 “...自动存储持续时间的局部变量没有精确的生命周期语义。”
  • @Merk 我认为你是对的,我之前的回答是完全错误的。因此,我对其进行了编辑,以使错误不会持续存在。向所有人道歉。
【解决方案3】:

以下是一个通用答案,不一定反映 Objective-C GC 支持。然而,各种 GC 实现,包括引用计数,都可以从可达性的角度来考虑,撇开怪癖不谈。


在GC语言中,一个对象只要是Strongly-Reachable就保证存在;这些强可达图的“根”可能因语言和执行环境而异。 “强”的确切含义也有所不同,但通常意味着边缘是强引用。 (在手动引用计数场景中,每条边都可以被认为是来自给定“所有者”的无与伦比的“保留”。)

CLR/.NET 上的 C# 就是这样一种实现,其中变量可以保留在范围内,但不能作为可达性图的“根”。查看Systems.Timer.Timer 类并查找GC.KeepAlive

如果在长时间运行的方法中声明了计时器,请使用 KeepAlive 防止在方法结束之前 [在计时器对象上] 发生垃圾收集。

【讨论】:

    【解决方案4】:

    截至 2012 年夏季,对于返回非对象类型内部指针的 Apple 对象,情况正在发生变化。在山狮的release notes 中,Apple 说:

    NS_RETURNS_INNER_POINTER

    返回指针的方法(Objective C 对象类型除外) 已用 clang 编译器属性修饰 objc_returns_inner_pointer(使用 clang 编译时)以防止 编译器从积极释放那些的接收者表达式 消息,似乎不再被引用,而返回 指针可能仍在使用中。

    对 NSData.h 头文件的检查表明这也适用于从 iOS 6 开始。

    还要注意NS_RETURNS_INNER_POINTERclang 规范中被定义为__attribute__((objc_returns_inner_pointer)),这使得

    对象的生命周期将至少延长到以下时间中最早的一个: 返回的指针或从它派生的任何指针的最后一次使用, 在调用函数中; 或自动释放池恢复为 以前的状态。

    注意事项: 如果您使用的是比 Mountain Lion 或 iOS 6 更早的版本,您在声明本地 NSData 或 NSMutableData 对象时仍需要使用此处讨论的任何方法(例如,__attribute__((objc_precise_lifetime)))。

    此外,即使使用最新的编译器和 Apple 库,如果您使用较旧或第三方库的对象不使用 __attribute__((objc_returns_inner_pointer)) 装饰其内部指针返回方法,您将需要装饰此类的局部变量声明带有__attribute__((objc_precise_lifetime)) 的对象或使用答案中讨论的其他方法之一。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-08
      • 1970-01-01
      相关资源
      最近更新 更多