Jak zadeklarować właściwości na poziomie klasy w Objective-C?

205

Może to oczywiste, ale nie wiem, jak zadeklarować właściwości klasy w Objective-C.

Muszę buforować słownik dla każdej klasy i zastanawiać się, jak umieścić go w klasie.

mamcx
źródło

Odpowiedzi:

190

właściwości mają określone znaczenie w Objective-C, ale myślę, że masz na myśli coś, co jest równoważne zmiennej statycznej? Np. Tylko jedna instancja dla wszystkich rodzajów Foo?

Aby zadeklarować funkcje klas w Objective-C, używasz przedrostka + zamiast - aby twoja implementacja wyglądała mniej więcej tak:

// Foo.h
@interface Foo {
}

+ (NSDictionary *)dictionary;

// Foo.m
+ (NSDictionary *)dictionary {
  static NSDictionary *fooDict = nil;
  if (fooDict == nil) {
    // create dict
  }
  return fooDict;
}
Andrew Grant
źródło
23
Czy to jest poprawne? Czy ustawienie fooDict na zero jako pierwszy wiersz metody słownikowej zawsze powoduje, że słownik jest odtwarzany za każdym razem?
PapillonUK
59
Linia statyczna NSDictionary * fooDict = nil; zostanie wykonany tylko raz! Nawet w metodzie wywoływanej wielokrotnie deklaracja (a także w tym przykładzie inicjalizacja) słowem kluczowym static zostanie zignorowana, jeśli istnieje zmienna statyczna o tej nazwie.
Binarian
3
@ BenC.R.Leggiero Tak, absolutnie. .Składnia -accessor nie jest związana z właściwościami w Objective-C, to po prostu skompilowane w skrótu dla każdej metody, która zwraca coś bez podejmowania jakichkolwiek args. W takim przypadku wolałbym to - osobiście wolę .składnię dla każdego użycia, w którym kod klienta zamierza coś uzyskać, nie wykonywać akcji (nawet jeśli kod implementacyjny może utworzyć coś raz lub wykonać działania niepożądane) . Intensywne użycie .składni powoduje również, że kod jest bardziej czytelny: obecność […]s oznacza, że ​​coś znaczącego jest robione, gdy pobieranie używa .składni zamiast tego.
Slipp D. Thompson
4
Spójrz na odpowiedź Alexa Nolasco, właściwości klasy są dostępne od wydania Xcode 8: stackoverflow.com/a/37849467/6666611
n3wbie
112

Korzystam z tego rozwiązania:

@interface Model
+ (int) value;
+ (void) setValue:(int)val;
@end

@implementation Model
static int value;
+ (int) value
{ @synchronized(self) { return value; } }
+ (void) setValue:(int)val
{ @synchronized(self) { value = val; } }
@end

Okazało się, że jest niezwykle przydatny jako zamiennik wzoru Singleton.

Aby z niego skorzystać, po prostu uzyskaj dostęp do swoich danych za pomocą notacji kropkowej:

Model.value = 1;
NSLog(@"%d = value", Model.value);
spooki
źródło
3
To jest spoko. Ale co u licha selfoznacza wewnątrz metody klasowej?
Todd Lehman,
8
@ToddLehman selfto obiekt, który otrzymał wiadomość . A ponieważ klasy są również obiektami , w tym przypadku selfoznaczaModel
spooki
6
Dlaczego miałby być getter @synchronized?
Matt Kantor
1
To naprawdę fajne, że to działa. Że możesz zapisać swoje własne właściwości na poziomie klasy, które działają dokładnie tak, jak prawdziwe. Wydaje mi się, że zsynchronizowanie siebie jest równoznaczne z użyciem „atomowej” w deklaracji właściwości i może zostać pominięte, jeśli chcesz mieć wersję „nieatomową”? Zastanowiłbym się również nad nazwami zmiennych bazowych „_”, ponieważ jest to domyślna nazwa firmy Apple, a także poprawić czytelność, ponieważ zwracanie / ustawianie self.value z getter / setter powoduje nieskończoną rekurencję. Moim zdaniem jest to właściwa odpowiedź.
Peter Segerblom
3
Fajnie, ale ... 10 linii kodu tylko po to, aby utworzyć 1 element statyczny? co za hack. Apple powinno po prostu włączyć tę funkcję.
John Henckel,
92

Jak widać w WWDC 2016 / XCode 8 ( co nowego w sesji LLVM @ 5: 05). Właściwości klasy można zadeklarować w następujący sposób

@interface MyType : NSObject
@property (class) NSString *someString;
@end

NSLog(@"format string %@", MyType.someString);

