Объявление/определение местоположений переменных в 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: почему я хотел бы объявлять методы в частных интерфейсах? Мои методы частного класса, похоже, очень просто компилируются без декларации в интерфейсе. Это в основном для удобства чтения?
Спасибо, кучка, ребята!
Ответы
Ответ 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.