我想澄清一些事情。
假设我有以下代码:
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
for (int i = 0; i < 5000000; i++) {
NSString *s = [NSString stringWithFormat:@"Hello, %@!", @"World"];
}
}
这将在此函数调用中创建500万个自动释放的字符串。我希望这可以保留这些对象直到应用程序终止,因为我看到的唯一的@autoreleasepool是在main.m中包装应用程序实例化的那个。但事实并非如此。在这个函数调用结束时,它们似乎都被调用了它们并从内存中删除。
本文件:
表示“Application Kit在事件循环的每个循环开始时在主线程上创建一个自动释放池,并在最后将其排出,从而释放处理事件时生成的任何自动释放的对象。”
这对我有意义,但这是在UIKit下,而不是Application Kit。我的问题是,UIKit / Cocoa Touch在这种情况下做了同样的事情,还是我的对象被释放有另一种解释?
谢谢!
答案 0 :(得分:20)
Andrew回答了你的主要问题,是的,你的自动释放池将在主运行循环的每个循环中耗尽。因此,当您回到主运行循环时,viewDidLoad
中创建的任何自动释放对象可能会立即被耗尽。它们肯定不会被保留“直到申请终止。”
但我们应该小心:您明确假设这些对象被添加到自动释放池中。这个假设有几点需要注意:
过去(并且仍然需要ARC-MRC互操作性),当从名称不以alloc
,new
,copy
开头的方法返回对象时或者mutableCopy
,这些对象会自动释放对象,仅在自动释放池耗尽时释放(即当您返回到运行循环时)。
但ARC最大限度地减少了对自动释放池的需求(参见http://rentzsch.tumblr.com/post/75082194868/arcs-fast-autorelease,其中讨论callerAcceptsFastAutorelease
,现在称为callerAcceptsOptimizedReturn
,prepareOptimizedReturn
,所以你经常不会看到这种autorelease
行为。因此,如果库和调用者都使用ARC,则对象可能不会放在自动释放池中,而是如果不需要ARC,ARC会立即巧妙地释放它们。
对于当代ARC项目,通常不需要自动释放池。但是在某些特殊情况下,人们仍然可以从使用自动释放池中受益。我将在下面概述其中一个案例。
请考虑以下代码:
#import "ViewController.h"
#import <sys/kdebug_signpost.h>
typedef enum : NSUInteger {
InnerLoop = 1,
MainLoop = 2
} MySignPostCodes;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"png"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
kdebug_signpost_start(MainLoop, 0, 0, 0, 1);
for (int j = 0; j < 500; i++) {
NSData *data = [NSData dataWithContentsOfURL:fileURL];
UIImage *image = [[UIImage alloc] initWithData:data];
NSLog(@"%p", NSStringFromCGSize(image.size)); // so it's not optimized out
[NSThread sleepForTimeInterval:0.01];
}
kdebug_signpost_end(MainLoop, 0, 0, 0, 1);
});
}
@end
以下代码将向自动释放池添加500,000个对象,只有当我回到运行循环时才会耗尽这些对象:
在这种情况下,您可以使用自动释放池来最小化高水位线:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"png"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
kdebug_signpost_start(MainLoop, 0, 0, 0, 1);
for (int j = 0; j < 5; j++) {
@autoreleasepool {
kdebug_signpost_start(InnerLoop, 0, 0, 0, 2);
for (long i = 0; i < 100; i++) {
NSData *data = [NSData dataWithContentsOfURL:fileURL];
UIImage *image = [[UIImage alloc] initWithData:data];
NSLog(@"%p", NSStringFromCGSize(image.size)); // so it's not optimized out
[NSThread sleepForTimeInterval:0.01];
}
kdebug_signpost_end(InnerLoop, 0, 0, 0, 2);
}
}
kdebug_signpost_end(MainLoop, 0, 0, 0, 1);
});
}
@end
使用ARC的底线,当它使用自动释放对象并且当变量超出范围时显式释放它时,并不总是显而易见的。您可以通过检查仪器中的行为来确认这一点。
顺便说一句,在使用NSString
类时,我会因为得出太多一般的内存管理结论而保持警惕,因为它已经过高度优化,并不总是符合标准的内存管理实践。
答案 1 :(得分:7)
是的,UIKit做同样的事情。系统创建的主线程自动释放池在每个运行循环周期结束时耗尽。最好不要在自己的代码中依赖这个确切的生命周期。如果您手动创建新线程(使用例如NSThread),则您负责在该线程上创建自动释放池。
编辑:Rob的回答提供了一些关于ARC下行为的更好的附加信息。一般来说,可以说,由于ARC能够进行一些优化,对象不太可能最终进入自动释放池。
答案 2 :(得分:0)
我认为当你将新对象分配给用来存放对象的引用时,原始对象会立即释放(如果没有其他任何东西指向它 - 引用计数变为零)使用ARC并假设默认strong
引用,如循环示例中所示。
MyObject *object = [[MyObject alloc] init]; // obj1, ref count 1 because strong
object = [[MyObject alloc] init]; // obj2, ref count of obj1 should be 0
// so obj1 gets released
Apple在Transitioning to ARC Release Notes
中说明尝试不再考虑保留/释放调用的位置,而是考虑应用程序算法。考虑对象中的“强弱”指针,对象所有权以及可能的保留周期。
听起来release
在为对象分配新值时被调用,
来自clang Clang 3.4 documentation
OBJECTIVE-C AUTOMATIC REFERENCE COUNTING (ARC)
评估赋值运算符时会发生赋值。该 语义因资格而异:
对于__strong对象,首先保留新的指针;第二, lvalue加载了原始语义;第三,新的指针是 用原始语义存储到左值中;最后,老了 指针被释放。这不是原子地执行的;外部 必须使用同步才能使这个安全 并发加载和存储。