为什么 ARC 仍然需要 @autoreleasepool?

发布于 2025-01-01 23:53:23 字数 874 浏览 5 评论 0 原文

在大多数情况下,对于 ARC(自动引用计数),我们根本不需要考虑 Objective-C 对象的内存管理。不再允许创建 NSAutoreleasePool ,但是有一种新语法:

@autoreleasepool {
    …
}

我的问题是,当我不应该手动释放/自动释放时,为什么我需要它?


编辑:简洁地总结我从所有答案和评论中得到的内容:

新语法:

@autoreleasepool { … } 是新语法

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
…
[pool drain];

更重要的是:

  • ARC使用autorelease以及release
  • 为此,它需要一个自动释放池。
  • ARC 不会为您创建自动释放池。 但是:
    • 每个 Cocoa 应用程序的主线程中都已经有一个自动释放池。
  • 有两种情况您可能需要使用@autoreleasepool:
    1. 当你处于辅助线程并且没有自动释放池时,你必须自己创建一个以防止泄漏,例如 myRunLoop(...) { @autoreleasepool { ... } return success; }
    2. 当您希望创建一个更本地化的池时,正如 @mattjgalloway 在他的回答中所示。

For the most part with ARC (Automatic Reference Counting), we don't need to think about memory management at all with Objective-C objects. It is not permitted to create NSAutoreleasePools anymore, however there is a new syntax:

@autoreleasepool {
    …
}

My question is, why would I ever need this when I'm not supposed to be manually releasing/autoreleasing ?


EDIT: To sum up what I got out of all the anwers and comments succinctly:

New Syntax:

@autoreleasepool { … } is new syntax for

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
…
[pool drain];

More importantly:

  • ARC uses autorelease as well as release.
  • It needs an auto release pool in place to do so.
  • ARC doesn't create the auto release pool for you. However:
    • The main thread of every Cocoa app already has an autorelease pool in it.
  • There are two occasions when you might want to make use of @autoreleasepool:
    1. When you are in a secondary thread and there is no auto release pool, you must make your own to prevent leaks, such as myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. When you wish to create a more local pool, as @mattjgalloway has shown in his answer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

沉鱼一梦 2025-01-08 23:53:23

ARC 并没有去掉保留、发布和自动发布,它只是为您添加了所需的内容。因此仍然存在对保留的调用,仍然存在对释放的调用,仍然存在对自动释放的调用,并且仍然存在自动释放池。

他们对新的 Clang 3.0 编译器和 ARC 所做的其他更改之一是用 @autoreleasepool 编译器指令替换了 NSAutoReleasePool。 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);
            }
        }
    }
}

当然,这是一个精心设计的示例,但是如果您在外部 for 循环内没有 @autoreleasepool 那么您就会稍后将释放 100000000 个对象,而不是每次外层 for 循环释放 10000 个对象。

更新:
另请参阅此答案 - https://stackoverflow.com/a/7950636/1068248 - 了解原因 @autoreleasepool 与 ARC 无关。

更新:
我研究了这里发生的事情的内部情况和 把它写在我的博客上。如果您看一下那里,您将确切地看到 ARC 正在做什么,以及新样式 @autoreleasepool 以及它如何引入编译器如何使用范围来推断有关保留、释放和释放内容的信息。需要自动释放。

ARC doesn't get rid of retains, releases and autoreleases, it just adds in the required ones for you. So there are still calls to retain, there are still calls to release, there are still calls to autorelease and there are still auto release pools.

One of the other changes they made with the new Clang 3.0 compiler and ARC is that they replaced NSAutoReleasePool with the @autoreleasepool compiler directive. NSAutoReleasePool was always a bit of a special "object" anyway and they made it so that the syntax of using one is not confused with an object so that it's generally a bit more simple.

So basically, you need @autoreleasepool because there are still auto release pools to worry about. You just don't need to worry about adding in autorelease calls.

An example of using an auto release pool:

- (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);
            }
        }
    }
}

A hugely contrived example, sure, but if you didn't have the @autoreleasepool inside the outer for-loop then you'd be releasing 100000000 objects later on rather than 10000 each time round the outer for-loop.

