Где поставить iVars в "современный" Objective-C?

В книге "iOS6 by Tutorials" Рэя Вендерлиха есть очень хорошая глава о написании более "современного" кода Objective-C. В одном разделе книги описывают, как переместить iVars из заголовка класса в файл реализации. Поскольку все iVars должны быть частными, это, по-видимому, правильно.

Но до сих пор я нашел 3 способа сделать это. Все делают это по-другому.

1.) Поместите iVars под @implementantion внутри блока фигурных скобок (так оно сделано в книге).

2.) Поместите iVars в @implementantion без блока фигурных скобок

3.) Поместите iVars внутри частного интерфейса над @implementantion (расширение класса)

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

В каком направлении я должен идти?

Изменить: Я говорю только об iVars. Не свойства. Только дополнительные переменные, которые объект нуждается только для себя и которые не должны подвергаться внешнему воздействию.

Образцы кода

1)

#import "Person.h"

@implementation Person
{
    int age;
    NSString *name;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

2)

#import "Person.h"

@implementation Person

int age;
NSString *name;


- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

3)

#import "Person.h"

@interface Person()
{
    int age;
    NSString *name;
}
@end

@implementation Person

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

Ответ 1

Возможность размещения переменных экземпляра в блоке @implementation или в расширении класса является функцией "современной среды выполнения Objective-C", которая используется каждой версией iOS и 64-разрядным Mac OS X.

Если вы хотите записать 32-разрядные приложения Mac OS X, вы должны поместить свои переменные экземпляра в объявление @interface. Скорее всего, вам не нужно поддерживать 32-разрядную версию вашего приложения. OS X поддерживает 64-разрядные приложения с версии 10.5 (Leopard), которая была выпущена более пяти лет назад.

Итак, допустим, вы только пишете приложения, которые будут использовать современную среду исполнения. Где вы должны поместить свои яйца?

Вариант 0: в @interface (не делать этого)

Во-первых, откройте, почему мы не хотим помещать переменные экземпляра в объявление @interface.

  • Ввод переменных экземпляра в @interface предоставляет детали реализации для пользователей класса. Это может привести к тому, что пользователи (даже сами, используя собственные классы!) Будут полагаться на детали реализации, которых они не должны. (Это не зависит от того, объявляем ли мы ivars @private.)

  • Включение переменных экземпляра в @interface делает компиляцию более продолжительной, поскольку в любое время мы добавляем, изменяем или удаляем объявление ivar, мы должны перекомпилировать каждый .m файл, который импортирует интерфейс.

Поэтому мы не хотим ставить переменные экземпляра в @interface. Где мы должны их поставить?

Вариант 2: В @implementation без брекетов (Do not Do It)

Затем обсудим ваш вариант 2: "Поместите iVars под @implementantion без блока фигурных скобок". Это означает не объявлять переменные экземпляра! Вы говорите об этом:

@implementation Person

int age;
NSString *name;

...

Этот код определяет две глобальные переменные. Он не объявляет никаких переменных экземпляра.

Хорошо определить глобальные переменные в вашем файле .m, даже в вашем @implementation, если вам нужны глобальные переменные - например, потому что вы хотите, чтобы все ваши экземпляры делили какое-то состояние, например кеш. Но вы не можете использовать эту опцию для объявления ivars, потому что она не объявляет ivars. (Кроме того, глобальные переменные, частные для вашей реализации, обычно должны быть объявлены static, чтобы избежать загрязнения глобального пространства имен и риска ошибок времени ссылки.)

Это оставляет ваши варианты 1 и 3.

Вариант 1: В @implementation с фигурными скобками (Do It)

Обычно мы хотим использовать опцию 1: поместите их в основной блок @implementation в фигурных скобках, например:

@implementation Person {
    int age;
    NSString *name;
}

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

Итак, когда мы хотим использовать ваш вариант 3, помещая их в расширение класса?

Вариант 3: в расширении класса (только если это необходимо)

Практически никогда не было причин помещать их в расширение класса в том же файле, что и класс @implementation. В этом случае мы могли бы просто поместить их в @implementation.

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

// UICollectionView.h

@interface UICollectionView : UIScrollView

- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@property (nonatomic, retain) UICollectionView *collectionViewLayout;
// etc.

@end

@interface UICollectionView (ReusableViews)

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;

- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;

- (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;
- (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;

@end

ОК, теперь мы можем реализовать основные методы UICollectionView в UICollectionView.m, и мы можем реализовать методы, которые управляют многоразовыми представлениями в UICollectionView+ReusableViews.m, что делает наш исходный код немного более управляемым.

Но нашему многоразовому коду управления просмотром нужны некоторые переменные экземпляра. Эти переменные должны быть подвержены основному классу @implementation в UICollectionView.m, поэтому компилятор выдает их в файле .o. И нам также нужно подвергнуть эти переменные экземпляра коду в UICollectionView+ReusableViews.m, поэтому эти методы могут использовать ivars.

Здесь нам нужно расширение класса. Мы можем разместить ivars с многократным просмотром-просмотром в расширении класса в закрытом файле заголовка:

// UICollectionView_ReusableViewsSupport.h

@interface UICollectionView () {
    NSMutableDictionary *registeredCellSources;
    NSMutableDictionary *spareCellsByIdentifier;

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end

Мы не будем отправлять этот заголовочный файл пользователям нашей библиотеки. Мы просто импортируем его в UICollectionView.m и в UICollectionView+ReusableViews.m, чтобы все, что должно было видеть эти ivars, могло их увидеть. Мы также выбрали метод, который мы хотим, чтобы основной метод init вызывал, чтобы инициализировать код управления повторным просмотром. Мы будем называть этот метод из -[UICollectionView initWithFrame:collectionViewLayout:] в UICollectionView.m, и мы его реализуем в UICollectionView+ReusableViews.m.

Ответ 2

Вариант 2 неверен. Это глобальные переменные, а не переменные экземпляра.

Варианты 1 и 3 по существу идентичны. Это не имеет никакого значения.

Выбор заключается в том, следует ли поместить переменные экземпляра в файл заголовка или файл реализации. Преимущество использования файла заголовка в том, что у вас есть быстрый и простой комбинатор клавиш (Command + Control + Up в Xcode) для просмотра и редактирования ваших переменных экземпляра и декларации интерфейса.

Недостатком является то, что вы публикуете личные данные своего класса в общедоступном заголовке. Это нежелательно, это некоторые случаи, особенно если вы пишете код для других пользователей. Другая потенциальная проблема заключается в том, что если вы используете Objective-C ++, полезно избегать размещения любых типов данных С++ в вашем файле заголовка.

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

Ответ 3

В основном это касается видимости ivar для подклассов. Подклассы не смогут получить доступ к переменным экземпляра, определенным в блоке @implementation.

Для многоразового кода, который я планирую распространять (например, в библиотечном или рамочном коде), где я предпочитаю не выставлять переменные экземпляра для публичного осмотра, тогда я склонен разместить ivars в блоке реализации (ваш вариант 1).

Ответ 4

Вы должны поместить переменные экземпляра в частный интерфейс выше реализации. Вариант 3.

Документация для чтения в этом Программирование в Objective-C.

Из документации:

Вы можете определить переменные экземпляра без свойств

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

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

Ответ 5

Публичные ivars действительно должны быть объявлены свойствами в @interface (вероятно, о чем вы думаете в 1). Частные ивары, если вы используете новейший Xcode и используете современную среду выполнения (64-разрядную ОС X или iOS), могут быть объявлены в @implementation (2), а не в расширении класса, что, скорее всего, думая в 3.