2010-12-31 3 views
1

Я надеюсь уточнить процессы, происходящие здесь.Пользовательское управление памятью UIButton в dealloc

Я создал подкласс UIButton, чей метод инициализации выглядит следующим образом:

- (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame { 
    self = [UIButton buttonWithType:UIButtonTypeCustom]; 
    [self setTitle:title forState:UIControlStateNormal]; 
    self.frame = btnFrame; 
    return self; 
} 

В моем контроллере зрения я создаю одну из этих кнопок и добавив его как подвид:

myButton = [[CustomButton alloc] initWithTitle:@"Title" frame:someFrame]; 
[self.view addSubview:myButton]; 

В методе контроллера вида dealloc я регистрирую счетчик остатков моей кнопки:

- (void)dealloc { 
    NSLog(@"RC: %d", [myButton retainCount]); //RC = 2 
    [super dealloc]; 
    NSLog(@"RC: %d", [myButton retainCount]); //RC = 1 
} 

Как я понимаю, myButton на самом деле не сохранен, хотя я использовал его, используя alloc, потому что в моем подклассе я создал кнопку автообновления (используя buttonWithType:).

В dealloc это означает, что , когда dealloc называется супервизор, отпускает кнопку, и ее количество удержания уменьшается до 1? Кнопка еще не автореализована?

Или мне нужно получить, чтобы сохранить отсчет до нуля после вызова [super dealloc]?

Cheers.

ответ

4

Это более чем сложно. Я кратко мой ответ на 5 частей:

  1. Создание пользовательского init метод, который возвращает другой объект
  2. ПРЕДУПРЕЖДЕНИЕ: остерегайтесь незаконного доступа к памяти!
  3. Как правильно передать право собственности на кнопке своего родительского вида
  4. Конкретных ответов на конкретные вопросы
  5. предложения по улучшению

Часть 1: Создание пользовательского init метода, возвращает другой объект:

Это пример особого случая, а именно: obj ect, возвращаемый с -initWithTitle:frame:, равен не тот же «объект», которому было отправлено сообщение в первую очередь.

Обычно говоря, процесс выглядит следующим образом:

instance = [Class alloc]; 
[instance init]; 
... 
[instance release]; 

Конечно, Alloc и Инициализационные вызовы обычно группируются в одну строку кода. Ключевое замечание здесь заключается в том, что «объект» (не более, чем выделенный блок памяти в этой точке), который получает вызов init, уже выделен. Если вы вернете другой объект (как в вашем примере), вы несете ответственность за освобождение этого исходного блока памяти.

Следующим шагом будет возврат нового объекта с надлежащим количеством удержаний.Поскольку вы используете заводский метод (+buttonWithType:), результирующий объект был автореализован, и вы должны сохранить его, чтобы установить правильное количество удержания.

Edit: Правильный -init метод должен явно проверить, чтобы убедиться, что он работает с правильно инициализирован объект перед тем он делает что-нибудь еще с этим объектом. Этот тест отсутствовал в моем ответе, но присутствует в bbum's answer.

Вот как ваш метод инициализации должен выглядеть:

- (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame { 
    [self release]; // discard the original "self" 
    self = [UIButton buttonWithType:UIButtonTypeCustom]; 
    if (self == nil) { return nil; } 
    [self retain]; // set the proper retain count 
    [self setTitle:title forState:UIControlStateNormal]; 
    self.frame = btnFrame; 
    return self; 
} 

Часть 2: ПРЕДУПРЕЖДЕНИЕ: остерегайтесь незаконного доступа к памяти!

Если вы назначаете экземпляр CustomButton, а затем заменяете его экземпляром UIButton, вы можете легко вызвать некоторые очень тонкие ошибки памяти. Скажем CustomButton имеет некоторые Ивар:

@class CustomButton : UIButton 
{ 
    int someVar; 
    int someOtherVar; 
} 
... 
@end; 

Теперь, когда вы замените выделенную CustomButton с экземпляром UIButton в вашем методе пользовательских init..., вы возвращаете блок памяти, который слишком мал, чтобы держать CustomButton, но ваш код будет продолжать обрабатывать этот блок кода, как если бы он был полноразмерным CustomButton. О, о.

Например, следующий код теперь очень, очень плохо:

- (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame { 
    [self release]; // discard the original "self" 
    self = [UIButton buttonWithType:UIButtonTypeCustom]; 
    [self retain]; // set the proper retain count 

    someOtherVar = 10; // danger, Will Robinson! 

    return self; 
} 

Часть 3: Как правильно передать право собственности на кнопку своего родительского вида:

Что касается метода контроллера вашего вида dealloc, вам необходимо позвонить [myButton release], если вы инициализировали кнопку, как показано. Это должно следовать правилу, что вы должны освобождать все, что вы выделяете, сохраняете или копируете. Лучший способ справиться с этой проблемой, чтобы позволить виду контроллера брать на себя ответственность этой кнопки (что он делает автоматически при добавлении кнопки как подвид):

myButton = [[CustomButton alloc] initWithTitle:@"Title" 
             frame:someFrame]; // RC = 1 
[self.view addSubview:myButton];       // RC = 2 
[myButton release];          // RC = 1 

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


Часть 4: Конкретные ответы на конкретные вопросы:

Q: Как я понимаю, MyButton на самом деле не сохраняется, даже если я его вызвал с помощью Alloc, потому что в моем подклассу Я создал кнопку автозапуска (используя buttonWithType :).

Исправить.

В: В dealloc это означает, что, когда dealloc называется супервизором, отпускает кнопку, и ее значение удержания уменьшается до 1? Кнопка еще не автореализована?

Также правильно.

Вопрос: Или мне нужно получить, чтобы сохранить отсчет до нуля после вызова [super dealloc]?

Сортировка :) Счет за хранение может или не упасть до нуля в момент его регистрации. Автореализованные объекты могут по-прежнему иметь счетчик удержания одного, поскольку они фактически принадлежат пулу автозаполнения для текущего цикла цикла. В этом отношении само представление все равно может принадлежать окну, которое еще не выпущено. Единственное, что вам действительно нужно беспокоиться - это сбалансировать собственное управление памятью. См. Apple's memory management guide. С точки зрения вашего viewController вы выделили кнопку один раз, поэтому вы должны ее выпустить ровно один раз. Когда дело доходит до вашего пользовательского метода init..., все становится немного сложнее, но принцип тот же. Блок памяти был выделен, поэтому он должен быть выпущен (часть 1), и (часть 2) init должен вернуть объект с сохранением количества единиц (для последующего его последующего выпуска).


Часть 5: Предложение по улучшению:

Вы могли бы избежать большинства пользовательских инициализатора беспорядок, просто создавая свой собственный метод фабрики в том же духе, как тот, предоставленной UIButton:

+ (id)buttonWithTitle:(NSString *)title frame:(CGRect)btnFrame { 
    UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom]; 
    [button setTitle:title forState:UIControlStateNormal]; 
    button.frame = btnFrame; 
    return button; 
} 

Следует отметить, что этот подход все еще может привести к ошибкам доступа памяти, как определено в части 2

+0

@bbum: какое объяснение неверно? Это стало очень длинным ответом, и я не уверен, в какой части вы ссылаетесь на ':)' –

+0

@bbum: точка, отсчитанная по абсолютному счету сохранения, бесполезна. Я использовал их только для иллюстрации неявного сохранения в 'addSubview:'. –

+0

Извините - количество задержек по инициализаторам * было * правильным (за исключением обсуждения абсолютного сохранения количества). И остальная часть вашего ответа - хотя довольно долго :) - неплохо. – bbum

