NSMutableArray, передающий утечку памяти параметра - PullRequest
2 голосов
/ 14 сентября 2009

У меня возникла трудная проблема, буду признателен за любую, я имею в виду ЛЮБУЮ помощь =)

Я опытный разработчик, я новичок в Objective-C / iPhone / Cocoa.

Я хочу создать контроллер класса, которому я могу передать NSMutableArray в качестве параметра.

Тогда имеем:

selTimeIntController = [[SingleSelectPickerViewController alloc] initWithSettings: listOfIntervals :kAlarmIntervalStr :myDataHolder.alarmInterval];
[self.navigationController pushViewController: selTimeIntController animated: YES];
[selTimeIntController release];

где этот listOfIntervals - это уже выделенный / инициализированный NSMutableArray *.

на моем SingleSelectPickerViewController у нас есть:

-(id)initWithSettings:(NSMutableArray*)sourceArray :(NSString*)viewCurrentValue :(NSString*)viewTitle {

    if(self = [self initWithNibName: kNibName bundle: [NSBundle mainBundle]]) {

            listOfIntervals = [NSMutableArray arrayWithArray: (NSMutableArray*)sourceArray];
            currentValue    = [[NSString alloc] initWithString: viewCurrentValue];
            title           = [[NSString alloc] initWithString: viewTitle];
    }

    return self;
}

Посредством отладки я могу видеть, как мой listOfIntervals создается на моем SingleSelectPickerViewController.

Здесь мы имеем дело с SingleSelectPickerViewController ':

- (void)dealloc {
    [super dealloc];

    [listOfIntervals release];
    [currentValue    release];
    [title           release];
}

Но каждый раз, когда я создаю экземпляр SingleSelectViewController, я сразу же получаю EXEC_BAD_ADDRESS со следующим стеком:

#0  0x96132688 in objc_msgSend ()
#1  0x00003ee2 in -[SingleSelectPickerViewController tableView:numberOfRowsInSection:] (self=0xd38940, _cmd=0x319a6bc0, tableView=0x102e000, section=0) at /Users/Cadu/iPhone/myApp/Classes/SingleSelectPickerViewController.m:115
#2  0x30a86bb4 in -[UISectionRowData refreshWithSection:tableView:tableViewRowData:] ()
#3  0x30a8879b in -[UITableViewRowData rectForFooterInSection:] ()
#4  0x30a883c7 in -[UITableViewRowData heightForTable] ()
#5  0x3094e8e6 in -[UITableView(_UITableViewPrivate) _updateContentSize] ()
#6  0x30940a7d in -[UITableView noteNumberOfRowsChanged] ()
#7  0x3094a2a0 in -[UITableView reloadData] ()
#8  0x30947661 in -[UITableView layoutSubviews] ()
#9  0x00b41d94 in -[CALayer layoutSublayers] ()
#10 0x00b41b55 in CALayerLayoutIfNeeded ()
#11 0x00b413ae in CA::Context::commit_transaction ()
#12 0x00b41022 in CA::Transaction::commit ()
#13 0x00b492e0 in CA::Transaction::observer_callback ()
#14 0x30245c32 in __CFRunLoopDoObservers ()
#15 0x3024503f in CFRunLoopRunSpecific ()
#16 0x30244628 in CFRunLoopRunInMode ()
#17 0x32044c31 in GSEventRunModal ()
#18 0x32044cf6 in GSEventRun ()
#19 0x309021ee in UIApplicationMain ()
#20 0x000020d8 in main (argc=1, argv=0xbffff0b8) at /Users/Cadu/iPhone/MyApp/

Есть идеи о том, что происходит?

Ответы [ 4 ]

7 голосов
/ 15 сентября 2009

В заголовке вопроса написано «утечка памяти». Все в вопросе указывает на «сбой». Это сбой, а не утечка памяти. Или, по крайней мере, вы не будете знать, есть ли у вас утечка памяти или нет, пока вы не исправите ошибки.

Наиболее вероятным источником сбоя является неправильное управление переменной экземпляра listOfIntervals.

listOfIntervals = [NSMutableArray arrayWithArray: (NSMutableArray*)sourceArray];

В частности, это должно быть:

listOfIntervals = [[NSMutableArray arrayWithArray: sourceArray] retain];

Как указывал Майк выше, передача ссылки на изменяемую коллекцию, вероятно, плохая идея. Что произойдет, если sourceArray изменится из-под вашего класса? Вы готовы с этим справиться?

Более распространенная идиома - объявить ваш метод как NSArray *, а затем скопировать массив:

listOfIntervals = [sourceArray mutableCopy]; // or -copy, if you don't need it to be mutable

(1) Приведение (NSMutableArray*) было ненужным. Знал ли вред, но зачем, если он не нужен?

