使用ARC时NSScanner scanUpToString泄漏

时间:2012-01-11 13:34:28

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

要解析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];

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

4 个答案:

答案 0 :(得分:6)

我怀疑这个名字实际上并没有泄漏,只是当你认为它没有被释放时。在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为自动释放来避免这种情况。正如rckoeness所说,你也不需要init,因为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实例没有被泄露,因为该字典(或后来从字典中读取这些键的东西)被泄露。

答案 1 :(得分:4)

我刚刚处理完全相同的问题。我的解决方案是根据应用的迭代次数重置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 :(得分:1)

没有理由初始化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];


    }

答案 3 :(得分:0)

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

// [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];
}