循环和便利方法会导致ARC出现内存峰值吗? [英] Do loops and convenience methods cause memory peaks with ARC?

查看:52
本文介绍了循环和便利方法会导致ARC出现内存峰值吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用ARC,并且在循环中修改字符串时看到一些奇怪的行为.

I'm working with ARC and seeing some strange behavior when modifying strings in a loop.

在我的情况下,我使用NSXMLParser委托回调进行循环,但是使用演示项目和示例代码(仅修改了某些NSString对象),我看到了相同的确切行为和症状.

In my situation, I'm looping using NSXMLParser delegate callbacks, but I see the same exact behavior and symptoms using a demo project and sample code which simply modifies some NSString objects.

您可以从GitHub下载演示项目,只需取消注释其中之一主视图控制器的viewDidLoad方法中有四个方法调用,以测试不同的行为.

You can download the demo project from GitHub, just uncomment one of the four method calls in the main view controller's viewDidLoad method to test the different behaviors.

为简单起见,这是一个简单的循环,我将其放入一个空的单视图应用程序中.我将此代码直接粘贴到viewDidLoad方法中.它在视图出现之前运行,因此屏幕为黑色,直到循环结束.

For simplicity's sake, here's a simple loop which I've stuck into an empty single-view application. I pasted this code directly into the viewDidLoad method. It runs before the view appears, so the screen is black until the loop finishes.

NSString *text;

for (NSInteger i = 0; i < 600000000; i++) {

    NSString *newText = [text stringByAppendingString:@" Hello"];

    if (text) {
        text = newText;
    }else{
        text = @"";
    }
}

以下代码也一直消耗内存,直到循环完成:

The following code also keeps eating memory until the loop completes:

NSString *text;

for (NSInteger i = 0; i < 600000000; i++) {

    if (text) {
        text = [text stringByAppendingString:@" Hello"];
    }else{
        text = @"";
    }
}

这是运行分配"工具时这两个循环在仪器中循环的样子:

Here's what these two loops loop like in Instruments, with the Allocations tool running:

看到了吗?逐渐稳定地使用内存,直到出现一堆内存警告,然后该应用自然终止.

See? Gradual and steady memory usage, until a whole bunch of memory warnings and then the app dies, naturally.

接下来,我尝试了一些不同的尝试.我使用了NSMutableString的实例,如下所示:

Next, I've tried something a little different. I used an instance of NSMutableString, like so:

NSMutableString *text;

for (NSInteger i = 0; i < 600000000; i++) {

    if (text) {
        [text appendString:@" Hello"];
    }else{
        text = [@"" mutableCopy];
    }
}

这段代码似乎执行得更好,但是仍然崩溃.看起来像这样:

This code seems to perform a lot better, but still crashes. Here's what that looks like:

接下来,我在一个较小的数据集上进行了尝试,以查看任一循环能否在构建过程中存活足够长的时间来完成.这是NSString版本:

Next, I tried this on a smaller dataset, to see if either loop can survive the build up long enough to finish. Here's the NSString version:

NSString *text;

for (NSInteger i = 0; i < 1000000; i++) {

    if (text) {
        text = [text stringByAppendingString:@" Hello"];
    }else{
        text = @"";
    }
}

它也会崩溃,并且生成的内存图看起来类似于使用此代码生成的第一个内存图:

It crashes as well, and the resultant memory graph looks similar to the first one generated using this code:

使用NSMutableString,相同的百万迭代循环不仅成功,而且在更少的时间内完成了.这是代码:

Using NSMutableString, the same million-iteration loop not only succeeds, but it does in a lot less time. Here's the code:

NSMutableString *text;

for (NSInteger i = 0; i < 1000000; i++) {

    if (text) {
        [text appendString:@" Hello"];
    }else{
        text = [@"" mutableCopy];
    }
}

看看内存使用情况图:

开始时的短暂尖峰是循环导致的内存使用量.还记得当我注意到在循环处理过程中屏幕是黑色的似乎无关紧要的事实时,因为我在viewDidLoad中运行它吗?在该峰值之后,立即显示该视图.因此看来,在这种情况下,NSMutableStrings不仅可以更有效地处理内存,而且速度也要快得多.迷人.

The short spike in the beginning is the memory usage incurred by the loop. Remember when I noted that seemingly irrelevant fact that the screen is black during the processing of the loop, because I run it in viewDidLoad? Immediately after that spike, the view appears. So it appears that not only do NSMutableStrings handle memory more efficiently in this scenario, but they're also much faster. Fascinating.

现在,回到我的实际情况...我正在使用NSXMLParser来解析API调用的结果.我已经创建了Objective-C对象来匹配我的XML响应结构.因此,例如考虑一个看起来像这样的XML响应:

Now, back to my actual scenario... I'm using NSXMLParser to parse out the results of an API call. I've created Objective-C objects to match my XML response structure. So, consider for example, an XML response looking something like this:

<person>
<firstname>John</firstname>
<lastname>Doe</lastname>
</person>

我的对象看起来像这样:

My object would look like this:

@interface Person : NSObject

@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;

@end

现在,在我的NSXMLParser委托中,我将继续遍历我的XML,并跟踪当前元素(由于数据相当平坦,我不需要完整的层次结构表示形式,将MSSQL数据库转储为XML),然后在foundCharacters方法中,运行以下命令:

Now, in my NSXMLParser delegate, I'd go ahead and loop through my XML, and I'd keep track of the current element (I don't need a full hierarchy representation since my data is rather flat, it's a dump of a MSSQL database as XML) and then in the foundCharacters method, I'd run something like this:

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
  if((currentProperty is EqualToString:@"firstname"]){
    self.workingPerson.firstname = [self.workingPerson.firstname stringByAppendingString:string]; 
  }
}

