Объявление / определение местоположения переменных в ObjectiveC?


С тех пор, как я начал работать над приложениями iOS и objective C, я был действительно озадачен различными местами, где можно было бы объявлять и определять переменные. С одной стороны, у нас есть традиционный подход C, с другой-у нас есть новые директивы ObjectiveC, которые добавляют OO поверх этого. Не могли бы вы, ребята, помочь мне понять лучшую практику и ситуации, когда я хотел бы использовать эти места для своих переменных и, возможно, исправить мое нынешнее понимание?

здесь пример класса (.h и .м):

#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, которые не имеют никакого представления о концепции класса и, следовательно, должны использоваться именно так, как они будут использоваться в C. Я видел, как они использовались для реализации статических переменных на основе синглетов раньше. Есть ли другие удобные способы использования, которые мне не хватает?
  • мое мнение от работы с iOS заключается в том, что ivars были полностью выведены за пределы директивы @synthesize и, таким образом, могут быть в основном проигнорированы. Это так?
  • относительно 5: Почему я когда-нибудь захочу объявить методы в частных интерфейсах? Мои частные методы класса, похоже, компилируются просто отлично без объявления в интерфейсе. Это в основном для удобства чтения?

Спасибо, ребята!

4 103

4 ответа:

я могу понять вашу растерянность. Тем более, что последние обновления 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 в заголовке. Но поскольку единственная причина, по которой вы хотите это сделать (а не объявлять свойство), - это создать закрытую переменную, теперь вы можете сделать это в файл реализации, а также и это предпочтительный способ.

An @interface блок в файле реализации-это на самом деле расширение и может использоваться для переадресации методов объявления (больше не требуется) и для (повторного)объявления свойств. Вы могли бы, например, объявить readonly свойство в заголовке.

@property (nonatomic, readonly) myReadOnlyVar;

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

что касается объявления переменных полностью вне любого @interface или @implementation блок, да это простые переменные C и работают точно так же.

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

#import <Foundation/Foundation.h>

// 1) What do I declare here?

здесь нет фактических определений переменных (это технически законно, если вы точно знаете, что делаете, но никогда не делайте этого). Вы можете определить несколько других видов вещей:

  • typdefs
  • метки
  • экстернов

экстерны выглядят так объявления переменных, но они просто обещание на самом деле объявить его где-то еще. В 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, которые имеют странную семантику хранения (например ивары, которые по какой-то причине не соответствуют свойству).


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

как правило, вы не должны @синтезировать больше. Clang (Xcode) сделает это за вас, и вы должны позволить ему.

за последние несколько лет, все стало значительно проще. Побочным эффектом является то, что в настоящее время существует три разных эпохи (хрупкий ABI, не хрупкий ABI, не хрупкий ABI + auto-syntheisze). Поэтому, когда вы видите старый код, это может быть немного запутанным. Таким образом, путаница, возникающая из простота :Д

Я тоже довольно новый, так что надеюсь не облажаться.

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

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

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

3 & 7: публичные объявления методов и свойств, а затем реализации.

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

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

: Животные.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
: Животные.м
#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 чтобы избежать побочных эффектов.

давайте попробуем получить доступ к переменные.

: Корова.h
#import "Animal.h"
@interface Cow : Animal
@end
: Корова.м
#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

мы все еще можем получить доступ к невидимым переменным с помощью среды выполнения.

: Корова.м (часть 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

давайте попробуем получить доступ к невидимым переменным.

File: main.м

#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, поскольку они разные птицы. Что касается переменных C, то любая переменная, определенная вне @interface X{} или @implementation X{} - это переменная C с областью действия файла и статическим хранилищем.

я не обсуждал памяти атрибуты управления, или только для чтения/readwrite, getter/setter атрибуты.