Что произойдет, если я не сохраню IBOutlet? - PullRequest
39 голосов
/ 09 августа 2009

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

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

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

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

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

Ответы [ 6 ]

71 голосов
/ 09 августа 2009

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

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

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

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

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

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

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

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

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

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

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

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

11 голосов
/ 09 августа 2009

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

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

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

5 голосов
/ 09 сентября 2011

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

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

Эта модель была моделью до 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

В этой модели вы не сохраняете ивары 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];
}

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

@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) проверяет, существует ли метод установки. Если он не существует (существует только ивар), он отправляет дополнительное удержание в ивар. По этой причине вы не должны сохранять IBOutlet, если нет метода установки.

2 голосов
/ 09 августа 2009

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

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

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

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

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

1 голос
/ 21 февраля 2011

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

0 голосов
/ 09 августа 2009

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

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

или

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

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

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

...