Что произойдет, если я не сохраню IBOutlet?

Если я сделаю это:

@interface RegisterController : UIViewController <UITextFieldDelegate>
{
    IBOutlet UITextField *usernameField;
}

вместо этого:

@interface RegisterController : UIViewController <UITextFieldDelegate>
{
    UITextField *usernameField;
}
@property (nonatomic, retain) IBOutlet UITextField *usernameField;

Произойдет ли что-нибудь плохое? Я знаю, что во втором случае поле сохраняется, но имеет ли это значение, поскольку перо владеет полем? Уйдет ли поле без сохранения? и при каких обстоятельствах? Код в первом случае работает, было интересно, проблема это или нет с точки зрения управления памятью.


person Jordan    schedule 09.08.2009    source источник


Ответы (6)


Рекомендуется декларировать свойства для всех ваших IBOutlets для ясности и единообразия. Подробности изложены в Руководстве по программированию управления памятью.. Основная суть заключается в том, что когда ваши объекты NIB разархивированы, код загрузки пера проходит и устанавливает все IBOutlets с помощью setValue: forKey :. Когда вы объявляете поведение управления памятью в свойстве, нет никакой загадки в том, что происходит. Если представление выгружается, но вы использовали свойство, которое было объявлено как сохранение, у вас все еще есть действительная ссылка на ваше текстовое поле.

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

Я собираюсь сделать некоторые предположения относительно контекста, в котором вы работаете - я предполагаю, что UITextField выше является подвидом другого представления, которое контролируется UIViewController. Я предполагаю, что в какой-то момент представление находится за пределами экрана (возможно, оно используется в контексте UINavigationController), и что в какой-то момент ваше приложение получает предупреждение о памяти.

Итак, допустим, вашему подклассу UIViewController необходимо получить доступ к своему представлению, чтобы отобразить его на экране. На этом этапе будет загружен файл пера, и все свойства IBOutlet будут установлены кодом загрузки пера с помощью setValue: forKey :. Здесь важно отметить представление верхнего уровня, для которого будет установлено свойство представления UIViewController (которое сохранит это представление верхнего уровня), и ваш UITextField, который также будет сохранен. Если он просто установлен, он будет сохранен кодом загрузки пера, в противном случае свойство сохранит его. UITextField также будет подпредставлением UIView верхнего уровня, поэтому он будет иметь дополнительное сохранение на нем, находясь в массиве подвидов представления верхнего уровня, поэтому на этом этапе текстовое поле было сохранено дважды.

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

Теперь предположим, что другой контроллер представления помещен в стек контроллера UINavigation, так что это представление больше не находится на переднем плане. В случае предупреждения о памяти, представление этого внеэкранного контроллера представления будет выгружено. На этом этапе свойство представления верхнего уровня UIView будет обнулено, оно будет освобождено и освобождено.

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

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

Один интересный момент, который здесь подчеркивается, заключается в том, что поскольку UITextField дополнительно сохраняется через свойство, вы, вероятно, не захотите сохранять его в случае предупреждения о памяти. По этой причине вы должны обнулить свойство в методе - [UIViewController viewDidUnload]. Это позволит избавиться от последней версии в UITextField и освободить ее, как задумано. Если вы используете свойство, вы должны не забыть освободить его явно. Хотя эти два действия функционально эквивалентны, цель различна.

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

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

Также стоит отметить, что поведение управления памятью в Mac OS X на рабочем столе отличается. На настольном компьютере установка IBOutlet без установщика не сохраняет переменную экземпляра; но снова использует сеттер, если он доступен.

person Joey Hagedorn    schedule 09.08.2009
comment
Джои, отлично пиши. Идеально! - person Jordan; 09.08.2009
comment
Ах, боюсь, я ошибся здесь в своем описании. Разница между iPhone OS и Mac OS X заключается в том, что в iPhone OS установка розетки будет сохранять объект по умолчанию, если сеттер недоступен, поэтому вы должны выпустить его. В Mac OS X, если сеттер недоступен, он просто назначает. Извините за ошибку, я исправил свой пост выше. - person Joey Hagedorn; 14.08.2009
comment
Это слишком много, чтобы читать, но +1, и я предпочел это и продолжу использовать сохранить (я собирался начать использовать assign). - person Dan Rosenstark; 09.07.2010
comment
Вот еще один очень связанный ответ, в котором проясняются некоторые вопросы об управлении памятью: stackoverflow.com/questions/1221516/ - person Joey Hagedorn; 28.09.2010