Zauważ, że właściwości klasy nigdy nie są syntetyzowane

@implementation
static NSString * _someString;
+ (NSString *)someString { return _someString; }
+ (void)setSomeString:(NSString *)newString { _someString = newString; }
@end
Alex Nolasco
źródło
8
Być może należy wyjaśnić, że jest to tylko cukier w deklaracjach akcesorów. Jak zauważyłeś, właściwość nie jest syntetyzowana: staticzmienna ( ) musi być nadal zadeklarowana i używana, a metody jawnie zaimplementowane, tak jak poprzednio. Składnia kropkowa już wcześniej działała. W sumie brzmi to jak coś większego niż jest w rzeczywistości.
jscs
6
Najważniejsze jest to, że możesz uzyskać dostęp do singletonów z kodu Swift bez użycia (), a sufiks typu jest usuwany zgodnie z konwencją. Np. XYZMyClass.shared (Swift 3) zamiast XYZMyClass.sharedMyClass ()
Ryan
To nie wygląda na bezpieczne dla wątków. Jeśli można to zmutować z różnych wątków w twoim kodzie, upewnię się, że poradzisz sobie z potencjalnymi warunkami wyścigu, które to stworzy.
smileBot
Ładny, czysty interfejs do konsumowania klas, ale wciąż jest to mnóstwo pracy. Wypróbowałem to ze statycznym blokiem i nie było to zbyt zabawne. W rzeczywistości użycie statycznego było znacznie łatwiejsze.
Departamento B,
1
implementacja może być łatwo ulepszona dla bezpiecznego wątku „singletonu” za pomocą tokena dispatch_once - ale w przeciwnym razie rozwiązanie poprawnie pokazuje odpowiedź
Motti Shneor
63

Jeśli szukasz ekwiwalentu klasy @property, odpowiedź brzmi: „nie ma czegoś takiego”. Ale pamiętajcie @property, w każdym razie to tylko cukier syntaktyczny; po prostu tworzy odpowiednio nazwane metody obiektowe.

Chcesz stworzyć metody klasowe, które uzyskują dostęp do zmiennych statycznych, które, jak powiedzieli inni, mają tylko nieco inną składnię.

Jim Puls
źródło
Nawet myśl, że właściwości są składniowe. Byłoby miło móc używać składni kropkowej dla rzeczy takich jak MyClass.class zamiast [MyClass class].
Zaky German
4
@ZakyGerman Możesz! UIDevice.currentDevice.identifierForVendorpracuje dla mnie.
tc.
1
@tc. Dziękuję Ci! Wypełnianie głupie teraz. Z jakiegoś powodu byłem przekonany, że próbowałem tego w przeszłości, ale to nie zadziałało. Czy to nowa funkcja przez przypadek?
Zaky niemiecki
1
@ZakyGerman To działało przez co najmniej rok lub dwa dla metod klasowych. Wierzę, że zawsze działało na przykład metody, jeśli metody pobierające / ustawiające mają oczekiwane typy.
tc.
21

Oto bezpieczny sposób na zrobienie tego:

// Foo.h
@interface Foo {
}

+(NSDictionary*) dictionary;

// Foo.m
+(NSDictionary*) dictionary
{
  static NSDictionary* fooDict = nil;

  static dispatch_once_t oncePredicate;

  dispatch_once(&oncePredicate, ^{
        // create dict
    });

  return fooDict;
}

Te zmiany zapewniają, że fooDict jest tworzony tylko raz.

Z dokumentacji Apple : „dispatch_once - Wykonuje obiekt bloku raz i tylko raz na czas życia aplikacji”.

Quentin
źródło
3
Czy kod dispatch_once nie jest nieistotny, ponieważ statyczny NSDictionary może zostać zainicjowany w pierwszym wierszu metody słownika + (NSDictionary *), a ponieważ jest statyczny, i tak zostanie zainicjowany tylko raz?
jcpennypincher
@jcpennypincher Próba zainicjować słownika na tej samej linii jako statyczny deklaracja daje następujący błąd kompilatora: Initializer element is not a compile-time constant.
George WS
@GeorgeWS Ten błąd pojawia się tylko dlatego, że próbujesz zainicjować go do wyniku funkcji (przydzielanie i inicjowanie są funkcjami). Jeśli zainicjujesz go do zera, a następnie dodasz if (obj == zero) i zainicjujesz tam, wszystko będzie dobrze.
Rob
1
Rob, to nie jest bezpieczne dla wątków. Przedstawiony tutaj kod jest najlepszy.
Ian Ollmann
Najbardziej podoba mi się ta odpowiedź, ponieważ jest kompletna i bezpieczna dla wątków - jednak lepiej radzi sobie tylko z implementacją, podczas gdy pytanie op dotyczyło deklaracji właściwości klasy - nie ich implementacji (co nie ma nic wspólnego z implementacją). W każdym razie dzięki.
Motti Shneor
11

