Декларація / визначення розташування змінних в ObjectiveC?


113

З того часу, як почав працювати над додатками для iOS та ціл C, я був дуже спантеличений різними місцями, де можна було оголосити та визначити змінні. З одного боку ми маємо традиційний підхід С, а з іншого - нові директиви ObjectiveC, які додають ОО до того ж. Чи можете ви, люди, допомогти мені зрозуміти найкращу практику та ситуації, коли я хотів би використовувати ці місця для моїх змінних і, можливо, виправити своє теперішнє розуміння?

Ось зразок класу (.h та .m):

#import <Foundation/Foundation.h>

// 1) What do I declare here?

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

// 3) class-specific method / property declarations

@end

і

#import "SampleClass.h"

// 4) what goes here?

@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

@implementation SampleClass
{
    // 6) define ivars
}

// 7) define methods and synthesize properties from both public and private
//    interfaces

@end
  • Я розумію 1 і 4, що це декларації на основі файлів у стилі С, які не розуміють поняття класу, і, таким чином, їх потрібно використовувати саме так, як вони будуть використовуватися в C. Я бачив їх раніше застосовувались для реалізації статичних синглів на змінних. Чи є інші зручні способи використання, які мені не вистачає?
  • Моя потреба в роботі з iOS полягає в тому, що ivars повністю припинено поза межами директиви @synthesize і, таким чином, їх можна здебільшого ігнорувати. Це так?
  • Щодо 5: чому я б хотів декларувати методи в приватних інтерфейсах? Мої методи приватного класу, здається, збираються просто без декларації в інтерфейсі. Це в основному для читабельності?

Дякую купу, люди!

Відповіді:


154

Я можу зрозуміти вашу розгубленість. Тим більше, що останні оновлення Xcode та нового компілятора LLVM змінили спосіб заявлення ivars та властивостей.

До "сучасного" Objective-C (у "старому" Obj-C 2.0) у вас не було багато варіантів. Змінні екземпляри, які використовуються для оголошення в заголовку між фігурними дужками { }:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

Ви мали змогу отримати доступ до цих змінних лише у своїй реалізації, але не з інших класів. Для цього вам довелося оголосити методи аксесуарів, які виглядають приблизно так:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}

- (int)myVar;
- (void)setMyVar:(int)newVar;

@end


// MyClass.m
@implementation MyClass

- (int)myVar {
   return myVar;
}

- (void)setMyVar:(int)newVar {
   if (newVar != myVar) {
      myVar = newVar;
   }
}

@end

Таким чином ви змогли отримати та встановити цю змінну екземпляра і з інших класів, використовуючи звичайний синтаксис квадратних дужок для надсилання повідомлень (методи виклику):

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

Оскільки вручну декларування та реалізація кожного методу доступу було дуже дратує, @propertyі вони @synthesizeбули введені для автоматичного генерування методів доступу:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

У результаті набагато чіткіший і коротший код. Методи аксесуара будуть реалізовані для вас, і ви все одно можете використовувати синтаксис дужок, як і раніше. Але крім того, ви також можете використовувати синтаксис крапки для доступу до властивостей:

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

З Xcode 4.4 вам більше не потрібно оголошувати змінну екземпляра, і ви можете також пропустити @synthesize. Якщо ви не декларуєте ivar, компілятор додасть його для вас, і він також генерує методи аксесуара без використання вами @synthesize.

За замовчуванням для автоматично створеного ivar - це ім’я або ваше майно, починаючи з підкреслення. Ви можете змінити створене ім’я ivar, скориставшись@synthesize myVar = iVarName;

// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

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

@interfaceБлок в файлі реалізація насправді є розширення і може використовуватися для передачі методів оголосити , то (більше не потрібно) і (перо) властивості DECLARE. Наприклад, ви можете оголосити readonlyвластивість у своєму заголовку.

@property (nonatomic, readonly) myReadOnlyVar;

і повторно визначити його у вашому файлі реалізації таким чином, readwriteщоб мати змогу встановити його за допомогою синтаксису властивості, а не лише за допомогою прямого доступу до ivar.

