Deklaracja / definicja lokalizacji zmiennych w ObjectiveC?

113

Odkąd zacząłem pracować nad aplikacjami na iOS i celem C, byłem naprawdę zaskoczony różnymi lokalizacjami, w których można 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 pomóc mi zrozumieć najlepsze praktyki i sytuacje, w których chciałbym użyć tych lokalizacji dla moich zmiennych i być może poprawić swoje obecne rozumienie?

Oto przykładowa 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
  • Rozumiem 1 i 4, że są to deklaracje i definicje oparte na plikach w stylu C, które w ogóle nie rozumieją pojęcia klasy, a zatem muszą być używane dokładnie tak, jak byłyby używane w C. Widziałem je używany do implementowania statycznych singletonów opartych na zmiennych wcześniej. Czy brakuje mi innych wygodnych zastosowań?
  • Moje wnioski z pracy z iOS są takie, że ivars zostały prawie całkowicie wycofane poza dyrektywą @synthesize i dlatego mogą być w większości ignorowane. Czy tak jest?
  • Odnośnie 5: dlaczego miałbym kiedykolwiek chcieć deklarować metody w prywatnych interfejsach? Moje metody klasy prywatnej wydają się dobrze kompilować bez deklaracji w interfejsie. Czy chodzi głównie o czytelność?

Wielkie dzięki, ludzie!

Alexandr Kurilin
źródło

Odpowiedzi:

154

Rozumiem twoje zmieszanie. Zwłaszcza, że ​​ostatnie aktualizacje Xcode i nowy kompilator LLVM zmieniły sposób deklarowania ivars i właściwości.

Przed „nowoczesnym” Objective-C (w „starym” Obj-C 2.0) nie było wielu możliwości wyboru. Zmienne instancji były wcześniej deklarowane w nagłówku między nawiasami klamrowymi { }:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

Możesz uzyskać dostęp do tych zmiennych tylko w swojej implementacji, ale nie z innych klas. Aby to zrobić, należało zadeklarować metody akcesorów, 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 mogłeś pobrać i ustawić tę zmienną instancji również 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 wdrażanie każdej metody akcesorów było dość denerwujące @propertyi @synthesizewprowadzono je, aby automatycznie generować metody akcesorów:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

Rezultatem jest znacznie jaśniejszy i krótszy kod. Metody dostępu zostaną zaimplementowane dla Ciebie i nadal możesz używać składni nawiasów, 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 ją @synthesizerównież pominąć . Jeśli nie zadeklarujesz ivar, kompilator doda go za Ciebie, a także wygeneruje metody akcesorów bez konieczności użycia @synthesize.

Domyślną nazwą dla automatycznie generowanego ivar jest nazwa lub twoja właściwość zaczynająca się od podkreślenia. Możesz zmienić wygenerowaną nazwę ivar za pomocą@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ędów kompatybilności nadal możesz zadeklarować ivars w nagłówku. Ale ponieważ jedynym powodem, dla którego chciałbyś to zrobić (a nie deklarować właściwości) jest utworzenie zmiennej prywatnej, możesz to teraz zrobić również w pliku implementacji i jest to preferowany sposób.

@interfaceBlok w pliku implementacji jest rzeczywiście Extension i może być użyty do przekazania metod Declare (nie potrzeba już) i (re) właściwości deklaracji. Możesz na przykład zadeklarować readonlywłaściwość w swoim nagłówku.

@property (nonatomic, readonly) myReadOnlyVar;

i ponownie zadeklaruj go w pliku implementacji, readwriteaby móc ustawić go przy użyciu składni właściwości, a nie tylko przez bezpośredni dostęp do ivar.

Co do deklarowania zmiennych całkowicie poza jakimkolwiek @interfacelub @implementationbloku, tak to są zwykłe zmienne C i działają dokładnie tak samo.

Perkusista B.
źródło
2
świetna odpowiedź! Uwaga: stackoverflow.com/questions/9859719/…
nycynik
44

Najpierw przeczytaj odpowiedź @ DrummerB. To dobry przegląd powodów i tego, co ogólnie powinieneś robić. Mając to na uwadze, odpowiadając na konkretne pytania:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

Nie ma tu żadnych definicji zmiennych (jest to technicznie legalne, jeśli wiesz dokładnie, co robisz, ale nigdy tego nie rób). Możesz zdefiniować kilka innych rodzajów rzeczy:

  • typy czcionek
  • wyliczenia
  • zewnętrzne

Extern wyglądają jak deklaracje zmiennych, ale są po prostu obietnicą zadeklarowania ich w innym miejscu. W ObjC powinny 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 swoim .mpliku zadeklarowałbyś rzeczywistą stałą:

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

Jak zauważył DrummerB, jest to dziedzictwo. Nie umieszczaj niczego tutaj.


// 3) class-specific method / property declarations

@end

Tak.


#import "SampleClass.h"

// 4) what goes here?

Stałe zewnętrzne, jak opisano powyżej. Tutaj można również zapisać zmienne statyczne. Są to odpowiedniki zmiennych klasowych w innych językach.


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

tak


@implementation SampleClass
{
    // 6) define ivars
}

Ale bardzo rzadko. Prawie zawsze powinieneś pozwolić clang (Xcode) na tworzenie zmiennych za Ciebie. Wyjątki dotyczą zwykle modułów ivar innych niż ObjC (takich jak obiekty Core Foundation, a zwłaszcza obiekty C ++, jeśli jest to klasa ObjC ++) lub ivary, które mają dziwną semantykę pamięci (jak ivary, które z jakiegoś powodu nie pasują do właściwości).


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