Update:
Also see this answer - https://stackoverflow.com/a/7950636/1068248 - for why @autoreleasepool is nothing to do with ARC.

Update:
I took a look into the internals of what's going on here and wrote it up on my blog. If you take a look there then you will see exactly what ARC is doing and how the new style @autoreleasepool and how it introduces a scope is used by the compiler to infer information about what retains, releases & autoreleases are required.

白龙吟 2025-01-08 23:53:23

@autoreleasepool 不会自动释放任何内容。它创建一个自动释放池,以便当到达块末尾时,在该块处于活动状态时由 ARC 自动释放的任何对象都将收到释放消息。 Apple 的高级内存管理编程指南如此解释:

在自动释放池块的末尾,块内收到自动释放消息的对象将被发送一条释放消息 - 每次在块内发送自动释放消息时,对象都会收到一条释放消息。

@autoreleasepool doesn't autorelease anything. It creates an autorelease pool, so that when the end of block is reached, any objects that were autoreleased by ARC while the block was active will be sent release messages. Apple's Advanced Memory Management Programming Guide explains it thus:

At the end of the autorelease pool block, objects that received an autorelease message within the block are sent a release message—an object receives a release message for each time it was sent an autorelease message within the block.

沧桑㈠ 2025-01-08 23:53:23

人们经常将 ARC 误解为某种垃圾收集等。事实是,一段时间后,Apple 的人们(感谢 llvm 和 clang 项目)意识到 Objective-C 的内存管理(所有保留释放等)可以在编译时完全自动化。这就是,只需阅读代码,甚至在运行之前! :)

为此,只有一个条件:我们必须遵循 规则,否则编译器将无法在编译时自动执行该过程。因此,为了确保我们永远不违反规则,我们不允许显式编写releaseretain等。这些调用会自动注入由编译器写入我们的代码。因此,我们内部仍然有 autoreleaseretainrelease 等,只是我们不再需要编写它们了。

ARC的A在编译时是自动的,这比像垃圾回收这样在运行时要好得多。

我们仍然有 @autoreleasepool{...} 因为拥有它不会违反任何规则,我们可以在需要时随时创建/耗尽我们的池:)。

People often misunderstand ARC for some kind of garbage collection or the like. The truth is that, after some time people at Apple (thanks to llvm and clang projects) realized that Objective-C's memory administration (all the retains and releases, etc.) can be fully automatized at compile time. This is, just by reading the code, even before it is run! :)

In order to do so there is only one condition: We MUST follow the rules, otherwise the compiler would not be able to automate the process at compile time. So, to ensure that we never break the rules, we are not allowed to explicitly write release, retain, etc. Those calls are Automatically injected into our code by the compiler. Hence internally we still have autoreleases, retain, release, etc. It is just we don't need to write them anymore.

The A of ARC is automatic at compile time, which is much better than at run time like garbage collection.

We still have @autoreleasepool{...} because having it does not break any of the rules, we are free create/drain our pool anytime we need it :).

枫林﹌晚霞¤ 2025-01-08 23:53:23

从方法返回新创建的对象需要自动释放池。例如,考虑这段代码:

- (NSString *)messageOfTheDay {
    return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}

在方法中创建的字符串的保留计数将为 1。现在谁来平衡保留计数和释放计数?

方法本身?不可能,它必须返回创建的对象,因此在返回之前不能释放它。

该方法的调用者?调用者并不期望检索一个需要释放的对象,方法名称并不暗示创建一个新对象,它只是表示返回一个对象,并且这个返回的对象可能是一个需要释放的新对象,但也可能是我们可能是现有的一个,但没有。该方法返回的内容甚至可能取决于某些内部状态,因此调用者无法知道是否必须释放该对象,并且不必关心。

如果调用者必须始终按照约定释放所有返回的对象,则每个不是新创建的对象在从方法返回之前都必须保留,并且一旦超出范围,调用者就必须将其释放,除非它再次返回。在许多情况下,这将是非常低效的,因为如果调用者并不总是释放返回的对象,则在许多情况下可以完全避免更改保留计数。

这就是为什么有自动释放池,所以第一个方法实际上会变成