Що стосується оголошення змінних повністю поза будь-яким @interfaceабо @implementationблоком, так, це звичайні змінні C і працюють точно так само.


2
чудова відповідь! Також зверніть увагу: stackoverflow.com/questions/9859719 / ...
nycynik

44

Спочатку прочитайте відповідь @ DrummerB. Це хороший огляд того, що потрібно робити, і що ти взагалі повинен робити. Зважаючи на це, до ваших конкретних питань:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

Тут немає жодних фактичних визначень змінних (технічно законно це робити, якщо ви точно знаєте, що робите, але ніколи цього не робіть). Ви можете визначити кілька інших речей:

  • typedefs
  • перерахунки
  • екстерн

Екстернажі виглядають як мінливі декларації, але вони просто обіцяють фактично декларувати це десь в іншому місці. У ObjC їх слід використовувати лише для декларування констант, і, як правило, лише рядкових констант. Наприклад:

extern NSString * const MYSomethingHappenedNotification;

Потім ви у своєму .mфайлі оголосите фактичну константу:

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

Як зазначає DrummerB, це спадщина. Не ставте тут нічого.


// 3) class-specific method / property declarations

@end

Так.


#import "SampleClass.h"

// 4) what goes here?

Зовнішні постійні, як описано вище. Також тут можна перейти до файлу статичних змінних. Це еквівалент змінних класів іншими мовами.


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

Так


@implementation SampleClass
{
    // 6) define ivars
}

Але дуже рідко. Майже завжди ви повинні дозволити clang (Xcode) створювати змінні для вас. Винятки, як правило, стосуються не-ObjC ivars (як об'єкти Core Foundation, і особливо об'єкти C ++, якщо це клас ObjC ++), або ivars, які мають дивну семантику зберігання (наприклад, ivars, які з якихось причин не збігаються з властивістю).


// 7) define methods and synthesize properties from both public and private
//    interfaces

Як правило, ви більше не повинні @synthesize. Clang (Xcode) зробить це за вас, і ви повинні дозволити.

За останні кілька років справи стали значно простішими. Побічним ефектом є те, що зараз існує три різні епохи (Fragile ABI, Non-logi ABI, Non-logible ABI + auto-syntheisze). Тож коли ви бачите старіший код, він може бути трохи заплутаним. Таким чином плутанина, що випливає з простоти: D


Цікаво, але чому ми не повинні чітко узагальнити? Я це роблю, тому що мені здається, що мій код легший для розуміння, особливо коли деякі властивості синтезували аксесуари, а деякі - спеціальні реалізації, оскільки я звик до синтезу. Чи є недоліки в явному синтезі?
Metabble

Проблема з використанням його як документації полягає в тому, що він насправді нічого не документує. Незважаючи на використання синтезу, можливо, ви перекрили один або обидва аксесуари. Немає можливості сказати з лінії синтезу нічого дійсно корисного. Єдине гірше, ніж жодна документація, - це введення в оману документації. Залиште це.
Роб Нап'єр

3
Чому №6 рідкість? Хіба це не найпростіший спосіб отримати приватну змінну?
pfrank

Найпростіший і найкращий спосіб отримати приватну власність - це №5.
Роб Нап'єр

1
@RobNapier Інколи все ще потрібно використовувати @ синтезувати (наприклад, якщо у власності лише переглянуто його аксесуар)
Енді

6

Я також досить нова, тому, сподіваюся, нічого не викручую.

1 & 4: Глобальні змінні у стилі C: вони мають широкий спектр файлів. Різниця між ними полягає в тому, що, оскільки вони мають файл у широкому розмірі, перший буде доступний для всіх, хто імпортує заголовок, а другий - ні.

2: змінні екземпляри. Більшість змінних екземплярів синтезуються та отримуються / встановлюються через аксесуари за допомогою властивостей, оскільки це робить управління пам'яттю приємним та простим, а також дає легко зрозуміти крапкові позначення.

6: Івари впровадження дещо нові. Це добре місце для розміщення приватних ivars, оскільки ви хочете викрити лише те, що потрібно в загальнодоступному заголовку, але підкласи не успадковують їх AFAIK.