Począwszy od Xcode 8 Objective-C obsługuje teraz właściwości klas:

@interface MyClass : NSObject
@property (class, nonatomic, assign, readonly) NSUUID* identifier;
@end

Ponieważ właściwości klas nigdy nie są syntetyzowane, musisz napisać własną implementację.

@implementation MyClass
static NSUUID*_identifier = nil;

+ (NSUUID *)identifier {
  if (_identifier == nil) {
    _identifier = [[NSUUID alloc] init];
  }
  return _identifier;
}
@end

Dostęp do właściwości klasy uzyskuje się za pomocą normalnej składni kropek na nazwie klasy:

MyClass.identifier;
berbie
źródło
7

Właściwości mają wartości tylko w obiektach, a nie w klasach.

Jeśli chcesz zapisać coś dla wszystkich obiektów klasy, musisz użyć zmiennej globalnej. Możesz to ukryć, deklarując staticw pliku implementacji.

Możesz także rozważyć użycie specyficznych relacji między swoimi obiektami: przypisujesz rolę wzorca konkretnemu obiektowi swojej klasy i łączysz inne obiekty z tym wzorcem. Mistrz zachowa słownik jako prostą właściwość. Myślę o drzewie takim jak to używane do hierarchii widoków w aplikacjach Cocoa.

Inną opcją jest utworzenie obiektu dedykowanej klasy, która składa się zarówno ze słownika „klasy”, jak i zestawu wszystkich obiektów związanych z tym słownikiem. To jest jak NSAutoreleasePoolw kakao.

mouviciel
źródło
7

Począwszy od Xcode 8, możesz użyć atrybutu właściwości class, na który odpowiedział Berbie.

Jednak w implementacji należy zdefiniować moduł pobierający i ustawiający klasę dla właściwości klasy za pomocą zmiennej statycznej zamiast iVar.

Próbka. H

@interface Sample: NSObject
@property (class, retain) Sample *sharedSample;
@end

Próbka.m

@implementation Sample
static Sample *_sharedSample;
+ ( Sample *)sharedSample {
   if (_sharedSample==nil) {
      [Sample setSharedSample:_sharedSample];
   }
   return _sharedSample;
}

+ (void)setSharedSample:(Sample *)sample {
   _sharedSample = [[Sample alloc]init];
}
@end
Jean-Marie D.
źródło
2

Jeśli masz wiele właściwości na poziomie klasy, wzorzec singletonu może być w porządku. Coś takiego:

// Foo.h
@interface Foo

+ (Foo *)singleton;

@property 1 ...
@property 2 ...
@property 3 ...

@end

I

// Foo.m

#import "Foo.h"

@implementation Foo

static Foo *_singleton = nil;

+ (Foo *)singleton {
    if (_singleton == nil) _singleton = [[Foo alloc] init];

    return _singleton;
}

@synthesize property1;
@synthesize property2;
@synthesise property3;

@end

Teraz uzyskaj dostęp do swoich właściwości na poziomie klasy, takich jak to:

[Foo singleton].property1 = value;
value = [Foo singleton].property2;
Pedro Borges
źródło
4
Ta implementacja singletona nie jest bezpieczna dla wątków, nie używaj jej
klefevre
1
Oczywiście nie jest to bezpieczne dla wątków, jesteś pierwszą osobą, która wspomina o bezpieczeństwie wątków i domyślnie nie ma sensu przeciążanie bezpieczeństwa wątków w kontekstach nie bezpiecznych dla wątków, tj. W jednym wątku.
Pedro Borges
Byłoby to dość łatwe w użyciu dispatch_oncetutaj.
Ian MacDonald
Organizacja producentów chciała odpowiedzi po stronie deklaracji, a nie implementacji - a nawet sugerowana implementacja jest niekompletna (nie jest bezpieczna dla wątków).
Motti Shneor
-3

[Wypróbuj to rozwiązanie, to proste] Możesz utworzyć zmienną statyczną w klasie Swift, a następnie wywołać ją z dowolnej klasy Objective-C.

jawad
źródło
1
OP nie zapytał, jak utworzyć właściwość statyczną w Swift, to nie rozwiązuje jego problemu.
Nathan F.
A raczej rozwiązał problem, ale nie odpowiedział na pytanie. Nie jestem pewien, czy to zasługuje i głosuj w dół ...
AmitaiB