- (NSString *)messageOfTheDay {
    NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    return [res autorelease];
}

在一个对象上调用 autorelease 将其添加到自动释放池中,但这到底是什么意思,将对象添加到自动释放池中?好吧,这意味着告诉你的系统“我希望你为我释放该对象,但在以后的某个时间,而不是现在;它有一个保留计数,需要通过释放来平衡,否则内存会泄漏,但我不能现在我自己做这件事,因为我需要该对象在我当前的范围之外保持活动状态,而我的调用者也不会为我做这件事,它不知道需要这样做,所以将其添加到您的池中,一旦您完成。清理那个池,也清理我的对象me."

使用 ARC,编译器会为您决定何时保留对象、何时释放对象以及何时将其添加到自动释放池,但它仍然需要存在自动释放池才能返回新创建的对象从方法中获取对象而不泄漏内存。苹果刚刚对生成的代码做了一些巧妙的优化,有时会在运行时消除自动释放池。这些优化要求调用者和被调用者都使用 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,因此还有池中的延迟对象释放被破坏,以及安全保留。要了解更多关于这是如何实现的以及幕后到底发生了什么,查看这篇博文。

现在讨论实际问题:为什么要使用 @autoreleasepool

对于大多数开发人员来说,现在在代码中使用此构造的原因只有一个,那就是在适用的情况下保持较小的内存占用。例如,考虑这个循环:

