Iphone ARC下紧环中UIKit的内存管理

Iphone ARC下紧环中UIKit的内存管理,iphone,ios,objective-c,cocoa-touch,automatic-ref-counting,Iphone,Ios,Objective C,Cocoa Touch,Automatic Ref Counting,我想了解更多关于如何在ARC紧循环下最好地处理内存管理的信息。特别是,我正在编写一个应用程序,它有一个while循环,这个循环持续了很长一段时间,我注意到,尽管已经实现了(我认为是)ARC中的最佳实践,堆仍在无限地增长 为了说明我遇到的问题,我首先设置了以下故意失败的测试: while (true) { NSMutableArray *array = [NSMutableArray arrayWithObject:@"Foo"]; [array addObjec

我想了解更多关于如何在ARC紧循环下最好地处理内存管理的信息。特别是,我正在编写一个应用程序,它有一个
while
循环,这个循环持续了很长一段时间,我注意到,尽管已经实现了(我认为是)ARC中的最佳实践,堆仍在无限地增长

为了说明我遇到的问题,我首先设置了以下故意失败的测试:

while (true) {         
    NSMutableArray *array = [NSMutableArray arrayWithObject:@"Foo"];
    [array addObject:@"bar"]; // do something with it to prevent compiler optimisations from skipping over it entirely
}
运行此代码并使用分配工具进行分析表明,内存使用量只是无限增加。但是,将其包装在一个
@autoreleasepool
中,如下所示,可以立即解决该问题,并保持内存使用量良好且较低:

while (true) {
    @autoreleasepool {         
        NSMutableArray *array = [NSMutableArray arrayWithObject:@"Foo"];
        [array addObject:@"bar"];
    }
}
太好了!这一切似乎都很好——对于使用
[[…alloc]init]
创建的非自动删除实例,它甚至可以很好地工作(正如预期的那样)。在我开始涉及任何
UIKit
类之前,一切都正常

例如,让我们创建一个
ui按钮
,看看会发生什么:

while (true) {
    @autoreleasepool {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        button.frame = CGRectZero;
    }
}
现在,内存使用量无限增加——实际上,
@autoreleasepool
似乎没有任何效果

因此问题是为什么
@autoreleasepool
NSMutableArray
工作良好,并保持内存在检查中,但当应用到
ui按钮时,堆会继续增长?

最重要的是,在这样一个无休止的循环中使用
UIKit
类时,如何防止堆无休止地扩展,这告诉我们
while(true)
while(keeprunningforlongtime)
样式循环中ARC的最佳实践是什么

我的直觉是(我可能完全错了)这可能是关于
while(true)
如何阻止运行循环的问题,这是将
UIKit
实例保留在内存中,而不是释放它们。。。但很明显,我对ARC的理解中遗漏了一些东西


(为了消除一个明显的原因,
NSZombiedEnabled
未启用。)

我在这里猜测,但是,它可以归结为这样一个事实:与UI相关的对象特别倾向于使用GCD或类似的(例如performSelectorOnMainThread:…)来确保某些操作在主线程上发生。这是您所怀疑的——排队的块或其他执行单元维护对实例的引用,等待其在runloop中的时间执行,但从未获得它

通常,阻止运行循环是不好的。曾几何时,这是一种比较常见的情况——阻力跟踪通常是通过这种方式完成的(或者有效地通过在阻力进行时仅在特殊模式下运行runloop)。但这会导致奇怪的交互甚至死锁,因为很多代码的设计都没有考虑到这种可能性——特别是在异步性为王的GCD世界中


请记住,如果愿意,您可以在while循环中显式运行runloop,虽然这与让它自然运行并不完全相同,但它通常是有效的。

至于UI*对象为什么会无限增长?内部实施细节。很可能是某种缓存或运行循环交互,通过阻止主运行循环,可以有效地禁用这种交互

这让我想到了真正的答案:

最重要的是,如何防止堆无限扩展 当在这样一个无休止的循环中使用UIKit类时,会发生什么 这告诉我们有关ARC in while(true)或 而(长时间保持修剪)样式循环

如何修复它请勿在主线程上使用紧循环请勿阻塞主运行循环

即使要解决UI*引起的堆增长问题,如果在主线程上使用
while(…)
循环,程序仍然无法工作。iOS应用程序和Cocoa应用程序的整个设计是,主线程有一个主运行循环,该主运行循环必须可以自由运行


如果没有?你的应用程序不会对用户输入做出响应(最终会被系统终止),你的绘图代码也不可能像预期的那样工作(因为运行循环会合并脏区域,并根据需要与主线程一起绘图,通常会卸载到次线程)。

对,我对此做了更多思考,再加上bbumWade Tegaskis关于阻塞运行循环的巨大贡献,并意识到缓解此类问题的方法是让运行循环循环,通过使用允许运行循环继续的
performSelector:withObject:afterDelay:
,同时安排循环在将来继续运行

例如,要使用
ui按钮返回我的原始示例,现在应将其重写为如下方法:-

- (void)spawnButton {
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame = CGRectZero;
    [self performSelector:@selector(spawnButton) withObject:nil afterDelay:0];
}
这样,该方法立即结束,当它超出范围时,
按钮
被正确释放,但在最后一行,
生成按钮
指示runloop在0秒内再次运行
生成按钮
,这反过来又指示runloop运行。。。等等,你明白了

然后,您只需在代码中的其他地方调用
[self-spawnButton]
即可开始循环

同样,也可以使用GCD解决这个问题,下面的代码基本上做了相同的事情:

- (void)spawnButton {
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    button.frame = CGRectZero;
    dispatch_async(dispatch_get_main_queue(), ^{
        [self spawnButton];
    });
}
这里唯一的区别是使用GCD将方法调用(异步)调度到主队列(主runloop)

在Instruments中分析它,我现在可以看到,尽管总体分配内存在增加,但活动内存仍然很低且是静态的,这表明runloop正在循环,旧的
UIButton
s正在被释放