在大多数情况下,使用 ARC(自动引用计数),我们根本不需要考虑 Objective-C 对象的内存管理。不再允许创建 NSAutoreleasePool
,但是有一种新语法:
@autoreleasepool {
…
}
我的问题是,当我不应该手动释放/自动释放时,为什么我需要这个?
编辑:总结一下我从所有答案和评论中得到的简洁:
新语法:
@autoreleasepool { … }
是新语法
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
…
[pool drain];
更重要的是:
ARC 使用自动释放和释放。
它需要一个自动释放池来执行此操作。
ARC 不会为您创建自动释放池。但是:每个 Cocoa 应用程序的主线程中已经有一个自动释放池。
每个 Cocoa 应用程序的主线程中已经有一个自动释放池。
有两种情况你可能想使用@autoreleasepool:当你在一个辅助线程中并且没有自动释放池时,你必须自己制作以防止泄漏,例如 myRunLoop(...) { @autoreleasepool { ... } 返回成功; }。当您希望创建一个更本地的池时,正如@mattjgalloway 在他的回答中所示。
当你在一个辅助线程并且没有自动释放池时,你必须自己制作以防止泄漏,例如 myRunLoop(...) { @autoreleasepool { ... } return success; }。
当您希望创建一个更本地的池时,正如@mattjgalloway 在他的回答中所示。
ARC 并没有摆脱保留、发布和自动发布,它只是为您添加所需的内容。所以仍然有保留的调用,仍然有释放的调用,仍然有自动释放的调用,还有自动释放池。
他们对新的 Clang 3.0 编译器和 ARC 所做的其他更改之一是他们将 NSAutoReleasePool
替换为 @autoreleasepool
编译器指令。无论如何,NSAutoReleasePool
总是有点特殊的“对象”,他们这样做是为了使使用 one 的语法不会与对象混淆,因此它通常更简单一些。
所以基本上,您需要 @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);
}
}
}
}
当然,这是一个非常人为的示例,但是如果您在外部 for
循环中没有 @autoreleasepool
,那么您稍后将释放 100000000 个对象,而不是每次围绕外部 for
循环释放 10000 个对象。
更新: 另请参阅此答案 - https://stackoverflow.com/a/7950636/1068248 - 为什么 @autoreleasepool
与 ARC 无关。
更新:我查看了这里和wrote it up on my blog的内部情况。如果您在那里查看,您将确切地看到 ARC 正在做什么,以及编译器如何使用新样式 @autoreleasepool
以及它如何引入范围来推断有关保留、释放和释放内容的信息。需要自动释放。
@autoreleasepool
不会自动释放任何内容。它创建了一个自动释放池,这样当到达块的末尾时,在块处于活动状态时由 ARC 自动释放的任何对象都将被发送释放消息。 Apple 的 Advanced Memory Management Programming Guide 是这样解释的:
在自动释放池块结束时,在块内收到自动释放消息的对象会收到释放消息——对象每次在块内收到自动释放消息时都会收到释放消息。
release
消息,但如果保留计数 > 1 对象不会被释放。
人们经常将 ARC 误解为某种垃圾收集等。事实是,一段时间后,Apple 的人们(感谢 llvm 和 clang 项目)意识到 Objective-C 的内存管理(所有的 retains
和 releases
等)可以在编译时完全自动化。这是,只需阅读代码,甚至在它运行之前! :)
为此,只有一个条件:我们必须遵循 rules,否则编译器将无法在编译时自动执行该过程。因此,为了确保我们从不违反规则,我们不允许显式编写 release
、retain
等。这些调用由编译器自动注入到我们的代码中。因此在内部我们仍然有 autorelease
、retain
、release
等。只是我们不再需要编写它们了。
ARC 的 A 在编译时是自动的,这比在运行时像垃圾收集要好得多。
我们仍然有 @autoreleasepool{...}
,因为它不会违反任何规则,我们可以在需要时自由创建/耗尽我们的池 :)。
从方法返回新创建的对象需要自动释放池。例如考虑这段代码:
- (NSString *)messageOfTheDay {
return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}
在该方法中创建的字符串的保留计数为 1。现在谁来平衡保留计数和释放?
方法本身?不可能,它必须返回创建的对象,所以它不能在返回之前释放它。
方法的调用者?调用者不期望检索需要释放的对象,方法名称并不意味着创建了一个新对象,它只是表示返回了一个对象,并且这个返回的对象可能是一个需要释放的新对象,但它可能是好是一个现有的,没有。该方法返回的内容甚至可能取决于某些内部状态,因此调用者无法知道它是否必须释放该对象并且不必关心。
如果调用者必须总是按照约定释放所有返回的对象,那么每个不是新创建的对象在从方法返回之前总是必须保留,并且一旦超出范围就必须由调用者释放,除非它再次返回。在许多情况下,这将是非常低效的,因为如果调用者不会总是释放返回的对象,那么在许多情况下可以完全避免更改保留计数。
这就是为什么有自动释放池的原因,所以第一种方法实际上会变成
- (NSString *)messageOfTheDay {
NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
return [res autorelease];
}
对对象调用 autorelease
会将其添加到自动释放池中,但是将对象添加到自动释放池中的真正含义是什么?好吧,这意味着告诉您的系统“我希望您为我释放该对象,但在以后的某个时间,而不是现在;它有一个保留计数,需要通过释放来平衡,否则内存会泄漏,但我不能现在自己做,因为我需要对象在我当前的范围之外保持活力,而我的调用者也不会为我做这件事,它不知道需要这样做。所以将它添加到你的池中,一旦你清理那个池子,也为我清理我的对象。"
使用 ARC,编译器会为您决定何时保留对象、何时释放对象以及何时将其添加到自动释放池,但它仍然需要存在自动释放池才能从方法返回新创建的对象而不会泄漏内存。 Apple 刚刚对生成的代码进行了一些漂亮的优化,有时会在运行时消除自动释放池。这些优化要求调用者和被调用者都使用 ARC(记住混合 ARC 和非 ARC 是合法的,并且也得到官方支持),如果确实如此,只能在运行时知道。
考虑这个 ARC 代码:
// Callee
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
系统生成的代码可以像以下代码一样运行(即允许您自由混合 ARC 和非 ARC 代码的安全版本):
// Callee
- (SomeObject *)getSomeObject {
return [[[SomeObject alloc] init] autorelease];
}
// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];
(请注意,调用者中的保留/释放只是防御性安全保留,并非严格要求,没有它,代码将完全正确)
或者它可以像这段代码一样运行,以防在运行时检测到两者都使用 ARC:
// Callee
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];
如您所见,Apple 消除了 atuorelease,因此也消除了池销毁时的延迟对象释放,以及安全保留。要详细了解如何做到这一点以及幕后的真实情况,check out this blog post.
现在回到实际问题:为什么要使用 @autoreleasepool
?
对于大多数开发人员来说,今天在他们的代码中使用这种结构只有一个原因,那就是在适用的情况下保持较小的内存占用。例如考虑这个循环:
for (int i = 0; i < 1000000; i++) {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
假设每次调用 tempObjectForData
都可能创建一个新的 TempObject
,该 TempObject
返回自动释放。 for 循环将创建一百万个这些临时对象,这些临时对象都收集在当前的自动释放池中,并且只有在该池被销毁时,所有临时对象也会被销毁。在此之前,您在内存中有 100 万个这样的临时对象。
如果您改为这样编写代码:
for (int i = 0; i < 1000000; i++) @autoreleasepool {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
然后每次 for 循环运行时都会创建一个新池,并在每次循环迭代结束时销毁。这样,尽管循环运行了一百万次,但在任何时候最多只有一个临时对象在内存中徘徊。
过去,在管理线程(例如使用 NSThread
)时,您通常还必须自己管理自动释放池,因为只有主线程会自动为 Cocoa/UIKit 应用程序提供自动释放池。然而,这在今天几乎是遗留问题,因为今天你可能一开始就不会使用线程。您将使用 GCD DispatchQueue
或 NSOperationQueue
,这两个都为您管理顶级自动释放池,在运行块/任务之前创建并在完成后销毁。
这是因为您仍然需要向编译器提供有关自动释放对象何时可以安全超出范围的提示。
@autoreleasepool
,因为我不知道 ARC 是否会决定自动发布某些内容?
自动释放池块和线程 Cocoa 应用程序中的每个线程都维护自己的自动释放池块堆栈。如果您正在编写仅 Foundation 程序或分离线程,则需要创建自己的自动释放池块。如果您的应用程序或线程是长期存在的并且可能会生成大量自动释放的对象,那么您应该使用自动释放池块(如 AppKit 和 UIKit 在主线程上所做的);否则,自动释放的对象会累积并且您的内存占用会增加。如果您的分离线程不进行 Cocoa 调用,则不需要使用自动释放池块。注意:如果您使用 POSIX 线程 API 而不是 NSThread 创建辅助线程,则您不能使用 Cocoa,除非 Cocoa 处于多线程模式。 Cocoa 只有在分离了它的第一个 NSThread 对象后才进入多线程模式。要在辅助 POSIX 线程上使用 Cocoa,您的应用程序必须首先分离至少一个 NSThread 对象,该对象可以立即退出。可以使用 NSThread 类方法 isMultiThreaded 来测试 Cocoa 是否处于多线程模式。
...
在自动引用计数或 ARC 中,系统使用与 MRR 相同的引用计数系统,但它会在编译时为您插入适当的内存管理方法调用。强烈建议您将 ARC 用于新项目。如果您使用 ARC,通常不需要了解本文档中描述的底层实现,尽管它在某些情况下可能会有所帮助。有关 ARC 的更多信息,请参阅过渡到 ARC 发行说明。
TL;博士
为什么 ARC 仍然需要 @autoreleasepool?
@autoreleasepool
被 Objective-C 和 Swift 用于与内部的 autorelese
一起工作
当您使用纯 Swift 并分配 Swift 对象时 - ARC 会处理它
但是,如果您决定调用/使用在内部使用 autorelese
的 Foundation/Legacy Objective-C code
(NSData
, Data
),则在救援中使用 @autoreleasepool
//Swift
let imageData = try! Data(contentsOf: url)
//Data init uses Objective-C code with [NSData dataWithContentsOfURL] which uses `autorelese`
长答案
MRC、ARC、GC
Manual Reference Counting(MRC)
或 Manual Retain-Release(MRR)
作为开发人员,您负责手动计算对象上的引用
Automatic Reference Counting(ARC)
在 iOS v5.0 和带有 xCode v4.2 的 OS X Mountain Lion 中引入
Garbage Collection(GC)
适用于 Mac OS,但在 OS X Mountain Lion 中已弃用。必须移至 ARC
MRC 和 ARC 中的引用计数
//MRC
NSLog(@"Retain Count: %d", [variable retainCount]);
//ARC
NSLog(@"Retain Count: %ld", CFGetRetainCount((__bridge CFTypeRef) variable));
堆中的每个对象都有一个整数值,表示在其上指出了多少引用。当它等于 0 对象被系统释放
分配对象
使用参考计数
释放对象。当 retainCount == 0 时调用 deinit
MRC
A *a1 = [[A alloc] init]; //this A object retainCount = 1
A *a2 = a1;
[a2 retain]; //this A object retainCount = 2
// a1, a2 -> object in heap with retainCount
释放对象的正确方法:
释放如果只有这个 - 悬空指针。因为它仍然可以指向堆中的对象,并且可以发送消息 = nil 如果只有这个 - 内存泄漏。 deinit 不会被调用
A *a = [[A alloc] init]; //++retainCount = 1
[a release]; //--retainCount = 0
a = nil; //guarantees that even somebody else has a reference to the object, and we try to send some message thought variable `a` this message will be just skipped
使用引用计数(对象所有者规则):
(0 -> 1) 分配、新建、复制、可变复制
(+1)保留您可以根据需要多次拥有一个对象(您可以多次调用保留)
(-1) 释放 如果您是所有者,则必须释放它。如果您释放的数量超过了 retainCount,它将为 0
(-1) autorelease 将一个应该被释放的对象添加到自动释放池。此池将在 RunLoop 迭代周期结束时处理(这意味着何时所有任务将在堆栈上完成)[关于] 并且在该释放之后将应用于池中的所有对象
(-1) @autoreleasepool 强制在块结束时处理自动释放池。当您在循环中处理自动释放并希望尽快清除资源时使用它。如果您不这样做,您的内存占用将不断增加
autorelease
用于在其中分配新对象并返回它时的方法调用
- (B *)foo {
B *b1 = [[B alloc] init]; //retainCount = 1
//fix - correct way - add it to fix wrong way
//[b1 autorelease];
//wrong way(without fix)
return b;
}
- (void)testFoo {
B *b2 = [a foo];
[b2 retain]; //retainCount = 2
//some logic
[b2 release]; //retainCount = 1
//Memory Leak
}
@autoreleasepool
示例
- (void)testFoo {
for(i=0; i<100; i++) {
B *b2 = [a foo];
//process b2
}
}
弧
ARC
的最大优势之一是它会在 Compile Time 的底层自动插入 retain
、release
、autorelease
,作为开发人员,您不应该再关心它了
启用/禁用 ARC
//enable
-fobjc-arc
//disable
-fno-objc-arc
优先级从高到低的变体
//1. local file - most priority
Build Phases -> Compile Sources -> Compiler Flags(Select files -> Enter)
//2. global
Build Settings -> Other C Flags(OTHER_CFLAGS)
//3. global
Build Settings -> Objective-C Automatic Reference Counting(CLANG_ENABLE_OBJC_ARC)
检查 ARC 是否启用/禁用
Preprocessor
__has_feature
函数被使用
__has_feature(objc_arc)
编译时间
// error if ARC is Off. Force to enable ARC
#if ! __has_feature(objc_arc)
#error Please enable ARC for this file
#endif
//or
// error if ARC is On. Force to disable ARC
#if __has_feature(objc_arc)
#error Please disable ARC for this file
#endif
运行
#if __has_feature(objc_arc)
// ARC is On
NSLog(@"ARC on");
#else
// ARC is Off
NSLog(@"ARC off");
#endif
逆向工程(针对 Objective-C)
//ARC is enabled
otool -I -v <binary_path> | grep "<mrc_message>"
//e.g.
otool -I -v "/Users/alex/ARC_experiments.app/ARC_experiments" | grep "_objc_release"
//result
0x00000001000080e0 748 _objc_release
//<mrc_message>
_objc_retain
_objc_release
_objc_autoreleaseReturnValue
_objc_retainAutoreleaseReturnValue
_objc_retainAutoreleasedReturnValue
_objc_storeStrong
将 Objective-C MRC 迁移到 ARC 的工具
ARC 生成错误,您应手动删除 retain
、release
、autorelease
和其他问题
Edit -> Convert -> To Objective-C ARC...
带有 MRC 的新 Xcode
如果启用 MRC,则会收到下一个错误(警告)(但构建会成功)
//release/retain/autorelease/retainCount
'release' is unavailable: not available in automatic reference counting mode
ARC forbids explicit message send of 'release'
关于这个话题似乎有很多困惑(至少有 80 人现在可能对此感到困惑,并认为他们需要在他们的代码周围撒上@autoreleasepool)。
如果一个项目(包括它的依赖项)专门使用 ARC,那么 @autoreleasepool 永远不需要使用并且不会做任何有用的事情。 ARC 将在正确的时间处理释放对象。例如:
@interface Testing: NSObject
+ (void) test;
@end
@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }
+ (void) test
{
while(true) NSLog(@"p = %p", [Testing new]);
}
@end
显示:
p = 0x17696f80
dealloc
p = 0x17570a90
dealloc
一旦值超出范围,每个测试对象就会被释放,而无需等待退出自动释放池。 (同样的事情发生在 NSNumber 示例中;这只是让我们观察 dealloc。) ARC 不使用自动释放。
仍然允许使用 @autoreleasepool 的原因是混合 ARC 和非 ARC 项目,它们尚未完全过渡到 ARC。
如果你调用非 ARC 代码,它可能会返回一个自动释放的对象。在这种情况下,上述循环会泄漏,因为当前的自动释放池永远不会退出。这就是你想在代码块周围放置一个@autoreleasepool 的地方。
但是,如果您已经完全完成了 ARC 过渡,那么请忘记 autoreleasepool。
+ (Testing *) testing { return [Testing new] }
。然后你会看到 dealloc 直到稍后才会被调用。如果您将循环内部包装在 @autoreleasepool
块中,则此问题已修复。
+ (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
[UIImage imageWithData]
投入等式时,突然之间,我开始看到传统的 autorelease
行为,要求 @autoreleasepool
将峰值内存保持在某个合理的水平。
@autoreleasepool
中呢?如果我不控制自动释放或释放的内容(ARC 为我这样做),我应该如何知道何时设置自动释放池?