此代码非常类似于第一个代码.我正在有效地使用NSXMLParser遍历XML,因此,如果我要记录所有方法调用,则会看到类似以下内容的内容:

This code is much like the first code. I'm effectively looping through the XML using NSXMLParser, so if I were to log all of my method calls, I'd see something like this:

parserDidStartDocument: 解析器:didStartElement:名称空间URI:qualifiedName:属性: 解析器:找到字符: 解析器:didStartElement:名称空间URI:qualifiedName: 解析器:didStartElement:名称空间URI:qualifiedName:属性: 解析器:foundCharacters: 解析器:didStartElement:名称空间URI:qualifiedName: 解析器:didStartElement:名称空间URI:qualifiedName:属性: 解析器:找到字符: 解析器:didStartElement:名称空间URI:qualifiedName: parserDidEndDocument:

parserDidStartDocument: parser:didStartElement:namespaceURI:qualifiedName:attributes: parser:foundCharacters: parser:didStartElement:namespaceURI:qualifiedName: parser:didStartElement:namespaceURI:qualifiedName:attributes: parser:foundCharacters: parser:didStartElement:namespaceURI:qualifiedName: parser:didStartElement:namespaceURI:qualifiedName:attributes: parser:foundCharacters: parser:didStartElement:namespaceURI:qualifiedName: parserDidEndDocument:

看到图案了吗?这是一个循环.请注意,也可能有多个连续的parser:foundCharacters:调用,这就是为什么我们将属性附加到先前的值.

See the pattern? It's a loop. Note that it's possible to have multiple consecutive calls to parser:foundCharacters: as well, which is why we append the property to previous values.

总结起来,这里有两个问题.首先,以任何形式的循环建立的内存似乎都会使应用程序崩溃.其次,对属性使用NSMutableString并不是那么优雅,我什至不确定它是否按预期工作.

To wrap it up, there are two problems here. First of all, memory build up in any sort of loop seems to crash the app. Second, using NSMutableString with properties is not so elegant, and I'm not even sure that it's working as intended.

通常,在使用ARC遍历字符串时,是否有一种方法可以克服这种内存堆积问题?我可以做一些NSXMLParser特有的事情吗?

In general, is there a way to overcome this memory buildup while looping through strings using ARC? Is there something specific to NSXMLParser that I can do?

初步测试表明,即使再使用@autoreleasepool{...}似乎也无法解决问题.

Initial tests indicate that even using a second @autoreleasepool{...} doesn't seem to fix the issue.

对象必须在存在时在内存中的某个位置移动,并且它们仍然存在,直到运行循环结束,此时自动释放池可能会耗尽.

The objects have to go somewhere in memory while thwy exist, and they're still there until the end of the runloop, when the autorelease pools can drain.

就NSXMLParser而言,这不能解决字符串情况下的任何问题,因为循环遍及方法调用,因此需要进一步测试.

This doesn't fix anything in the strings situation as far as NSXMLParser goes, it might, because the loop is spread across method calls - need to test further.

(请注意,我称此为内存峰值,因为从理论上讲,ARC会在某个时刻清理内存,直到内存达到峰值后才清除内存.实际上没有泄漏,但效果相同.)

(Note that I call this a memory peak, because in theory, ARC will clean up memory at some point, just not until after it peaks out. Nothing is actually leaking, but it's having the same effect.)

在循环内插入自动释放池会产生一些有趣的效果.追加到NSString对象时,似乎几乎可以减轻积聚:

Sticking the autorelease pool inside of the loop has some interesting effects. It seems to almost mitigate the buildup when appending to an NSString object:

NSString *text;

for (NSInteger i = 0; i < 600000000; i++) {

        @autoreleasepool {
            if (text) {
                text = [text stringByAppendingString:@" Hello"];
            }else{
                text = [@"" mutableCopy];
            }
        }
    }

分配"跟踪如下所示:

我确实注意到随着时间的推移内存逐渐增加,但这大约是150 KB,而不是之前看到的350 MB.但是,使用NSMutableString的此代码的行为与不使用自动释放池的情况相同:

I do notice a gradual buildup of memory over time, but it's to the tune of about a 150 kilobytes, not the 350 megabytes seen earlier. However, this code, using NSMutableString behaves the same as it did without the autorelease pool:

NSMutableString *text;

for (NSInteger i = 0; i < 600000000; i++) {

        @autoreleasepool {
            if (text) {
                [text appendString:@" Hello"];
            }else{
                text = [@"" mutableCopy];
            }
        }
    }

以及分配跟踪:

看来,NSMutableString显然不受自动释放池的影响.我不知道为什么,但是首先,我想将它与我们之前看到的结合起来,即NSMutableString可以自己处理大约一百万次迭代,而NSString不能.

It would appear that NSMutableString is apparently immune to the autorelease pool. I'm not sure why, but at first guess, I'd tie this in with what we saw earlier, that NSMutableString can handle about a million iterations on its own, whereas NSString cannot.

那么,解决这个问题的正确方法是什么?

So, what's the correct way of resolving this?

推荐答案

您正在用大量自动释放对象污染自动释放池.

You are polluting the autorelease pool with tons and tons of autoreleased objects.

使用自动释放池围绕循环的内部部分:

Surround the internal part of the loop with an autorelease pool:

for (...) {
    @autoreleasepool {
        ... your test code here ....
    }
}

这篇关于循环和便利方法会导致ARC出现内存峰值吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