【问题标题】:NSScanner scanUpToString leaking while using ARC使用 ARC 时 NSScanner scanUpToString 泄漏
【发布时间】:2012-02-07 20:40:14
【问题描述】:

要解析 URL 的部分查询字符串,我使用此方法:

NSScanner *scanner = [[NSScanner alloc] initWithString:query];
        [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];

        NSString *parameterString = [NSString new];
        while ([scanner scanUpToString:ampersand intoString:&parameterString]) 
        {
            NSScanner *parameterScanner = [[NSScanner alloc] initWithString:parameterString];

            NSString *name = [NSString new];
            [parameterScanner scanUpToString:isEqual intoString:&name];

            NSString *value = [parameterString substringFromIndex:([name length] + 1)];
            [parameters setObject:value forKey:name];


        }

在这个项目中,我使用的是 ARC,但该方法仍然在这一行泄漏:

[parameterScanner scanUpToString:isEqual intoString:&name];

究竟是什么泄漏,我该如何解决?

【问题讨论】:

  • 您是否通过 Instruments 的 Leaks 工具看到了这个泄漏?如果是这样,请转到下部面板并将显示选项从泄漏更改为周期和根以显示新的保留周期检测工具。这可能表明保留周期是否对 ARC 下的这种泄漏负责。此外,您可以使用 Allocations 工具中的 heap shot 功能来确定在上述代码的每次传递中究竟哪些对象正在泄漏。
  • name 变量泄漏,每次都通过上面的代码。有什么办法可以解决吗?

标签: iphone objective-c automatic-ref-counting nsscanner


【解决方案1】:

我刚刚处理了完全相同的问题。我的解决方案是根据应用到它的迭代次数来重置 NSScanner 对象。换句话说,每次运行扫描仪完整性测试时,我都会增加一个值,然后基于该值重新创建扫描仪对象并将当前位置从前一个扫描仪应用到新扫描仪。每次创建新版本的扫描仪时,我都会放置一个 @autoreleasepool 标记。

我以这种方式接近它的原因是,因为 NSScanner 只是一个内存猪,并且在循环完成之前不会释放。我只通过活动查看器而不是任何工具验证了这一点。 (我在 Mac OS X 应用程序设置中测试过)

享受吧!

NSUInteger currentLocation = 0;
while (currentLocation < [dehyphenatedText length])
{
@autoreleasepool
{
    NSUInteger iterations = 0;
    NSScanner * scanner = [NSScanner scannerWithString:dehyphenatedText];
    [scanner setCharactersToBeSkipped: nil];
    [scanner setScanLocation: currentLocation];
    while (([scanner scanLocation] < [dehyphenatedText length]) && (iterations < 15000))
    {
    NSString * found=nil;
    [scanner scanCharactersFromSet:inverted intoString:&found];
    if ((found != nil) && ([found length] > 0))
    {
               // Some code to process the results
            }
        found = nil;
        if ([scanner scanLocation] < [dehyphenatedText length])
        {
           [scanner scanCharactersFromSet: whiteSpaceAndMore intoString:nil];
        }

        iterations ++;
    }
    }
currentLocation = [scanner scanLocation];
 }

