Должен ли каждый ивар быть собственностью?

Я считаю, что при программировании для iOS рекомендуется повсеместно использовать свойства для доступа к переменным экземпляра из-за преимуществ, которые это дает, помимо прочего, управлению памятью.

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

4 ответа

Решение

Не обязательно объявлять свойства для всех ivars. Несколько моментов приходят на ум:

  • Если ивару назначают только один раз за время существования объекта, вы ничего не получите, объявив свойство. Просто сохраните / скопируйте / назначьте во время init и затем отпустите при необходимости во время dealloc,
  • Если ивар будет часто меняться, объявление свойства и всегда использование методов доступа поможет избежать ошибок управления памятью.
  • Вы можете объявить свойства в расширении класса в файле.m, а не в файле.h, если свойства и ivars должны быть частными.
  • При нацеливании на iOS 4.0+ вам не нужно вообще объявлять ivars в заголовке, если вы определяете свойство и синтезируете методы доступа.

Поэтому я обычно использую свойства, но для таких вещей, как NSMutableArray что объект выделяется во время init и использует, чтобы держать кучу whatevers, я буду использовать простой старый ивар, так как я никогда не буду переназначать ивар.

Хотя ответ Даниэля верен, я думаю, что он упускает важный момент. А именно:

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

Преимущества - последовательность; последовательное управление памятью и последовательное поведение.

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

iVar = [foo retain];
self.iVar = foo;

Первый - это прямая установка переменной экземпляра, и уведомлений об изменениях не будет. Второй проходит через установщик и, таким образом, сохраняет любые настройки подкласса после установки и гарантирует, что любые наблюдатели свойства будут уведомлены об изменении.

Если вы используете ivars непосредственно во всем своем коде (внутри класса - если вы используете ivars экземпляра непосредственно вне этого экземпляра, хорошо... любой подрядчик, работающий на вашей кодовой базе, должен удвоить свои ставки;), тогда вы должны либо обрабатывать распространение уведомлений об изменениях вручную (обычно путем вызова willChangeValueForKey:/didChangeValueForKey) или явным образом спроектировать ваше приложение, чтобы избежать использования механизмов, основанных на наблюдении значения ключа.

Вы говорите "занимает слишком много кода". Я не вижу этого; в приведенных выше двух строках кода точечный синтаксис содержит меньше символов. Даже вызов метода сеттера с использованием традиционного синтаксиса будет меньше кода.

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

Свойство - это просто синтаксический сахар, который не позволяет вам писать одни и те же методы снова и снова. Со свойством у вас есть сеттер, который освобождает старый объект и сохраняет новый бесплатно.

Для приватных полей - я предлагаю использовать прямые ivars безопасно только для примитивных типов (BOOL/int/float и т. Д.). Я считаю хорошей практикой оборачивать в свойствах все, что связано с управлением памятью - даже редко используемые поля. Дополнительным преимуществом этого подхода является то, что IDE обычно выделяет прямой доступ к иварам по-разному, поэтому у вас всегда есть хорошее разделение простых скалярных полей и полей типа объекта.

Вопреки этому, я бы настоятельно рекомендовал любые прямые ивары в публичном интерфейсе класса. Из-за динамической природы языка это может привести к ошибкам во время выполнения, которые чрезвычайно сложно найти, локализовать и исправить. Рассмотрим следующую иерархию

@interface BaseControl
...
@end

@interface Label : BaseControl
...
@end

@interface Button : BaseControl {
  @public
    BOOL enabled;
}
@end

и фрагмент кода

- (void)enableAllButtons {
    NSArray *buttons = [self getAllButtons];   // expected to contain only Button instances
    for (Button *button in buttons) {
        button->enabled = YES;
    }
} 

Теперь представьте, что где-то в логике -getAllButtons есть ошибка, и вы также получите некоторые метки, возвращенные в этом массиве - так что этим экземплярам класса Label будет присвоен пропущенный ivar. Факт, который может быть удивительным, заключается в том, что -enableAllButtons не будет аварийно завершать работу в этом случае. Но в этот момент внутренняя структура этих экземпляров Label повреждена, и это приведет к неопределенному поведению и сбоям при их использовании в других местах.

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

Очевидно, что если вы используете @property (assign) BOOL enabled; вы получите простое в диагностике и исправлении исключение времени выполнения в -enableAllButtons.

Другие вопросы по тегам