1

Первое:

Не называйте retainCount

Абсолютное сохранение количества объекта находится рядом с бесполезным. Есть всегда лучшие способы рассуждать об управлении памятью в вашем приложении.


Следующая:

Ваш метод initWithTitle:frame: выделяет и возвращает экземпляр UIButton, а не экземпляр подкласса. Если это то, что вы хотите, нет необходимости в подклассе.

Если вам действительно нужен экземпляр подкласса UIButton, это будет сложнее. Быстрый поиск в Google и чтение документации показывают, что UIButton действительно не предназначен для подкласса.

Я просто попытался:

@interface FooButton:UIButton 
@end 
@implementation FooButton 
@end 

FooButton *f = [FooButton buttonWithType: UIButtonTypeDetailDisclosure]; 
NSLog(@"%@", f); 

И напечатал:

<UIButton: 0x9d03fa0; frame = (0 0; 29 31); opaque = NO; layer = <CALayer: 0x9d040a0>> 

Т.е.единственный способ, который будет использоваться для создания UIButton экземпляров, явно явно не выделить через [self class]. Вы можете пойти по пути, чтобы инициализировать экземпляр вручную ala UIView или UIControl, но это, вероятно, много неприятностей.

Что вы пытаетесь сделать?

5

Это заслуживает двух ответов .... один для конкретного вопроса и один для управления памятью при замене экземпляра в -init (этот).

Инициализаторы - странная птица в мире управления объектами-C. Фактически, вы управляете self. При входе self сохраняется. При выходе вы должны вернуть либо оставленный объект - не обязательно должен быть тот же объект, что и self - илиnil.

Таким образом, нарушая стандартную идиому [[[Foo alloc] init] autorelease] вниз:

id x = [Foo alloc]; // allocates instance with RC +1 
x = [x init]; // RC is preserved, but x may differ 
[x autorelease]; // RC -1 (sometime in future) 

Обратите внимание, что все сохраняют отсчеты [RC] выражены как дельт.

Таким образом, в методе init, вы обычно не меняют сохранить счетчик self на всех!

Однако, если вы хотите вернуть какой-то другой объект, вам нужно releaseselfиretain все, что вы собираетесь вернуться (выделяется ли то или ранее выделено где-то еще, скажем, когда объект извлекается из кеш).

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

- init { 
    [self release]; 
    self = nil; 
    id newObject = [SomeClass alloc]; 
    newObject = [newObject init]; 
    if (newObject) { 
     self = newObject; 
     ... initialize self here, if that is your fancy ... 
    } 
    return self; 
} 
+0

+1 за то, что он лаконичен! –

+0

+1 Как у вас была прекрасная причина указать мне, что я ошибаюсь – EmptyStack

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