Mos*_*she 13 memory-management objective-c instruments nsstring automatic-ref-counting
我正在使用ARC,并在循环中修改字符串时看到一些奇怪的行为.
在我的情况下,我使用NSXMLParser委托回调循环,但我看到使用演示项目和简单修改某些NSString对象的示例代码的相同的确切行为和症状.
您可以从GitHub下载演示项目,只需在主视图控制器的viewDidLoad方法中取消注释四个方法调用之一,即可测试不同的行为.
为简单起见,这是一个简单的循环,我已经陷入了一个空的单视图应用程序.我将此代码直接粘贴到viewDidLoad方法中.它在视图出现之前运行,因此屏幕为黑色,直到循环结束.
NSString *text;
for (NSInteger i = 0; i < 600000000; i++) {
NSString *newText = [text stringByAppendingString:@" Hello"];
if (text) {
text = newText;
}else{
text = @"";
}
}
Run Code Online (Sandbox Code Playgroud)
以下代码在循环完成之前还会保留内存:
NSString *text;
for (NSInteger i = 0; i < 600000000; i++) {
if (text) {
text = [text stringByAppendingString:@" Hello"];
}else{
text = @"";
}
}
Run Code Online (Sandbox Code Playgroud)
以下是这两个循环在Instruments中循环的方式,运行Allocations工具:

看到?逐渐稳定的内存使用,直到一大堆内存警告,然后应用程序自然死亡.
接下来,我尝试了一些不同的东西.我使用了一个实例NSMutableString,如下:
NSMutableString *text;
for (NSInteger i = 0; i < 600000000; i++) {
if (text) {
[text appendString:@" Hello"];
}else{
text = [@"" mutableCopy];
}
}
Run Code Online (Sandbox Code Playgroud)
这段代码似乎表现得更好,但仍然崩溃.这是看起来像:

接下来,我在一个较小的数据集上尝试了这个,看看是否有一个循环可以在构建期间存活足够长的时间来完成.这是NSString版本:
NSString *text;
for (NSInteger i = 0; i < 1000000; i++) {
if (text) {
text = [text stringByAppendingString:@" Hello"];
}else{
text = @"";
}
}
Run Code Online (Sandbox Code Playgroud)
它也会崩溃,结果内存图看起来与使用此代码生成的第一个相似:

使用NSMutableString相同的百万次迭代循环不仅可以成功,而且可以在更短的时间内完成.这是代码:
NSMutableString *text;
for (NSInteger i = 0; i < 1000000; i++) {
if (text) {
[text appendString:@" Hello"];
}else{
text = [@"" mutableCopy];
}
}
Run Code Online (Sandbox Code Playgroud)
并查看内存使用情况图:

开头的短峰值是循环引起的内存使用量.还记得当我注意到在处理循环期间屏幕是黑色的看似无关的事实,因为我在viewDidLoad中运行它吗?在该峰值之后,立即出现视图.所以看起来不仅NSMutableStrings在这种情况下更有效地处理内存,而且它们也更快.迷人.
现在,回到我的实际场景......我正在使用NSXMLParser解析API调用的结果.我创建了Objective-C对象来匹配我的XML响应结构.因此,例如,考虑一个类似于此的XML响应:
<person>
<firstname>John</firstname>
<lastname>Doe</lastname>
</person>
Run Code Online (Sandbox Code Playgroud)
我的对象看起来像这样:
@interface Person : NSObject
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;
@end
Run Code Online (Sandbox Code Playgroud)
现在,在我的NSXMLParser委托中,我继续循环遍历我的XML,并且我将跟踪当前元素(我不需要完整的层次结构表示,因为我的数据相当平坦,它是一个转储MSSQL数据库作为XML)然后在foundCharacters方法中,我运行这样的事情:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
if((currentProperty is EqualToString:@"firstname"]){
self.workingPerson.firstname = [self.workingPerson.firstname stringByAppendingString:string];
}
}
Run Code Online (Sandbox Code Playgroud)
这段代码与第一段代码非常相似.我正在使用XML实际循环NSXMLParser,所以如果我要记录所有的方法调用,我会看到这样的事情:
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:,这就是我们将属性附加到以前的值的原因.
总结一下,这里有两个问题.首先,在任何类型的循环中建立内存似乎会使应用程序崩溃.其次,使用NSMutableString属性不是那么优雅,我甚至不确定它是否按预期工作.
通常,有没有办法在使用ARC循环字符串时克服这种内存累积?我可以做一些特定于NSXMLParser的东西吗?
编辑:
初步测试表明,即使使用秒@autoreleasepool{...},似乎也无法解决问题.
当存在时,对象必须在内存中的某个位置,并且它们仍然存在,直到runloop结束,此时自动释放池可以消耗.
就NSXMLParser而言,这并不能解决字符串情况中的任何问题,因为循环遍布方法调用 - 需要进一步测试.
(请注意,我称之为内存峰值,因为从理论上讲,ARC会在某个时刻清理内存,直到它达到峰值为止.实际上没有任何泄漏,但它具有相同的效果.)
编辑2:
在循环内部粘贴自动释放池有一些有趣的效果.它似乎几乎可以缓解附加到NSString对象时的累积:
NSString *text;
for (NSInteger i = 0; i < 600000000; i++) {
@autoreleasepool {
if (text) {
text = [text stringByAppendingString:@" Hello"];
}else{
text = [@"" mutableCopy];
}
}
}
Run Code Online (Sandbox Code Playgroud)
分配跟踪如下所示:

我确实注意到随着时间的推移逐渐积累内存,但它大约是150千字节,而不是之前看到的350兆字节.但是,此代码的使用NSMutableString行为与没有自动释放池时的行为相同:
NSMutableString *text;
for (NSInteger i = 0; i < 600000000; i++) {
@autoreleasepool {
if (text) {
[text appendString:@" Hello"];
}else{
text = [@"" mutableCopy];
}
}
}
Run Code Online (Sandbox Code Playgroud)
分配跟踪:

看起来NSMutableString显然不受自动释放池的影响.我不确定为什么,但是最初猜测,我将这与我们之前看到的相结合,它NSMutableString可以自己处理大约一百万次迭代,而NSString不能.
那么,解决这个问题的正确方法是什么?
bbu*_*bum 11
您正在使用数吨和大量自动释放的对象来污染自动释放池.
使用自动释放池环绕循环的内部部分:
for (...) {
@autoreleasepool {
... your test code here ....
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
820 次 |
| 最近记录: |