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!

Author: Alexandr Kurilin, 2012-09-28

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.

 140
Author: DrummerB,
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: d
 40
Author: Rob Napier,
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: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.

 6
Author: Metabble,
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.

Spróbujmy uzyskać dostęp do zmiennych.

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.

 5
Author: Jano,
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