mk1*_*k12 191 memory-management objective-c xcode4.2 automatic-ref-counting
在大多数情况下使用ARC(自动引用计数),我们不需要考虑使用Objective-C对象的内存管理.不允许再创建NSAutoreleasePools,但是有一个新的语法:
@autoreleasepool {
…
}
Run Code Online (Sandbox Code Playgroud)
我的问题是,当我不应该手动释放/自动释放时,为什么我需要这个呢?
编辑:总结我从所有的答案和评论中得到的简洁:
新语法:
@autoreleasepool { … } 是新的语法
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
…
[pool drain];
Run Code Online (Sandbox Code Playgroud)
更重要的是:
autorelease以及release.@autoreleasepool:
myRunLoop(…) { @autoreleasepool { … } return success; }.mat*_*way 212
ARC没有摆脱保留,发布和自动释放,它只是为您添加所需的.所以仍有调用保留,仍有调用释放,仍有调用自动释放,仍有自动释放池.
他们使用新的Clang 3.0编译器和ARC进行的其他一项更改是NSAutoReleasePool用@autoreleasepool编译器指令替换它们.NSAutoReleasePool无论如何,它总是有点特殊的"对象",并且它们使得使用它的语法不会与对象混淆,因此它通常更简单一些.
所以基本上,你需要@autoreleasepool因为仍然需要担心自动释放池.您只需要担心添加autorelease呼叫.
使用自动释放池的示例:
- (void)useALoadOfNumbers {
for (int j = 0; j < 10000; ++j) {
@autoreleasepool {
for (int i = 0; i < 10000; ++i) {
NSNumber *number = [NSNumber numberWithInt:(i+j)];
NSLog(@"number = %p", number);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
一个非常人为的例子,当然,如果你没有@autoreleasepool在外部for循环中,那么你将在以后释放100000000个对象,而不是每次围绕外循环释放10000个for.
更新:
另请参阅此答案 - /sf/answers/556544551/ - 为什么@autoreleasepool与ARC无关.
更新:
我看了一下这里发生了什么,并在我的博客上写下来.如果您查看那里,那么您将看到ARC正在做什么以及@autoreleasepool编译器如何使用新样式及其如何引入范围来推断需要保留,释放和自动释放的信息.
out*_*tis 15
@autoreleasepool不会自动释放任何东西.它创建一个自动释放池,以便在到达块结束时,在块处于活动状态时由ARC自动释放的任何对象将被发送释放消息.Apple的高级内存管理编程指南解释如下:
在自动释放池块的末尾,在块中接收到自动释放消息的对象被发送释放消息 - 对象在每次在块内发送自动释放消息时接收释放消息.
人们经常误解ARC进行某种垃圾收集等.事实是,经过一段时间后人们在苹果(感谢LLVM和铛项目)意识到,Objective-C的内存管理(所有retains和releases等),可以完全在自动化的编译时间.这就是通过阅读代码,甚至在它运行之前!:)
为了做到这一点,只有一个条件:我们必须遵循规则,否则编译器将无法在编译时自动化该过程.因此,为了确保我们永远不会打破规则,我们是不允许明确写入release,retain等这些调用由编译器自动注入到我们的代码.因此,在内部我们还是有autoreleaseS, retain,release等它只是我们并不需要把它们写下去了.
ARC的A在编译时是自动的,这比在垃圾收集时的运行时要好得多.
我们仍然拥有@autoreleasepool{...}它,因为它没有违反任何规则,我们可以随时创建/消耗我们的池我们需要它:).
从方法返回新创建的对象需要自动释放池。例如考虑这段代码:
- (NSString *)messageOfTheDay {
return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}
Run Code Online (Sandbox Code Playgroud)
在该方法中创建的字符串的保留计数将为 1。现在谁来平衡保留计数和释放计数?
方法本身?不可能,它必须返回创建的对象,因此在返回之前不能释放它。
该方法的调用者?调用者并不期望检索一个需要释放的对象,方法名称并不暗示创建一个新对象,它只是表示返回一个对象,并且这个返回的对象可能是一个需要释放的新对象,但也可能是我们可能是现有的一个,但没有。该方法返回的内容甚至可能取决于某些内部状态,因此调用者无法知道是否必须释放该对象,并且不必关心。
如果调用者必须始终按照约定释放所有返回的对象,则每个不是新创建的对象在从方法返回之前都必须保留,并且一旦超出范围,调用者就必须将其释放,除非它再次被返回。在许多情况下,这将是非常低效的,因为如果调用者并不总是释放返回的对象,则在许多情况下可以完全避免更改保留计数。
这就是为什么有自动释放池,所以第一个方法实际上会变成
- (NSString *)messageOfTheDay {
NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
return [res autorelease];
}
Run Code Online (Sandbox Code Playgroud)
调用autorelease一个对象会将其添加到自动释放池中,但这到底意味着什么,将对象添加到自动释放池中?好吧,这意味着告诉你的系统“我希望你为我释放该对象,但在以后的某个时间,而不是现在;它有一个保留计数,需要通过释放来平衡,否则内存会泄漏,但我自己不能这样做现在,因为我需要该对象在当前范围之外保持活动状态,并且我的调用者也不会为我执行此操作,所以它不知道需要执行此操作。因此,将其添加到您的池中,一旦您清理了该对象泳池,还帮我清理我的物体。 ”
使用 ARC,编译器可以为您决定何时保留对象、何时释放对象以及何时将其添加到自动释放池,但它仍然需要自动释放池的存在,以便能够从方法返回新创建的对象而不泄漏内存。苹果刚刚对生成的代码做了一些巧妙的优化,有时会在运行时消除自动释放池。这些优化要求调用者和被调用者都使用 ARC(请记住,混合 ARC 和非 ARC 是合法的,并且也是官方支持的),并且是否确实如此只能在运行时知道。
考虑这个 ARC 代码:
// Callee
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
Run Code Online (Sandbox Code Playgroud)
系统生成的代码可以像以下代码一样运行(这是允许您自由混合 ARC 和非 ARC 代码的安全版本):
// Callee
- (SomeObject *)getSomeObject {
return [[[SomeObject alloc] init] autorelease];
}
// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];
Run Code Online (Sandbox Code Playgroud)
(注意调用者中的保留/释放只是一种防御性安全保留,不是严格要求的,没有它代码将完全正确)
或者它可以像下面的代码一样运行,以防在运行时检测到两者都使用 ARC:
// Callee
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,Apple 消除了 atuorelease,因此也消除了池被销毁时的延迟对象释放,以及安全保留。要了解更多关于这是如何实现的以及幕后到底发生了什么,请查看这篇博客文章。
现在讨论实际问题:为什么要使用@autoreleasepool?
对于大多数开发人员来说,现在在代码中使用此构造的原因只有一个,那就是在适用的情况下保持较小的内存占用。例如考虑这个循环:
for (int i = 0; i < 1000000; i++) {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
Run Code Online (Sandbox Code Playgroud)
假设每次调用都tempObjectForData可能创建一个TempObject返回 autorelease 的新对象。for 循环将创建一百万个临时对象,这些对象全部收集在当前的自动释放池中,并且只有在该池被销毁后,所有临时对象也会被销毁。在此之前,内存中将有一百万个临时对象。
如果你写这样的代码:
for (int i = 0; i < 1000000; i++) @autoreleasepool {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
Run Code Online (Sandbox Code Playgroud)
然后,每次 for 循环运行时都会创建一个新池,并在每次循环迭代结束时销毁。这样,尽管循环运行了一百万次,但任何时候最多有一个临时对象挂在内存中。
过去,在管理线程(例如使用NSThread)时,您通常还必须自己管理自动释放池,因为只有主线程会自动拥有 Cocoa/UIKit 应用程序的自动释放池。然而,这在今天几乎是遗留下来的,因为今天您可能不会一开始就使用线程。您可以使用 GCDDispatchQueue或NSOperationQueue,这两个都为您管理顶级自动释放池,在运行块/任务之前创建并在完成后销毁。
| 归档时间: |
|
| 查看次数: |
65884 次 |
| 最近记录: |