Где поставить 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

5 ответов

Решение

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

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

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

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

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

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

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

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

Вариант 2: в @implementation без брекетов (не делай этого)

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

@implementation Person

int age;
NSString *name;

...

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

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

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

Вариант 1: в @implementation с брекетами (сделай это)

Обычно мы хотим использовать вариант 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Таким образом, эти методы могут использовать ивары.

Это где нам нужно расширение класса. Мы можем поместить ивары reusable-view-management в расширение класса в частном заголовочном файле:

// UICollectionView_ReusableViewsSupport.h

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

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end

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

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

Варианты 1 и 3 практически идентичны. Это не имеет абсолютно никакого значения.

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

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

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

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

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

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

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

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

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

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

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

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

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