Куди поставити iVars у “сучасному” Objective-C?


82

У книзі "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

4
Немає коментарів щодо "право", але інший варіант - до якого я, здається, рухаюся - повністю скидати iVars і робити все властивістю (переважно в розширенні класу у файлі .m). Послідовність позбавляє мене від роздумів про реалізацію деталей стану.
Філіп Міллз,

1
Це питання дійсно було б корисно для інших, якщо ви оновили питання, щоб показати реальні приклади коду, що демонструють кожен з 3 варіантів. Я особисто використовую лише варіант 1, бачив варіант 3, але я не знайомий з варіантом 2.
rmaddy

1
Дякую Медді. Я додав зразок коду.
TalkingCode

4
На жаль, щойно помітив оновлення. Варіант 2 недійсний. Це не створює іварів. Це створює глобальні змінні файлу. Кожен екземпляр цього класу матиме спільний набір змінних. Ви можете зробити їх змінними класу, а не змінними екземпляра.
rmaddy

Відповіді:


162

Можливість розміщувати змінні екземпляра в @implementationблоці або в розширенні класу - це особливість «сучасного середовища виконання 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брекетах (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-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:]in UICollectionView.m, а реалізуємо в UICollectionView+ReusableViews.m.


4
Варіант 3: також можна використовувати, коли 1) ви хочете примусити використовувати властивості для своїх ivars, навіть у межах вашої власної реалізації (@property int age), і 2) ви хочете замінити загальнодоступне властивість лише для читання (@property (readonly) int age) у вашому заголовку, щоб дозволити коду отримати доступ до властивості як readwrite у реалізації (@property (readwrite) int age). Якщо немає інших способів зробити це, @rob mayoff?
leanne

@leanne Якщо ви хочете замінити readonlyвластивість приватною readwrite, вам слід використовувати розширення класу. Але питання полягало в тому, куди поставити івари, а не де подавати декларації про майно. Я не бачу, як використання розширення класу може перешкодити реалізації отримати доступ до ivars. Для цього вам потрібно помістити реалізації методів у категорію, тому що компілятор повинен побачити всі розширення класу, що декларують ivar, перш ніж він побачить @implementation.
грабуй майофф

@rob mayoff, true і true - ви робите дійсні бали. Холлі уточнив, що тут не йдеться про властивості, і "накладення" було з мого боку дещо сильним щодо їх використання. Незалежно від того, якщо хтось хоче використовувати властивості для доступу до своїх ivars, замість того, щоб робити це безпосередньо, тоді це буде спосіб зробити це. І звичайно, ви все ще можете отримати доступ до ivars безпосередньо, використовуючи '_', (_age = someAge). Я додав коментар, оскільки вважав, що використання власності варто згадати під час обговорення ivars, оскільки багато людей використовують їх разом, і це був би спосіб зробити це.
leanne

Це «Варіант 0: В @ інтерфейсі (Не робіть цього)».
грабувати майофф

5

Варіант 2 абсолютно невірний. Це глобальні змінні, а не змінні екземпляра.

Варіанти 1 і 3 по суті ідентичні. Це абсолютно не має значення.

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

Недоліком є ​​те, що ви публікуєте приватні дані свого класу в загальнодоступному заголовку. Це не бажано в деяких випадках, особливо якщо ви пишете код для використання іншими. Інша потенційна проблема полягає в тому, що якщо ви використовуєте Objective-C ++, добре уникати розміщення будь-яких типів даних C ++ у своєму заголовковому файлі.

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


4

В основному це пов’язано з видимістю івару для підкласів. Підкласи не матимуть доступу до змінних екземпляра, визначених у @implementationблоці.

Для багаторазового коду, який я планую розповсюджувати (наприклад, бібліотеки або коду фреймворку), де я вважаю за краще не виставляти змінні екземпляра для загальнодоступної перевірки, тоді я схильний розміщувати ivars у блоці реалізації (ваш варіант 1).


3

Ви повинні розмістити змінні екземпляра в приватному інтерфейсі над реалізацією. Варіант 3.

Документація, яку слід прочитати з цього приводу, - посібник із програмування в Objective-C .

З документації:

Ви можете визначити змінні екземпляра без властивостей

Найкращою практикою є використання властивості на об’єкті в будь-який час, коли потрібно відстежувати значення чи інший об’єкт.

Якщо вам потрібно визначити власні змінні екземпляра без оголошення властивості, ви можете додати їх всередину фігурних дужок у верхній частині інтерфейсу класу або реалізації, наприклад:


1
Я згоден з вами: якщо ви збиралися визначати ivar, найкраще місце має розширення приватного класу. Цікаво, що документація, на яку ви посилаєтесь, не тільки не займає позиції щодо того, де слід визначати ivars (у ньому просто викладаються всі три альтернативи), але йде далі і рекомендує взагалі не використовувати ivars, скоріше "найкраща практика використовувати властивість". Це найкраща порада, яку я бачив на сьогодні.
Роб

1

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.