Generalnie nie powinieneś już @synthesize. Clang (Xcode) zrobi to za Ciebie i powinieneś na to pozwolić.

W ciągu ostatnich kilku lat sprawy znacznie się uprościły. Efektem ubocznym jest to, że istnieją teraz trzy różne epoki (Fragile ABI, Non-fragile ABI, Non-fragile ABI + auto-syntheisze). Więc kiedy zobaczysz starszy kod, może to być trochę zagmatwane. Stąd zamieszanie wynikające z prostoty: D.

Rob Napier
źródło
Zastanawiam się, ale dlaczego nie mielibyśmy jawnie syntetyzować? Robię to, ponieważ uważam, że mój kod jest łatwiejszy do zrozumienia, zwłaszcza gdy niektóre właściwości mają zsyntetyzowane metody dostępu, a niektóre mają niestandardowe implementacje, ponieważ jestem przyzwyczajony do syntetyzowania. Czy są jakieś wady jednoznacznej syntezy?
Metabble
Problem z używaniem go jako dokumentacji polega na tym, że tak naprawdę niczego nie dokumentuje. Pomimo używania syntetyzowania, być może przesłoniłeś jeden lub oba akcesory. Nie ma sposobu, aby stwierdzić na podstawie linii syntetyzowania coś naprawdę użytecznego. Jedyną rzeczą gorszą niż brak dokumentacji jest myląca dokumentacja. Zostaw to.
Rob Napier
3
Dlaczego numer 6 jest rzadki? Czy nie jest to najłatwiejszy sposób na uzyskanie zmiennej prywatnej?
pfrank
Najłatwiejszym i najlepszym sposobem na zdobycie własności prywatnej jest # 5.
Rob Napier,
1
@RobNapier Nadal konieczne jest czasami użycie @ synthesize (np. Jeśli właściwość jest tylko do odczytu, ma nadpisany akcesor)
Andy
6

Jestem też całkiem nowy, więc mam nadzieję, że niczego nie schrzanię.

1 i 4: Zmienne globalne w stylu C: mają zakres obejmujący cały plik. Różnica między nimi polega na tym, że ponieważ mają one szerokość pliku, pierwsza będzie dostępna dla każdego, kto importuje nagłówek, a druga nie.

2: zmienne instancji. Większość zmiennych instancji jest syntetyzowana i pobierana / ustawiana za pomocą metod dostępu przy użyciu właściwości, ponieważ sprawia, że ​​zarządzanie pamięcią jest przyjemne i proste, a także zapewnia łatwą do zrozumienia notację kropkową.

6: Ivars implementacji są nieco nowe. To dobre miejsce na umieszczenie prywatnych ivarów, ponieważ chcesz ujawnić tylko to, co jest potrzebne w publicznym nagłówku, ale podklasy nie dziedziczą ich AFAIK.

3 i 7: Publiczne deklaracje metod i właściwości, a następnie implementacje.

5: Prywatny interfejs. Zawsze używam prywatnych interfejsów, kiedy tylko mogę, aby zachować porządek i stworzyć rodzaj 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.

Metabble
źródło
1
Nie myśl, że coś schrzaniłeś :) Kilka komentarzy - # 1 & # 4 esp z # 4 często widzisz statyczne zmienne przechowywania. # 1 często zobaczysz określoną pamięć zewnętrzną, a następnie rzeczywistą pamięć przydzieloną w # 4. # 2) zwykle tylko wtedy, gdy podklasa tego potrzebuje z jakiegokolwiek powodu. # 5 Nie trzeba już przekazywać deklaracji prywatnych metod.
Carl Veazey,
Tak, właśnie sprawdziłem deklarację do przodu. Kiedyś dawał ostrzeżenie, jeśli jedna metoda prywatna wywoływała inną, która została zdefiniowana po niej bez deklaracji przekazania, prawda? Byłem trochę zaskoczony, kiedy mnie to nie ostrzegło.
Metabble,
Tak, to nowa część kompilatora. Ostatnio naprawdę zrobili wiele postępów.
Carl Veazey,
6

To jest przykład wszystkich rodzajó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

Zwróć uwagę, że zmienne iNotVisible nie są widoczne z żadnej innej klasy. Jest to problem z widocznością, więc zadeklarowanie ich z @propertylub @publicnie zmienia tego.

Wewnątrz konstruktora dobrą praktyką jest uzyskiwanie dostępu do zmiennych zadeklarowanych za @propertypomocą podkreślenia zamiast tego, selfaby uniknąć skutków ubocznych.

Spróbujmy uzyskać dostęp do zmiennych.

Plik: Cow.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ą środowiska uruchomieniowego.

Plik: Cow.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 drukuje

iMadeVisible 
iMadeVisible2 
iMadeVisible3

Zauważ, że udało mi się uzyskać dostęp do zaplecza ivar, _iNotVisible2które jest prywatne dla podklasy. W Objective-C można odczytać lub ustawić wszystkie zmienne, nawet te, które są zaznaczone @private, bez wyjątków.

Nie uwzględniłem powiązanych obiektów ani zmiennych C, ponieważ są to różne ptaki. Jeśli chodzi o zmienne C, każda zmienna zdefiniowana na zewnątrz @interface X{}lub @implementation X{}jest zmienną C o zasięgu pliku i pamięci statycznej.

Nie omawiałem atrybutów zarządzania pamięcią ani atrybutów tylko do odczytu / odczytu, pobierania / ustawiania.

Jano
źródło