【讨论】:

    【解决方案2】:

    使用自动释放的初始化器怎么样?

    // [NSScanner scannerWithString:]
    // and
    // [NSString string]
    
    NSScanner *scanner = [NSScanner scannerWithString:query];
    [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
    
    NSString *parameterString = [NSString string];
    while ([scanner scanUpToString:ampersand intoString:&parameterString]) 
    {
        NSScanner *parameterScanner = [NSScanner scannerWithString:parameterString];
    
        NSString *name = [NSString string];
        [parameterScanner scanUpToString:isEqual intoString:&name];
    
        NSString *value = [parameterString substringFromIndex:([name length] + 1)];
        [parameters setObject:value forKey:name];
    }
    

    【讨论】:

    • 不,就像上一个答案中所说的那样;自动释放不是以一种或另一种方式正确完成的。
    【解决方案3】:

    我怀疑这个名字实际上并没有泄露,只是当你认为它是的时候它并没有被释放。在 ARC 下,我相信 scanUpToString:intoString: 的定义类似于使用 NSError 的方法。换句话说,它需要NSString * __autoreleasing *。因此,传递给它的任何值实际上都是自动释放的,并且在当前自动释放池耗尽之前不会被释放。假设你周围没有其他人,那将是运行循环再次运行的时候。如果该内存使用对您来说是个问题,则可以在循环周围放置一个显式的自动释放池,以便对象立即消失:

    NSScanner *scanner = [[NSScanner alloc] initWithString:query];
    [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
    
    @autoreleasepool
    {
        NSString *parameterString = [NSString new];
        while ([scanner scanUpToString:ampersand intoString:&parameterString]) 
        {
            NSScanner *parameterScanner = [[NSScanner alloc] initWithString:parameterString];
    
            NSString *name = [NSString new];
            [parameterScanner scanUpToString:isEqual intoString:&name];
    
            NSString *value = [parameterString substringFromIndex:([name length] + 1)];
            [parameters setObject:value forKey:name];
    
    
        }
    }
    

    这可能是不必要的,无论如何运行循环都会清除对象。

    也就是说,仍然存在一个小问题,这意味着编译器正在为您创建一个额外的临时变量。您的name 变量隐含为__strong,因此编译器插入一个临时变量__autoreleasing 并为您复制这些值。您可以通过将NSString 明确声明为自动释放来避免这种情况。你也不需要init 它,正如 rckoeness 所说,因为scanUpToString:intoString: 正在为你做这件事(这就是为什么它必须首先是__autoreleasing)。 (请参阅http://developer.apple.com/library/mac/ipad/#releasenotes/ObjectiveC/RN-TransitioningToARC/_index.html 了解更多信息)。

    所以,总的来说,我认为您实际上希望您的代码看起来像这样:

    NSScanner *scanner = [[NSScanner alloc] initWithString:query];
    [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
    
    NSString __autoreleasing *parameterString = nil;
    while ([scanner scanUpToString:ampersand intoString:&parameterString]) 
    {
        NSScanner *parameterScanner = [[NSScanner alloc] initWithString:parameterString];
    
        NSString __autoreleasing *name = nil;
        [parameterScanner scanUpToString:isEqual intoString:&name];
    
        NSString *value = [parameterString substringFromIndex:([name length] + 1)];
        [parameters setObject:value forKey:name];
    }
    

    希望有帮助!


    我有另一个想法,也许name 只是一个红鲱鱼。泄漏将显示分配发生的位置,但name 在添加到parameters 时,在此循环之外继续存在。我假设它是NSMutableDictionary 或类似的,基于选择器。如果我是你,我会确认 name 实例没有被泄露,因为该字典(或后来从字典中读取这些键的东西)正在泄露。

    【讨论】:

    • 但是如果名称没有泄漏,为什么仪器显示它是泄漏的呢?上述解决方案未能解决(按指示的仪器)泄漏。
    • 我添加了另一个可能解释它的注释。不过我也留下了原文,因为优化仍然有效。
    【解决方案4】:

    没有理由初始化name变量

        NSScanner *scanner = [[NSScanner alloc] initWithString:query];
        [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
        NSString *parameterString = [NSString new];
        while ([scanner scanUpToString:ampersand intoString:&parameterString]) 
        {
            NSScanner *parameterScanner = [[NSScanner alloc] initWithString:parameterString];
    
            NSString *name = nil;
            [parameterScanner scanUpToString:isEqual intoString:&name];
    
            NSString *value = [parameterString substringFromIndex:([name length] + 1)];
            [parameters setObject:value forKey:name];
    
    
        }
    

    【讨论】:

    • 它仍然在泄漏,当我没有初始化名称时。
    猜你喜欢
    • 2013-01-20
    • 1970-01-01
    • 1970-01-01
    • 2012-12-09
    • 2012-04-03
    • 1970-01-01
    • 2012-02-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多