Статический анализатор показывает неправильную утечку ??(XCode 4.0, iOS 4.3 и выше) - PullRequest
2 голосов
/ 03 ноября 2011

Счастливого ноября всем,

Ну, я попробовал Xcode Build и проанализировал мой проект, и он показал некоторые необычные утечки, которые я не мог смириться с моим знанием Objective C.

Поэтому я решил создать тестовый проект и спросить здесь ..

MemoryTestController.h

@interface MemoryTestController : UIViewController{
  UIImageView *tstImageView;
}
@property(nonatomic,retain) UIImageView *tstImageView;
@end

MemoryTestController.m

@implementation MemoryTestController
@synthesize tstImageView;

- (void)viewDidLoad{
  [super viewDidLoad];

  self.tstImageView  = [[UIImageView alloc] //<==This object is leaking
                           initWithFrame:<SomeFrame>]; 
  self.tstImageView.image = [UIImage imageNamed:@"SomeImage.png"];
  [self.view addSubview:tstImageView];
  [tstImageView release];
}

-(void)dealloc{
  [tstImageView release];
  [super dealloc];
}
@end

Когда я пытаюсь построитьи проанализируйте, лязг статический анализатор скажет

Потенциальная утечка объекта в строке хх

И линия преступника

self.tstImageView  = [[UIImageView alloc]initWithFrame:<SomeFrame>];

Я думаю, что яосвобождаю один раз за каждый раз, когда я выделяю / сохраняю.Я что-то упустил, или в Статическом анализаторе есть какие-то ошибки?

РЕДАКТИРОВАТЬ: Есть ли там утечка?

Хорошо, я запускаю вышеупомянутый проект, используя инструмент Leak в инструменте .. Он не показываллюбая утечка, хотя я пытался много раз .. Кому я должен верить?Статический анализатор или течеискатель?

Ответы [ 2 ]

5 голосов
/ 03 ноября 2011

ваша проблема в том, как вы ее выпустите:

- (void)viewDidLoad{
  [super viewDidLoad];

  self.tstImageView  = [[UIImageView alloc] //<==This object is leaking
                           initWithFrame:<SomeFrame>]; 
  self.tstImageView.image = [UIImage imageNamed:@"SomeImage.png"];
  [self.view addSubview:tstImageView];
  [tstImageView release]; // << here
}

Вы должны сделать это следующим образом:

- (void)viewDidLoad{
  [super viewDidLoad];

  UIImageView * imageView  = [[UIImageView alloc] initWithFrame:<SomeFrame>]; 
  imageView.image = [UIImage imageNamed:@"SomeImage.png"];
  self.tstImageView  = imageView;
  [imageView release];
  [self.view addSubview:self.tstImageView];
}

Проверка правильна, потому что она не может предполагать, что переменная идентична той, которую вы установили. Поэтому форма, которую вы используете в OP, может привести к дисбалансу количества ссылок, поскольку значение ивара может не совпадать с тем, которое вы ему присвоили к моменту публикации сообщения на иваре.

Эти случаи маловероятны для UIImageView и весьма маловероятны в контексте вашей программы, но эти примеры должны дать вам представление о том, почему средство проверки предполагает, что ассоциации объект-> ивар не следует доверять:

Между созданием представления изображения и сообщением о его выпуске через ivar у вас есть:

  self.tstImageView  = [[UIImageView alloc] initWithFrame:<SomeFrame>]; 
  self.tstImageView.image = [UIImage imageNamed:@"SomeImage.png"];
  [self.view addSubview:tstImageView];

1) присвоение вида изображения через сеттер 2) доступ к просмотру изображения через геттер 3) прямой доступ к ivar, при добавлении в self.view

  • Сеттер мог скопировать или использовать кэшированное значение. UIImageView является плохим примером, но контролер не знает, как обычно передаются типы - даже если бы он это сделал, он (иногда) делал небезопасные предположения.

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

- (void)setName:(NSString *)inName {
  NSString * prev = name;
  if (inName == prev) return;
  if (0 == [inName count]) name = @"";
  else name = [inName copy];
  [prev release];
}
  • значение, удерживаемое иваром, может тем временем измениться. вряд ли проблема в этом случае, но допустим, что добавление представления изображения в качестве подпредставления может в конечном итоге перезвонить и изменить self в процессе / эффекте добавления подпредставления, а также заменить или удалить представление изображения, которое вы передали. В этом случае передаваемое вами представление переменной будет иметь утечку, а представление, которым оно заменено, будет иметь отрицательный дисбаланс.

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

РЕДАКТИРОВАТЬ: есть ли утечка там?

Хорошо, я запускаю вышеупомянутый проект, используя Инструмент утечки в инструменте. Утечки не было, хотя я пытался это много раз .. Кому я должен верить? Статический анализатор или утечка инструмент?

Статический анализатор сообщает, что существует потенциальная утечка , потому что он не может гарантировать, что задание / распределение, которое он следует, правильно сохраняется / освобождается. Вы можете гарантировать правильность подсчета ссылок и порадовать статический анализатор, изменив свою программу так, как я написал в своем примере.

То, как вы написали это, сделало невозможным для анализатора следовать указаниям.

Если у вас нет утечек и зомби, значит, утечки нет. Но решение легко исправить - и программы могут меняться в процессе разработки. Пользоваться формой, которую я разместил, намного проще, так что для инструментария и для вас легче проверить правильность программы. Статический анализатор не всегда корректен, но вы должны настроить свои программы так, чтобы он понравился, потому что статический анализ очень полезен. Размещенную мною программу также легче понять и подтвердить, что она правильная.

1 голос
/ 03 ноября 2011

когда вы объявляете свойство с помощью retain следующим образом

@property(nonatomic,retain) UIImageView *tstImageView;

добавляется установщик, который будет включать retainCount при назначении этому свойству.Когда вы делаете, как показано ниже, у созданного вами объекта уже есть retainCount == 1

self.tstImageView  = [[UIImageView alloc] 
                           initWithFrame:<SomeFrame>];

, поэтому у объекта tstImageView есть 2 в retainCount.

вместо

UIImageView* view = [[UIImageView alloc] initWithFrame:<SomeFrame>];
self.tstImageView  = view;
[view release];

тогда, хотя это не имеет отношения к вашей утечке, когда вы освобождаете его, напишите так вместо этого

self.tstImageView = nil;

, поскольку установщик тогда правильно установит retainCount

...