Утечка NSScanner scanUpToString при использовании ARC - PullRequest
2 голосов
/ 11 января 2012

Для анализа части строки запроса 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 ]

6 голосов
/ 16 января 2012

Я подозреваю, что имя на самом деле не утечка, оно просто не выпускается, когда вы думаете, что это так. В 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 голосов
/ 26 ноября 2012

Я только что имел дело с точно такой же проблемой.Моим решением было сбросить объект 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];
 }
1 голос
/ 11 января 2012

Нет причины инициализировать переменную 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];


    }
0 голосов
/ 20 января 2012

как насчет использования инициализатора с автоматическим освобождением?

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