Объявление чего-либо IBOutlet с точки зрения управления памятью ничего не делает (IBOutlet буквально # определяется как ничего). Единственная причина для включения IBOutlet в объявление - это если вы намереваетесь подключить его в Interface Builder (это то, для чего предназначено объявление IBOutlet, подсказка для IB).

Теперь единственная причина создать @property для переменной экземпляра - это если вы намереваетесь присвоить их программно. Если вы этого не сделаете (то есть вы только настраиваете свой пользовательский интерфейс в IB), не имеет значения, создаете ли вы свойство или нет. Нет причин, ИМО.

Вернемся к вашему вопросу. Если вы устанавливаете только этот ivar (usernameField) в IB, не беспокойтесь об этом свойстве, это ни на что не повлияет. Если вы ДЕЙСТВИТЕЛЬНО создаете свойство для usernameField (потому что вы создаете его программно), определенно сделайте свойство для него, и ОБЯЗАТЕЛЬНО сделайте так, чтобы свойство сохранялось, если это так.

person zpasternack    schedule 09.08.2009

На самом деле есть две модели:

СТАРАЯ МОДЕЛЬ

Эта модель была моделью до Objective-C 2.0 и унаследована от Mac OS X. Она все еще работает, но вы не должны объявлять свойства для изменения ivars. Это:

@interface StrokeWidthController : UIViewController {
    IBOutlet UISlider* slider;
    IBOutlet UILabel* label;
    IBOutlet StrokeDemoView* strokeDemoView;
    CGFloat strokeWidth;
}
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end

В этой модели вы не сохраняете ivars IBOutlet, но вы должны их освободить. Это:

- (void)dealloc {
    [slider release];
    [label release];
    [strokeDemoView release];
    [super dealloc];
}

НОВАЯ МОДЕЛЬ

Вы должны объявить свойства для переменных IBOutlet:

@interface StrokeWidthController : UIViewController {
    IBOutlet UISlider* slider;
    IBOutlet UILabel* label;
    IBOutlet StrokeDemoView* strokeDemoView;
    CGFloat strokeWidth;
}
@property (retain, nonatomic) UISlider* slider;
@property (retain, nonatomic) UILabel* label;
@property (retain, nonatomic) StrokeDemoView* strokeDemoView;
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end

Кроме того, вы должны освободить переменные в dealloc:

- (void)dealloc {
    self.slider = nil;
    self.label = nil;
    self.strokeDemoView = nil;
    [super dealloc];
}

В дальнейшем режиме на не хрупких платформах вы можете удалить ivars:

@interface StrokeWidthController : UIViewController {
    CGFloat strokeWidth;
}
@property (retain, nonatomic) IBOutlet UISlider* slider;
@property (retain, nonatomic) IBOutlet UILabel* label;
@property (retain, nonatomic) IBOutlet StrokeDemoView* strokeDemoView;
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end

СТРАННАЯ ВЕЩЬ

В обоих случаях выходы настраиваются путем вызова setValue: forKey :. Среда выполнения внутренне (в частности _decodeObjectBinary) проверяет, существует ли метод установки. Если он не существует (существует только ivar), он отправляет дополнительное удержание ivar. По этой причине вы не должны сохранять IBOutlet, если нет метода установки.

person Freeman    schedule 09.09.2011
comment
Принято считать, что вы не должны использовать сеттеры @property в dealloc, потому что они могут иметь непредвиденные последствия, если вы предоставили свой собственный сеттер. Вместо этого вы должны использовать [someOutletVar release]; someOutletVar = nil;, чтобы избежать случайного выделения чего-либо во время освобождения. - person Paul Scott; 17.11.2012

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

В обоих случаях вам все равно нужно будет освободить и обнулить IBOutlet в методах dealloc или viewDidUnload.

IBOutlet указывает на объект, созданный в файле XIB. Этот объект принадлежит объекту Owner файла XIB-файла (обычно это контроллер представления, в котором объявлен IBOutlet.

Поскольку объект создается в результате загрузки XIB, его счетчик сохранения равен 1 и принадлежит Владельцу вашего файла, как упоминалось выше. Это означает, что владелец файла несет ответственность за его освобождение, когда он освобождается.

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

person Jasarien    schedule 09.08.2009

Объекты в файле пера создаются со счетчиком сохранения 1, а затем автоматически освобождаются. По мере того, как он перестраивает иерархию объектов, UIKit повторно устанавливает соединения между объектами с помощью setValue: forKey :, который использует доступный метод установки или сохраняет объект по умолчанию, если метод установки недоступен. Это значит, что любой объект, для которого у вас есть розетка, остается действительным. Однако, если есть какие-либо объекты верхнего уровня, которые вы не храните в торговых точках, вы должны сохранить либо массив, возвращаемый методом loadNibNamed: owner: options:, либо объекты внутри массива, чтобы предотвратить преждевременное освобождение этих объектов.

person Dharmateja    schedule 21.02.2011

Во втором случае вы добавляете метод получения / установки для этого конкретного IBOutlet. Каждый раз, когда вы добавляете метод получения / установки, вы (почти всегда) хотите, чтобы он сохранялся для проблем с управлением памятью. Я думаю, что лучший способ задать свой вопрос был бы следующим:

@interface RegisterController : UIViewController <UITextFieldDelegate>
{
IBOutlet UITextField *usernameField;
}
@property (nonatomic) IBOutlet UITextField *usernameField;

or

@interface RegisterController : UIViewController <UITextFieldDelegate>
{
IBOutlet UITextField *usernameField;
}
@property (nonatomic, retain) IBOutlet UITextField *usernameField;

В этом случае да, вам нужно будет добавить сохранение, поскольку это повлияет на управление памятью. Даже если это может не иметь никаких эффектов, если вы программно добавляете и удаляете IBOutlet, вы потенциально можете столкнуться с проблемами.

Как правило: всегда добавляйте @property (с сохранением) всякий раз, когда у вас есть IBOutlet.

person Community    schedule 09.08.2009
comment
Давайте на мгновение забудем о геттере / сеттере. В исходном вопросе. Что плохого произойдет, если IBOutlets не сохранятся? Код отлично работает без настройки свойства (сохранения). Я могу получить доступ к usernameField, например, usernameField.text без проблем. - person Jordan; 09.08.2009
comment
Если вы не добавите сохранение, вы можете столкнуться с проблемами, если измените IBOutlet из кода (это может произойти, когда ваша программа становится большой). Хотя это может не быть проблемой прямо сейчас, в конечном итоге она может стать проблемой. . Обратите внимание: если вы сейчас просто используете IB, он будет работать отлично. - person ; 09.08.2009