Deklaracja/definicja lokalizacji zmiennych w ObjectiveC?
Odkąd zacząłem pracować nad aplikacjami iOS i objective C, byłem naprawdę zaskoczony różnymi lokalizacjami, w których można było deklarować i definiować zmienne. Z jednej strony mamy tradycyjne podejście C, z drugiej mamy nowe dyrektywy ObjectiveC, które dodają do tego oo. Czy moglibyście mi pomóc zrozumieć najlepsze praktyki i sytuacje, w których chciałbym użyć tych lokalizacji dla moich zmiennych i być może poprawić moje obecne zrozumienie?
Oto próbka Klasa (.h i .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
I
#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
- moje rozumienie 1 i 4 jest takie, że są to deklaracje i definicje oparte na plikach w stylu C, które nie mają żadnego zrozumienia pojęcia klasy i dlatego muszą być używane dokładnie tak, jak byłyby używane w C. widziałem je wcześniej używane do implementacji singletonów opartych na zmiennych statycznych. Czy brakuje mi innych wygodnych zastosowań?
- moja opinia z pracy z iOS jest taka, że Ivary zostały całkowicie stopniowane poza dyrektywą @ synthesize i dlatego może być w większości ignorowany. O to chodzi?
- odnośnie 5: Dlaczego miałbym chcieć deklarować metody w prywatnych interfejsach? Moje prywatne metody klasowe wydają się dobrze kompilować bez deklaracji w interfejsie. Czy chodzi głównie o czytelność?
Wielkie dzięki, ludzie!
4 answers
Rozumiem Twoje zamieszanie. Zwłaszcza, że Ostatnie aktualizacje Xcode i Nowy kompilator LLVM zmienił sposób Ivars i właściwości mogą być deklarowane.
Przed" nowoczesnym "Objective-C (w" starym " Obj-C 2.0) nie miałeś zbyt dużego wyboru. Zmienne instancji używane do zadeklarowania w nagłówku między nawiasami klamrowymi { }
:
// MyClass.h
@interface MyClass : NSObject {
int myVar;
}
@end
Można było uzyskać dostęp do tych zmiennych tylko w swojej implementacji, ale nie z innych klas. Aby to zrobić, trzeba było zadeklarować metody accessor, które wyglądają mniej więcej tak:
// 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
W ten sposób udało Ci się uzyskać i ustawić zmienną instancji z innych klas, używając zwykłej składni nawiasu kwadratowego do wysyłania wiadomości (metody wywołania):
// OtherClass.m
int v = [myClass myVar]; // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];
Ponieważ ręczne deklarowanie i implementowanie każdej metody accessora było dość irytujące, @property
i @synthesize
zostały wprowadzone do automatycznego generowania metod accessora:
// MyClass.h
@interface MyClass : NSObject {
int myVar;
}
@property (nonatomic) int myVar;
@end
// MyClass.m
@implementation MyClass
@synthesize myVar;
@end
Wynik jest znacznie jaśniejszy i krótszy. Metody dostępu zostanie zaimplementowany dla Ciebie i nadal możesz używać składni nawiasu tak jak poprzednio. Ale dodatkowo, możesz również użyć składni kropki, aby uzyskać dostęp do właściwości:
// OtherClass.m
int v = myClass.myVar; // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;
Od Xcode 4.4 nie musisz już samodzielnie deklarować zmiennej instancji i możesz też pominąć @synthesize
. Jeśli nie zadeklarujesz ivar, kompilator doda go za Ciebie, a także wygeneruje metody accessora bez konieczności używania @synthesize
.
Domyślną nazwą automatycznie generowanego Ivara jest nazwa lub Twoja nieruchomość zaczynająca się od podkreślenia. Możesz zmienić nazwę wygenerowanego Ivara używając @synthesize myVar = iVarName;
// MyClass.h
@interface MyClass : NSObject
@property (nonatomic) int myVar;
@end
// MyClass.m
@implementation MyClass
@end
Będzie to działać dokładnie tak, jak powyższy kod. Ze względu na kompatybilność nadal możesz zadeklarować ivars w nagłówku. Ale ponieważ jedynym powodem, dla którego chcesz to zrobić (a nie zadeklarować właściwość) jest utworzenie zmiennej prywatnej, możesz teraz to zrobić również w pliku implementacji i jest to preferowany sposób.
An @interface
blok w realizacji plik jest w rzeczywistości rozszerzeniem i może być użyty do przekazania metod deklaracji (nie jest już potrzebny) oraz do (ponownego)zadeklarowania właściwości. Możesz na przykład zadeklarować właściwość readonly
w nagłówku.
@property (nonatomic, readonly) myReadOnlyVar;
I ponownie zapisuje go w pliku implementacji jako readwrite
, aby móc ustawić go przy użyciu składni właściwości, a nie tylko poprzez bezpośredni dostęp do ivar.
Jeśli chodzi o deklarowanie zmiennych całkowicie poza dowolnym @interface
lub @implementation
blokiem, to tak są to zwykłe zmienne C i działają dokładnie tak samo.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-14 17:56:41
Najpierw przeczytaj odpowiedź @ DrummerB. To dobry przegląd dlaczego i co powinieneś ogólnie zrobić. Mając to na uwadze, do Twoich konkretnych pytań:
#import <Foundation/Foundation.h>
// 1) What do I declare here?
Nie ma tu definicji zmiennych (technicznie jest to legalne, jeśli dokładnie wiesz, co robisz, ale nigdy tego nie rób). Można zdefiniować kilka innych rodzajów rzeczy:
- typdefs
- enums
- externs
Externs wyglądają jak deklaracje zmiennych, ale są tylko obiecaj, że będziesz to ogłaszał gdzie indziej. W ObjC powinny one być używane tylko do deklarowania stałych i ogólnie tylko stałych łańcuchowych. Na przykład:
extern NSString * const MYSomethingHappenedNotification;
Następnie w pliku .m
zadeklarujesz rzeczywistą stałą:
NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";
@interface SampleClass : NSObject
{
// 2) ivar declarations
// Pretty much never used?
}
Jak zauważył DrummerB, to jest dziedzictwo. Nic tu nie wkładaj.
// 3) class-specific method / property declarations
@end
Tak.
#import "SampleClass.h"
// 4) what goes here?
Stałe zewnętrzne, jak opisano powyżej. Również plik statyczne zmienne mogą przejść tutaj. Są to równoważne zmiennych klas w innych językach.
@interface SampleClass()
// 5) private interface, can define private methods and properties here
@end
Yep
@implementation SampleClass
{
// 6) define ivars
}
Ale bardzo rzadko. Prawie zawsze powinieneś zezwolić clang (Xcode) na tworzenie zmiennych dla Ciebie. Wyjątki dotyczą zazwyczaj innych niż ObjC ivars (jak obiekty Core Foundation, a zwłaszcza obiekty C++, jeśli jest to klasa ObjC++), lub ivars, które mają dziwną semantykę pamięci masowej (jak ivars, które z jakiegoś powodu nie pasują do właściwości).
// 7) define methods and synthesize properties from both public and private
// interfaces
Ogólnie nie powinieneś @ syntetyzować już nie. Clang (Xcode) zrobi to za Ciebie i powinieneś na to pozwolić.
W ciągu ostatnich kilku lat sprawy stały się znacznie prostsze. Efektem ubocznym jest to, że obecnie istnieją trzy różne epoki (kruche ABI, nietrwałe ABI, nietrwałe ABI + auto-synteisze). Więc kiedy widzisz starszy kod, może to być trochę mylące. Dlatego zamieszanie wynikające z prostoty: dWarning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-28 01:51:29
Ja też jestem całkiem nowy, więc mam nadzieję, że niczego nie spieprzę.
1 & 4: zmienne globalne w stylu C: mają szeroki zakres plików. Różnica między tymi dwoma polega na tym, że ponieważ są szerokie, pierwszy będzie dostępny dla każdego importującego nagłówek, podczas gdy drugi nie jest.
2: zmienne instancji. Większość zmiennych instancji jest syntetyzowana i pobierana/ustawiana za pomocą accesorów przy użyciu właściwości, ponieważ sprawia, że zarządzanie pamięcią jest przyjemne i proste, a także daje łatwy do zrozumienia zapis kropek.
6: implementacja ivars jest nieco Nowa. To dobre miejsce, aby umieścić prywatne Ivary, ponieważ chcesz ujawnić tylko to, co jest potrzebne w nagłówku publicznym, ale podklasy nie dziedziczą ich AFAIK.
3 & 7: Public method and property declarations, then implementations.
5: prywatny interfejs. Zawsze używam prywatnych interfejsów, kiedy tylko mogę, aby utrzymać porządek i stworzyć coś w rodzaju efektu czarnej skrzynki. Jeśli nie muszą o tym wiedzieć, umieść to tam. Robię to również dla czytelności, Nie wiem, czy są jakieś inne powody.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-28 01:30:28
Jest to przykład wszystkich typów zmiennych zadeklarowanych w Objective-C. nazwa zmiennej wskazuje na jej dostęp.
Plik: 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
Plik: 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
Zauważ, że zmienne iNotVisible nie są widoczne z żadnej innej klasy. Jest to problem z widocznością, więc deklarowanie ich za pomocą @property
lub @public
nie zmienia tego.
Wewnątrz konstruktora dobrą praktyką jest dostęp do zmiennych zadeklarowanych przez @property
używając podkreślenia zamiast self
, aby uniknąć side efekty.
Plik: Krowa.h
#import "Animal.h"
@interface Cow : Animal
@end
Plik: Krowa.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
Nadal możemy uzyskać dostęp do niewidocznych zmiennych za pomocą runtime.
Plik: Krowa.m (Część 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
Spróbujmy uzyskać dostęp do niewidocznych zmiennych.
Plik: main.m
#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
Cow *cow = [Cow new];
[cow performSelector:@selector(blindAccess)];
}
}
To wydruki
iMadeVisible
iMadeVisible2
iMadeVisible3
Zauważ, że byłem w stanie uzyskać dostęp do kopii zapasowej ivar _iNotVisible2
, która jest prywatna dla podklasy. W Objective-C wszystkie zmienne można odczytać lub ustawić, nawet te, które są oznaczone @private
, BEZ WYJĄTKÓW.
Nie uwzględniłem powiązanych obiektów ani zmiennych C, ponieważ są to różne ptaki. Podobnie jak w przypadku zmiennych C, każda zmienna zdefiniowana poza @interface X{}
lub @implementation X{}
jest zmienną C z zakresem pliku i statycznym przechowywaniem.
Nie omawiałem atrybutów zarządzania pamięcią, ani readonly / readwrite, Getter / setter.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-16 02:26:35