Пользовательское управление памятью UIButton в dealloc - PullRequest
1 голос
/ 31 декабря 2010

Я надеюсь прояснить процессы, происходящие здесь.

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

- (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]?

Приветствие.

Ответы [ 3 ]

5 голосов
/ 31 декабря 2010

Это заслуживает двух ответов .... один для конкретного вопроса и один для управления памятью при замене экземпляра в -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!

Однако , еслиесли вы хотите вернуть какой-то другой объект, вам нужно release self и 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;
}
4 голосов
/ 31 декабря 2010

Это более чем сложно. Я суммировал мой ответ в 5 частях:

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

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

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

Обычно, процесс идет так:

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

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

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

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

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

- (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 : Конкретные ответы на конкретные вопросы:

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

Правильно.

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

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

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

Сортировка :) Количество сохраняемых записей может уменьшаться или уменьшаться до нуля в тот момент, когда вы регистрируете его. У автоматически выпущенных объектов все еще может быть счетчик хранения, равный единице, так как они фактически принадлежат пулу автоматического выпуска для текущего цикла выполнения. В этом отношении само представление может все еще принадлежать окну, которое еще не было выпущено. Единственное, о чем вам действительно нужно беспокоиться - это сбалансировать собственное управление памятью. Подробнее см. Руководство по управлению памятью Apple . С точки зрения вашего 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

1 голос
/ 31 декабря 2010

Первый:

Не звоните 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]. Вы могли бы пойти по пути, пытаясь инициализировать экземпляр вручную, например, UIView или UIControl, но это, вероятно, много проблем.

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

...