Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

@property (nonatomic, readonly) myReadOnlyVar;

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

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

Ответ 2

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

#import <Foundation/Foundation.h>

// 1) What do I declare here?

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

  • typdefs
  • перечислений
  • экстернов

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


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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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