2016-11-04 4 views
0

Наша команда компании работает над существующим приложением. Этот проект не является ARC (автоматический подсчет ссылок). Возникает сомнение относительно выпуска объекта по следующему коду.Проект без ARC: NSMutableArray, утечка памяти NSString

Код 1: Почему при выполнении этого кода нет сбоя?

NSMutableArray *arraytest=[[NSMutableArray alloc]init]; 
for(int i=0;i<100;i++) 
{ 
    NSString *str=[NSString stringWithFormat:@"string:%d",i]; 
    [arraytest addObject:str]; 
} 
NSLog(@"arraytest before:%@",arraytest); 
[arraytest release]; 
NSLog(@"arraytest after:%@",arraytest); 

Аналогичного код: с изменяемой копией

Код 2: После того, как изменяется следующий код врежется в последней строке.

NSMutableArray *arraytest=[[NSMutableArray alloc]init]; 
for(int i=0;i<100;i++) 
{ 
    NSString *str=[NSString stringWithFormat:@"string:%d",i]; 
    [arraytest addObject:str]; 
} 
NSLog(@"arraytest before:%@",arraytest); 
NSMutableArray *copyarray=[arraytest mutableCopy]; 
[arraytest release]; 
NSLog(@"copyarray:%@",copyarray); 
NSLog(@"arraytest after:%@",arraytest); 

Почему происходит утечка памяти в этой строке?

enter image description here

И почему есть утечка памяти в этой строке?

enter image description here

Что такое правильный способ, чтобы выполнить приведенный выше код без утечек памяти? Ребята из нашей компании говорят, что autorelease не должны использоваться выше кода.

ответ

0

Рассмотрим первый пример:

NSMutableArray *arraytest = [[NSMutableArray alloc] init]; 

// `arraytest` populated ... 

NSLog(@"arraytest before:%@", arraytest); 
[arraytest release]; 
NSLog(@"arraytest after:%@", arraytest); // DANGER: referencing dangling pointer!!! 

Это последнее NSLog утверждение чрезвычайно опасно, потому что после того, как называется [arraytest release] (уменьшение удержания сосчитать массива от +1 к 0), объект, на который указывает arraytest будет уже deallocated и arraytest теперь dangling pointer в эту освобожденную память. Вы не должны ссылаться на указатель после того, как он был освобожден. Иногда может показаться, что вы можете его использовать, но это небезопасно, и ваше приложение теперь может неожиданно сбой. (Если вы использовали зомби~d, хотя, это было бы безопасно предупредили вас о вашей попытке неправильно использовать этот оборванный указатель.)


Рассмотрим ваш второй пример:

NSMutableArray *arraytest = [[NSMutableArray alloc] init]; 

// `arraytest` populated ... 

NSLog(@"arraytest before:%@",arraytest); 
NSMutableArray *copyarray = [arraytest mutableCopy]; 
[arraytest release]; 
NSLog(@"copyarray:%@", copyarray); 
NSLog(@"arraytest after:%@", arraytest); // DANGER: referencing dangling pointer!!! 

В этом примере, вы по-прежнему есть очень опасный NSLog из arraytest в конце, после того, как вы его выпустили, и ваше использование этого болтающегося указателя может легко сработать. Поэтому вы хотите избавиться от этого.

Но вы теперь ввели утечку. Пока вы выпустили объект, на который указывал arraytest, вы не выпустили объект, на который указывает copyarray, результат mutableCopy. Таким образом, этот новый экземпляр copyarray будет протекать. И таким образом, все те строки, которые были первоначально выделены, когда вы создали arraytest, теперь будут ссылаться на утечку copyarray, и они тоже будут течь.

Если вы добавили [copyarray release] в конец этой подпрограммы, были бы разрешены как утечка массива, так и протекающие строки.


Теперь рассмотрим ваш третий пример, показанный только в окончательном Инструменты снимке экрана:

