【问题标题】:How to fix long NSString crash?如何修复长时间的 NSString 崩溃?
【发布时间】:2013-03-18 10:35:06
【问题描述】:

在我正在开发的应用程序中,我在 for 循环中从文本文件中读取数值,然后进行一些计算并将结果附加到结果字符串中。

该文件中有 22050 个值。我注意到,超过一定数量的附加循环/值(~5300)它往往会崩溃。

我想也许我有内存泄漏,所以我去掉了附加的字符串,一切正常。我试图摆脱所有东西,但附加的字符串和应用程序崩溃了。我在所有异常上都有一个断点,我没有得到任何异常。

我想确定一下,所以我开始了一个新项目。我只放了一个 UIButton,当它被推送时会调用这段代码:

- (IBAction)TestPressed:(id)sender
{
    NSString *testString = @"";

    for (int i = 0; i < 22050; i++)
    {
        testString = [testString stringByAppendingString:@"12.34567890\n"];
    }

    NSLog(@"%@", testString);
}

我在 NSLog 行上有一个断点。该应用程序之前崩溃。

NSString 长度有限制吗?会不会占用太多内存?

【问题讨论】:

  • 如果你使用一个可变的字符串,会有什么不同吗? NSMutableString *testString = [NSMutableString string];[testString appendString:@"12.34567890|n"];
  • 您使用的是 iOS 还是 MacOS?如果您使用 iOS 并使用 stringByAppendingString 而您没有使用 ARC,那么您就像筛子一样泄漏内存。如果您使用的是 ARC,那么至少它应该为新字符串释放旧字符串。 @Fogmeister 的建议也应该改进,不管 ARC。
  • @gaige 在这种情况下,Mac/iOS 和 ARC/MRC 之间没有区别。临时字符串只是进入自动释放池。
  • @NikolaiRuhe 我不相信这是真的。对于 ARC,分配本地拥有的变量通常会在分配后导致释放,因为 ARC 知道它是唯一的所有者。我刚刚编译了这个确切的代码并查看了反汇编代码,它调用的是-release,而不是-autorelease
  • @gaige 问题是stringByAppendingString: 返回一个自动释放的对象。所以 ARC 不能神奇地从池中移除对象。如果 stringByAppendingString: 也使用 ARC 编译(我不知道),这将起作用的唯一方法。然后stringByAppendingString: 中的代码可以决定省略自动释放,因为它知道接收代码正确处理内存管理。但这没有记录在案,任何推测对于 MacOS 和 iOS 应该是相同的。

标签: objective-c crash nsstring


【解决方案1】:

问题是您在每次迭代中都创建了一个新字符串。有两个选项可以解决这个问题:要么使用可变字符串来创建结果:

NSMutableString *testString = [NSMutableString string];

for (int i = 0; i < 22050; i++)
{
    [testString appendString:@"12.34567890\n"];
}

NSLog(@"%@", testString);

...或者使用自动释放池来移除循环中的实例:

NSString *testString = @"";

for (int i = 0; i < 22050; i++)
{
    @autoreleasepool {
        testString = [testString stringByAppendingString:@"12.34567890\n"];
    }
}

NSLog(@"%@", testString);

请注意,我包含第二个版本只是为了说明问题发生的原因以及如何解决它。它仍然是低效的,因为它创建了 22049 个平均长度为 120,000 个字符的临时字符串。

【讨论】:

  • @AnoopVaidya 我更喜欢第一个。它可能要快得多。
  • 是的,我一直使用那个,从未尝试过第二个,尽管知道这种方式也是可能的。
  • 不幸的是,这段代码实际上会崩溃,因为您创建的字符串会立即被自动释放池弹出。你可能不得不写更像这样的东西:@autoreleasepool { NSString *oldTestString = testString; testString = [[testString stringByAppendingString:@“12.34567890\n”] 保留]; [oldTestString 发布]; }
  • @eyebrowsoffire 只有在没有 ARC 的情况下编译才适用。使用现代的 @autoreleasepool 我只是把 ARC 当作给定的。
  • 啊,原来如此。 @autoreleasepool 指令可以与手动保留释放一起使用。我使用很多 MRR 代码,从技术上讲,sn-p 代码可能是 ARC 或 MRR,所以我的大脑只是自动假设为 MRR。
【解决方案2】:

使用NSMutableString 附加字符串,否则会分配太多内存。

【讨论】:

    【解决方案3】:

    +[NSString stringByAppendingString:] 每次调用它时都会创建一个新的、自动释放的对象。在自动释放池弹出之前,自动释放的对象不会被释放,这通常意味着在事件循环结束时。在这种情况下,最好使用 NSMutableString 来代替其他答案的建议。但是,如果您确实遇到了需要创建许多自动释放对象而不是使用可变对象的问题,您可能需要在创建对象的代码周围包裹一个 @autoreleasepool {} 块,这样您就没有大量内存膨胀,但您必须小心保留要保留在 @autoreleasepool 块范围之外的对象。

    一般来说,我尽量避免使用自动释放的对象,原因如下:

    1) 对象实际被释放的时间可能是不确定的,除非您的方法本身中确实有一个自动释放池。否则,无法知道自动释放池的范围以及调用代码何时决定弹出该池。

    2) 自动发布本质上比手动发布更占用资源。需要将对象添加到池中,然后迭代并稍后释放。

    3) 自动释放在多线程环境中变得特别棘手,因为您经常会在释放对象的线程之间发生竞争。在这些情况下,很容易创建一个间歇性崩溃的场景,该场景难以重现或找到其根本原因。

    4) 在处理自动释放时,崩溃通常更难分析。您经常会看到堆栈跟踪只是一个自动释放池弹出一个对象,如果没有重现案例,几乎不可能确定它是什么对象,释放它的代码路径等。

    5) 有时,像重构这样简单的事情会以您通常意想不到的方式影响自动释放的对象。从简单迭代更改为基于块的迭代引入了一个自动释放池,它可能会在您有机会保留对象之前释放它。

    通常最适合使用自动释放对象的是动态创建并由非构造方法返回的对象。这样调用代码就有机会保留对象,但被调用的方法不必担心自己指定所有者。

    这有点啰嗦,也许超出了最初问题的范围,但我认为了解内存管理中良好决策背后的“为什么”很重要。

    【讨论】:

      猜你喜欢
      • 2019-10-07
      • 2018-01-18
      • 1970-01-01
      • 1970-01-01
      • 2022-01-10
      • 1970-01-01
      • 2021-12-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多