for (int i = 0; i < 1000000; i++) {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

假设每次调用 tempObjectForData 都可能创建一个新的 TempObject 并返回 autorelease。 for 循环将创建一百万个临时对象,这些对象全部收集在当前的自动释放池中,并且只有在该池被销毁后,所有临时对象也会被销毁。在此之前,内存中将有一百万个临时对象。

如果您编写这样的代码:

for (int i = 0; i < 1000000; i++) @autoreleasepool {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

则每次 for 循环运行时都会创建一个新池,并在每次循环迭代结束时销毁。这样,尽管循环运行了一百万次,但任何时候最多有一个临时对象挂在内存中。

过去,在管理线程时(例如使用 NSThread),您通常还必须自己管理自动释放池,因为只有主线程会自动拥有 Cocoa/UIKit 应用程序的自动释放池。然而,这在今天几乎是遗留下来的,因为今天您可能不会一开始就使用线程。您可以使用 GCD DispatchQueue 或 NSOperationQueue ,这两个都可以为您管理顶级自动释放池,在运行块/任务之前创建并销毁一次完成了。

Autorelease pools are required for returning newly created objects from a method. E.g. consider this piece of code:

- (NSString *)messageOfTheDay {
    return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}

The string created in the method will have a retain count of one. Now who shall balance that retain count with a release?

The method itself? Not possible, it has to return the created object, so it must not release it prior to returning.

The caller of the method? The caller does not expect to retrieve an object that needs releasing, the method name does not imply that a new object is created, it only says that an object is returned and this returned object may be a new one requiring a release but it may as well be an existing one that doesn't. What the method does return may even depend on some internal state, so the the caller cannot know if it has to release that object and it shouldn't have to care.

If the caller had to always release all returned object by convention, then every object not newly created would always have to be retained prior to returning it from a method and it would have to be released by the caller once it goes out of scope, unless it is returned again. This would be highly inefficient in many cases as one can completely avoid altering retain counts in many cases if the caller will not always release the returned object.

That's why there are autorelease pools, so the first method will in fact become

- (NSString *)messageOfTheDay {
    NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    return [res autorelease];
}

Calling autorelease on an object adds it to the autorelease pool, but what does that really mean, adding an object to the autorelease pool? Well, it means telling your system "I want you to to release that object for me but at some later time, not now; it has a retain count that needs to be balanced by a release otherwise memory will leak but I cannot do that myself right now, as I need the object to stay alive beyond my current scope and my caller won't do it for me either, it has no knowledge that this needs to be done. So add it to your pool and once you clean up that pool, also clean up my object for me."

With ARC the compiler decides for you when to retain an object, when to release an object and when to add it to an autorelease pool but it still requires the presence of autorelease pools to be able to return newly created objects from methods without leaking memory. Apple has just made some nifty optimizations to the generated code which will sometimes eliminate autorelease pools during runtime. These optimizations require that both, the caller and the callee are using ARC (remember mixing ARC and non-ARC is legal and also officially supported) and if that is actually the case can only be known at runtime.

Consider this ARC Code:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];

The code that the system generates, can either behave like the following code (that is the safe version that allows you to freely mix ARC and non-ARC code):

// Callee
- (SomeObject *)getSomeObject {
    return [[[SomeObject alloc] init] autorelease];
}

// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];

(Note the retain/release in the caller is just a defensive safety retain, it's not strictly required, the code would be perfectly correct without it)

Or it can behave like this code, in case that both are detected to use ARC at runtime:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];

As you can see, Apple eliminates the atuorelease, thus also the delayed object release when the pool is destroyed, as well as the safety retain. To learn more about how that is possible and what's really going on behind the scenes, check out this blog post.

Now to the actual question: Why would one use @autoreleasepool?

For most developers, there's only one reason left today for using this construct in their code and that is to keep the memory footprint small where applicable. E.g. consider this loop:

for (int i = 0; i < 1000000; i++) {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Assume that every call to tempObjectForData may create a new TempObject that is returned autorelease. The for-loop will create one million of these temp objects which are all collected in the current autoreleasepool and only once that pool is destroyed, all the temp objects are destroyed as well. Until that happens, you have one million of these temp objects in memory.

If you write the code like this instead:

for (int i = 0; i < 1000000; i++) @autoreleasepool {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Then a new pool is created every time the for-loop runs and is destroyed at the end of each loop iteration. That way at most one temp object is hanging around in memory at any time despite the loop running one million times.

In the past you often had to also manage autoreleasepools yourself when managing threads (e.g. using NSThread) as only the main thread automatically has an autorelease pool for a Cocoa/UIKit app. Yet this is pretty much legacy today as today you probably wouldn't use threads to begin with. You'd use GCD DispatchQueue's or NSOperationQueue's and these two both do manage a top level autorelease pool for you, created before running a block/task and destroyed once done with it.

夕色琉璃 2025-01-08 23:53:23

这是因为您仍然需要向编译器提供有关自动释放对象何时超出范围是安全的提示。

It's because you still need to provide the compiler with hints about when it is safe for autoreleased objects to go out of scope.

往昔成烟 2025-01-08 23:53:23

引用自 https://developer.apple.com /library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html

自动释放池块和线程

Cocoa 应用程序中的每个线程都维护自己的堆栈
自动释放池块。如果您正在编写仅限基础的程序
或者如果你分离一个线程,你需要创建你自己的自动释放
池块。

如果您的应用程序或线程寿命较长并且可能会生成
很多自动释放的对象,你应该使用自动释放池块
(就像 AppKit 和 UIKit 在主线程上所做的那样);否则,自动释放
对象不断累积,内存占用也会增加。如果你的独立
线程不进行 Cocoa 调用,您不需要使用
自动释放池块。

注意:如果您使用 POSIX 线程 API 创建辅助线程
除了 NSThread,你不能使用 Cocoa,除非 Cocoa 位于
多线程模式。 Cocoa只有在之后才进入多线程模式
分离它的第一个 NSThread 对象。在辅助 POSIX 上使用 Cocoa
线程,您的应用程序必须首先分离至少一个 NSThread
对象,可以立即退出。你可以测试一下Cocoa是否在
多线程模式使用 NSThread 类方法 isMultiThreaded。

...

在自动引用计数(ARC)中,系统使用相同的
引用计数系统与 MRR 一样,但它插入了适当的内存
管理方法在编译时需要您。你很坚强
鼓励在新项目中使用 ARC。如果你使用ARC,有
通常不需要了解底层实现
本文档中进行了描述,尽管在某些情况下可能会
有帮助。有关 ARC 的更多信息,请参阅过渡到 ARC 发行说明。

Quoted from https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html:

Autorelease Pool Blocks and Threads

Each thread in a Cocoa application maintains its own stack of
autorelease pool blocks. If you are writing a Foundation-only program
or if you detach a thread, you need to create your own autorelease
pool block.

If your application or thread is long-lived and potentially generates
a lot of autoreleased objects, you should use autorelease pool blocks
(like AppKit and UIKit do on the main thread); otherwise, autoreleased
objects accumulate and your memory footprint grows. If your detached
thread does not make Cocoa calls, you do not need to use an
autorelease pool block.

Note: If you create secondary threads using the POSIX thread APIs
instead of NSThread, you cannot use Cocoa unless Cocoa is in
multithreading mode. Cocoa enters multithreading mode only after
detaching its first NSThread object. To use Cocoa on secondary POSIX
threads, your application must first detach at least one NSThread
object, which can immediately exit. You can test whether Cocoa is in
multithreading mode with the NSThread class method isMultiThreaded.

...

In Automatic Reference Counting, or ARC, the system uses the same
reference counting system as MRR, but it insertsthe appropriate memory
management method callsfor you at compile-time. You are strongly
encouraged to use ARC for new projects. If you use ARC, there is
typically no need to understand the underlying implementation
described in this document, although it may in some situations be
helpful. For more about ARC, see Transitioning to ARC Release Notes.

韵柒 2025-01-08 23:53:23

TL;DR

为什么 ARC 仍然需要@autoreleasepool?

Objective-C 使用 @autoreleasepool ,Swift 使用 autoreleasepool 来释放放入自动释放池的对象(通过使用 autorelease 内)手动(不是在 RunLoop 迭代周期结束时)

当您使用纯 Swift(没有 Objective-C 对象)并分配 Swift 对象时 - ARC 会自动处理它

但是如果您决定调用/使用 Foundation /旧版 Objective-C code(NSData, Data...) 在内部使用 autorelese,然后在 @autoreleasepool 中使用救援

//Swift
//Data.init() uses Objective-C code with [NSData dataWithContentsOfURL] which uses `autorelese` inside
let imageData = try! Data(contentsOf: url)

[MRC、ARC、GC]

TL;DR

Why is @autoreleasepool still needed with ARC?

@autoreleasepool is used by Objective-C and autoreleasepool by Swift to release objects which were put into auto release pool (by using autorelease inside) manually(not at the end of RunLoop iteration cycle)

When you work with pure Swift(without Objective-C objects) and allocate Swift objects - ARC handles it automatically

But if you decide call/use Foundation/Legacy Objective-C code(NSData, Data...) which uses autorelese inside then @autoreleasepool in a rescue

//Swift
//Data.init() uses Objective-C code with [NSData dataWithContentsOfURL] which uses `autorelese` inside
let imageData = try! Data(contentsOf: url)

[MRC, ARC, GC]

孤者何惧 2025-01-08 23:53:23

这个主题似乎有很多困惑(至少有 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 不使用 autorelease。

仍然允许使用 @autoreleasepool 的原因是对于混合 ARC 和非 ARC 项目,这尚未完全过渡到 ARC。

如果调用非 ARC 代码,它可能会返回一个自动释放的对象。在这种情况下,上面的循环将会泄漏,因为当前的自动释放池永远不会退出。这就是您想要在代码块周围放置 @autoreleasepool 的地方。

但如果你已经完全完成了 ARC 过渡,那么就忘掉 autoreleasepool 吧。

There seems to be a lot of confusion on this topic (and at least 80 people who probably are now confused about this and think they need to sprinkle @autoreleasepool around their code).

If a project (including its dependencies) exclusively uses ARC, then @autoreleasepool never needs to be used and will do nothing useful. ARC will handle releasing objects at the correct time. For example:

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

displays:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

Each Testing object is deallocated as soon as the value goes out of scope, without waiting for an autorelease pool to be exited. (The same thing happens with the NSNumber example; this just lets us observe the dealloc.) ARC does not use autorelease.

The reason @autoreleasepool is still allowed is for mixed ARC and non-ARC projects, which haven't yet completely transitioned to ARC.

If you call into non-ARC code, it may return an autoreleased object. In that case, the above loop would leak, since the current autorelease pool will never be exited. That's where you'd want to put an @autoreleasepool around the code block.

But if you've completely made the ARC transition, then forget about autoreleasepool.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文