NSMutableArray *arraytest = [[NSMutableArray alloc] init]; 
for (int i=0; i<100; i++) { 
    NSString *str = [NSString stringWithFormat:@"string:%d", i]; 
    [arraytest addObject:str]; 
    [str release];      // DANGER: released `str` whose ownership was never transferred to you!!! 
} 
NSLog(@"arraytest before:%@",arraytest); 
NSMutableArray *copyarray=[arraytest mutableCopy]; 
[arraytest release]; 
NSLog(@"copyarray:%@",copyarray); 
NSLog(@"arraytest after:%@",arraytest); // DANGER: referencing dangling pointer!!! 

В этом последнем примере мы усугубляют проблему, по overreleasing строку, которую вы только что добавили массив. Таким образом, вы создали объект автореферата (фактически счетчик удержания будет равен нулю, когда пул авторезистов слит), добавили его в массив (увеличив его эффективное количество удержания до +1) и выпустив str (уменьшив его количество возврата до +0 после дренирование бассейна).

Приложение теперь находится в неустойчивой ситуации, потому что массив теперь ссылается на объекты, которые могут быть выпущены, когда пул авторесурсов слит, и заканчивается массивом оборванных указателей. Хуже того, если бы этот массив когда-либо был правильно выпущен, все эти строки были бы слишком завышены.

И, конечно, у вас все еще есть утечка массива, как описано во втором примере выше. Но если вы правильно выпустили этот copyarray, все эти строки были бы слишком завышены.


Вероятно, само собой разумеется, в этой точке, но способ устранить эту утечку, чтобы просто выпустить copyarray:

NSMutableArray *arraytest = [[NSMutableArray alloc] init]; 
for (int i=0; i<100; i++) { 
    NSString *str = [NSString stringWithFormat:@"string:%d", i]; 
    [arraytest addObject:str]; 
} 
NSLog(@"arraytest before:%@", arraytest); 
NSMutableArray *copyarray = [arraytest mutableCopy]; 
[arraytest release]; 
NSLog(@"copyarray:%@", copyarray); 
[copyarray release]; 

Это следует за Basic Memory Management Rules, а именно, что вы несете ответственность за вызов release на те объекты, которыми вы владеете, благодаря их получению от метода, начинающегося с alloc, new, copy или mutableCopy).


Несколько заключительных замечаний:

  1. Если вы собираетесь использовать ручной подсчет ссылок, я предлагаю вам сделать частое использование статического анализатора Xcode (в сдвиг + команду + B или «Проанализировать» в меню «Продукт» Xcode). Удивительно хорошо определить проблемы с памятью памяти вручную. Инструменты полезны, но, как показано в нашем третьем примере выше, вас легко можно привлечь к неправильным выводам (например, «gee, мне нужно выпустить все эти строки»). Статический анализатор указал бы на некоторые из этих проблем для вас.

    Нижняя линия, всегда убедитесь, что у вас есть чистый счет здоровья от статического анализатора, прежде чем продолжить. Нет смысла пытаться перепроверить, какие проблемы могут проявиться в Инструментах, когда анализатор мог бы сказать вам, в чем именно проблема.

  2. Я бы посоветовал не делать каких-либо выводов из-за того, что конкретный висячий указатель не разбил ваше приложение, а другой сделал. Это просто не предсказуемо. Если вы включите зомби (только временно, для целей разработки/тестирования, а не для производственных приложений), это привлечет ваше внимание к любым попыткам ссылаться на ранее освобожденный объект.

  3. Я замечаю, что вы используете NSString в своих тестах.Вы должны знать, что NSString имеет оптимизацию внутренней памяти, которая может привести к нестандартному поведению. Я бы опасался использовать NSString в подобных экспериментах.

    Не поймите меня неправильно: если вы будете следовать всем Basic Memory Management Rules, NSString будет вести себя правильно. Но если вы пытаетесь выяснить, какие ошибки/сбои возникают, когда они сознательно не соблюдают эти правила управления памятью, имейте в виду, что NSString может вводить в заблуждение.

  4. Излишне говорить, что использование ARC значительно упростит вашу жизнь. См. Transitioning to ARC Release Notes для получения дополнительной информации.

Смежные вопросы