(2) Вам необходимо сохранить listOfIntervals. + arrayWithArray: создаст автоматически выпущенный массив и, таким образом, массив был released после инициализации объекта, что приведет к аварийному завершению, которое вы видите.

(3) -copy & -mutableCopy возвращает сохраненные объекты, нет необходимости вызывать -retain.

Однако вам также необходимо исправить ваш -dealloc метод:

- (void)dealloc {
    // move this [super dealloc];

    [listOfIntervals release];
    [currentValue    release];
    [title           release];
    [super dealloc]; // to here
}

[super dealloc] всегда должно быть последним . В -dealloc нет ничего волшебного, и, таким образом, имея этот вызов сначала, вы приказали экземпляру освободить себя , а затем , пройдя и очистив переменные экземпляра. Это привело бы ко второму сбою (или неожиданному поведению).

В целом, я бы посоветовал вам перечитать рекомендации по управлению памятью.

http://developer.apple.com/iPhone/library/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html

0 голосов
/ 14 сентября 2009
  1. А как насчет sourceArray - он где-то хранится? Вы должны сохранить выделенный объект, чтобы использовать его за пределами его области действия, в противном случае он автоматически освобождается. Вы можете использовать простую ссылку на него в своем классе без повторного создания массива.

Или вы можете

listOfIntervals = [[NSMutableArray arrayWithArray: (NSMutableArray *) sourceArray] retain];

, а затем используйте его и отпустите.

  1. Ответ выше заметил, что вы вызываете [super dealloc] перед освобождением всех ваших распределений классов. [super dealloc] должен быть вызван в конце.

  2. Существует множество полезных ссылок об управлении памятью Какао, особенно об использовании функций выделения / сохранения. Это действительно важная часть программирования Какао / iPhone. См. Этот пример: Управление памятью в Какао или просто Google для этого

Надеюсь, это поможет, удачи

0 голосов
/ 15 сентября 2009

На этом вам нужно вернуться к основам.


Проблема 1: Вы передаете изменчивый фундаментный объект .

Это почти всегда свидетельствует о плохом дизайне. Посмотрите на Cocoa / CocoaTouch, и вы увидите очень мало случаев использования изменяемых классов, передаваемых в качестве параметров или возвращаемых. Это почти всегда происходит из-за ограничений производительности.

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


Проблема 2: Вы не сохраняете массив

Это основа управления памятью, и вы ДОЛЖНЫ это понять, прежде чем пытаться вникнуть в себя слишком много. Apple, вероятно, может объяснить это лучше, чем я, но это сводится к:

Если вам нужен доступ к объекту за пределами области, в которой он вам был предоставлен, сохраните его. Отпустите его, когда закончите позже.

То, что вы здесь делаете, это назначение массива с автоматическим освобождением в переменную экземпляра listOfIntervals, и, конечно, он освобождается и освобождается позже, взрывая ваше приложение при следующей попытке доступа к нему , Вместо этого вот правильный код:

- (id)initWithIntervals:(NSArray *)sourceArray
           currentValue:(NSString *)viewCurrentValue
                  title:(NSString *)viewTitle
{
  if (self = [self initWithNibName:kNibName bundle:nil])
  {
    listOfIntervals = [sourceArray mutableCopy];
    currentValue    = [viewCurrentValue copy];
    title           = [viewTitle copy];
  }

  return self;
}

Примечания:

  • Этот метод правильно назван. У него есть четкие имена для каждого аргумента.
  • Нет необходимости звонить [NSBundle mainBundle]. Как говорится в документации, NSViewController сам это выяснит.
  • Все переданные аргументы скопированы . Это имеет два важных эффекта:
    • NSArray и NSString являются объектами значения . То есть ваш код интересуется их значением, а не самим объектом. Какао хорошо позаботится о том, чтобы сделать это максимально эффективным.
    • -copy и -mutableCopt оба возвращают объекты со счетом удержания +1, поэтому они никуда не денутся, пока вы их не отпустите. Идеально подходит для типичной переменной экземпляра.
0 голосов
/ 14 сентября 2009

Я новичок в программировании на Mac, но думаю, что ваш метод dealloc находится в неправильном порядке.

Должно быть:

- (void)dealloc {
    [listOfIntervals release];
    [currentValue    release];
    [title           release];

    [super dealloc];
}

Вы должны это исправить, хотя я не думаю, что это решит вашу проблему.

Также я не понимаю, что вы делаете здесь:

if(self = [self initWithNibName: kNibName bundle: [NSBundle mainBundle]]) {
    //...
}

Я думаю, что должно быть:

if ( ! [super initWithNibName: kNibName bundle: [NSBundle mainBundle]] ) {

       return nil;
}

//...
...