3 та 7: Оголошення публічного методу та властивостей, а потім реалізація.

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


1
Не думайте, що ви щось накрутили :) Кілька коментарів - # 1 & # 4 esp з №4 часто ви бачите статичні змінні. # 1 часто ви бачите вказане зовнішнє сховище, а потім фактичне сховище, виділене в №4. # 2) лише зазвичай, якщо підклас потрібен з будь-якої причини. №5 більше не потрібно пересилати оголошення приватних методів.
Карл Вейзей,

Так, я просто перевірив подачу декларації сам. Він використовувався для попередження, якщо один приватний метод викликав інший, який був визначений після нього без прямого оголошення, так? Я був свого роду здивований, коли це не попередило мене.
Metabble

Так, це нова частина компілятора. Останнім часом вони дуже багато зробили.
Карл Вейзей

6

Це приклад усіх видів змінних, задекларованих у Objective-C. Назва змінної вказує на її доступ.

Файл: Animal.h

@interface Animal : NSObject
{
    NSObject *iProtected;
@package
    NSObject *iPackage;
@private
    NSObject *iPrivate;
@protected
    NSObject *iProtected2; // default access. Only visible to subclasses.
@public
    NSObject *iPublic;
}

@property (nonatomic,strong) NSObject *iPublic2;

@end

Файл: Animal.m

#import "Animal.h"

// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
    NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end

@implementation Animal {
@public
    NSString *iNotVisible3;
}

-(id) init {
    self = [super init];
    if (self){
        iProtected  = @"iProtected";
        iPackage    = @"iPackage";
        iPrivate    = @"iPrivate";
        iProtected2 = @"iProtected2";
        iPublic     = @"iPublic";
        _iPublic2    = @"iPublic2";

        iNotVisible   = @"iNotVisible";
        _iNotVisible2 = @"iNotVisible2";
        iNotVisible3  = @"iNotVisible3";
    }
    return self;
}

@end

Зауважте, що змінні iNotVisible не відображаються в жодному іншому класі. Це проблема видимості, тому оголосити їх @propertyабо @publicне змінити.

Всередині конструктора є хорошою практикою отримати доступ до змінних, оголошених за @propertyдопомогою підкреслення замість цього, selfщоб уникнути побічних ефектів.

Спробуємо отримати доступ до змінних.

Файл: Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

Файл: Cow.m

#import "Cow.h"
#include <objc/runtime.h>

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        iProtected    = @"iProtected";
        iPackage      = @"iPackage";
        //iPrivate    = @"iPrivate"; // compiler error: variable is private
        iProtected2   = @"iProtected2";
        iPublic       = @"iPublic";
        self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private

        //iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
        //_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
        //iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
    }
    return self;
}
@end

Ми все ще можемо отримати доступ до невидимих ​​змінних, використовуючи час виконання.

Файл: Cow.m (частина 2)

@implementation Cow(blindAcess)

- (void) setIvar:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    object_setIvar(self, ivar, value);
}

- (id) getIvar:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    id thing = object_getIvar(self, ivar);
    return thing;
}

-(void) blindAccess {
    [self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
    [self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
    [self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
    NSLog(@"\n%@ \n%@ \n%@",
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

Спробуємо отримати доступ до не видимих ​​змінних.

Файл: main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        [cow performSelector:@selector(blindAccess)];
    }
}

Це відбитки

iMadeVisible 
iMadeVisible2 
iMadeVisible3

Зауважте, що мені вдалося отримати доступ до резервного ivar, _iNotVisible2який є приватним для підкласу. У Objective-C всі змінні можуть бути прочитані або встановлені, навіть ті, що позначені @private, без винятку.

Я не включав асоційовані об'єкти чи змінні С, оскільки це різні птахи. Що стосується змінних C, то будь-яка змінна, визначена за межами, @interface X{}або @implementation X{}це змінна C з областю файлу та статичним зберіганням.

Я не обговорював атрибути управління пам’яттю, ані атрибути для читання / перезапису, атрибути / задачі.

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