Jak powinien wyglądać mój singleton Objective-C? [Zamknięte]


334

Moja metoda akcesorium singleton jest zwykle pewnym wariantem:

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    @synchronized(self)
    {
        if (gInstance == NULL)
            gInstance = [[self alloc] init];
    }

    return(gInstance);
}

Co mogę zrobić, aby to poprawić?


27
To, co masz, jest w porządku, ale możesz przenieść deklarację zmiennej globalnej do metody + wystąpienia (jedyne miejsce, w którym trzeba jej użyć, chyba że zezwalasz również na jej ustawienie) i użyj nazwy takiej jak + defaultMyClass lub + sharedMyClass dla Twojej metody. + instancja nie ujawnia intencji.
Chris Hanson,

Ponieważ jest mało prawdopodobne, że „odpowiedź” na to pytanie zmieni się w najbliższym czasie, kładę na to historyczną blokadę. Dwa powody 1) Dużo wyświetleń, głosów i dobra treść 2) Aby zapobiec jo-jo otwartych / zamkniętych. To było świetne pytanie jak na swoje czasy, ale pytania tego typu nie są odpowiednie dla przepełnienia stosu. Mamy teraz przegląd kodu do sprawdzania działającego kodu. Przenieś całą dyskusję na to pytanie do tego meta pytania .
George Stocker

Odpowiedzi:


207

Inną opcją jest użycie +(void)initializemetody. Z dokumentacji:

Środowisko wykonawcze wysyła initializedo każdej klasy w programie dokładnie jeden raz tuż przed klasą lub dowolną klasą, która z niej dziedziczy, wysyłana jest pierwsza wiadomość z poziomu programu. (W związku z tym nigdy nie można wywołać metody, jeśli klasa nie jest używana.) Środowisko wykonawcze wysyła initializekomunikat do klas w sposób bezpieczny dla wątków. Nadklasy otrzymują ten komunikat przed podklasami.

Możesz więc zrobić coś podobnego do tego:

static MySingleton *sharedSingleton;

+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        sharedSingleton = [[MySingleton alloc] init];
    }
}

7
Jeśli środowisko wykonawcze wywoła to tylko raz, co robi BOOL? Czy jest to środek ostrożności na wypadek, gdyby ktoś wywołał tę funkcję wprost ze swojego kodu?
Aftermathew

5
Tak, jest to środek ostrożności, ponieważ funkcję można również wywołać bezpośrednio.
Robbie Hanson,

33
Jest to również wymagane, ponieważ mogą istnieć podklasy. Jeśli nie zastąpią +initializeswojej, nadklasy będą wywoływane, jeśli podklasa zostanie użyta po raz pierwszy.
Sven

3
@Paul możesz zastąpić releasemetodę i uczynić ją pustą. :)

4
@aryaxt: Z wymienionych dokumentów jest już bezpieczny wątek. Tak więc połączenie odbywa się raz na runtime - okres. Wydaje się, że jest to prawidłowe, bezpieczne dla wątków, optymalnie wydajne rozwiązanie.
lilbyrdie

95
@interface MySingleton : NSObject
{
}

+ (MySingleton *)sharedSingleton;
@end

@implementation MySingleton

+ (MySingleton *)sharedSingleton
{
  static MySingleton *sharedSingleton;

  @synchronized(self)
  {
    if (!sharedSingleton)
      sharedSingleton = [[MySingleton alloc] init];

    return sharedSingleton;
  }
}

@end

[Źródło]


7
To wszystko, czego zwykle powinieneś używać do singletonów. Między innymi, utrzymywanie osobnych klas w stanie natychmiastowej dostępności ułatwia ich testowanie, ponieważ możesz testować osobne instancje zamiast sposobu resetowania ich stanu.
Chris Hanson

3
Stig Brautaset: Nie, nie można pominąć @synchronized w tym przykładzie. Ma on za zadanie poradzić sobie z możliwym stanem wyścigu dwóch wątków wykonujących tę funkcję statyczną w tym samym czasie, oba omijając jednocześnie test „if (! SharedSingleton)”, co skutkuje dwoma [alokacjami MySingleton]. .. @synchronized {blok zakresu} zmusza ten hipotetyczny drugi wątek do czekania, aż pierwszy wątek opuści {blok zakresu}, zanim będzie mógł przejść do niego. Mam nadzieję, że to pomoże! =)
MechEthan

3
Co powstrzymuje kogoś przed tworzeniem własnego wystąpienia obiektu? MySingleton *s = [[MySingelton alloc] init];
lindon lis

1
@lindonfox Jaka jest odpowiedź na twoje pytanie?
Raffi Khatchadourian,

1
@Raffi - przepraszam, chyba chyba zapomniałem wkleić odpowiedź. W każdym razie mam książkę, Pro Objective-C Design Patterns for iOSktóra wyjaśnia, w jaki sposób tworzysz „ścisły” singelton. Zasadniczo, ponieważ nie można ustawić metod inicjujących jako prywatnych, należy zastąpić metody przydzielania i kopiowania. Więc jeśli spróbujesz zrobić coś takiego [[MySingelton alloc] init], otrzymasz błąd czasu wykonywania (choć niestety błąd czasu kompilacji). Nie rozumiem, w jaki sposób wszystkie szczegóły dotyczące tworzenia obiektu, ale + (id) allocWithZone:(NSZone *)zonesharedSingleton
wdrażasz

59

Według mojej innej odpowiedzi poniżej, myślę, że powinieneś zrobić:

+ (id)sharedFoo
{
    static dispatch_once_t once;
    static MyFoo *sharedFoo;
    dispatch_once(&once, ^ { sharedFoo = [[self alloc] init]; });
    return sharedFoo;
}

6
Nie przejmuj się wszystkim, co robisz powyżej. Uczyń swoje (miejmy nadzieję bardzo nieliczne) singletony osobno możliwe do utworzenia i po prostu skorzystaj z metody współdzielonej / domyślnej. To, co zrobiłeś, jest konieczne tylko wtedy, gdy naprawdę, naprawdę, TYLKO chcesz jednego wystąpienia swojej klasy. Którego nie robisz, zwłaszcza. do testów jednostkowych.
Chris Hanson,

Chodzi o to, że jest to przykładowy kod Apple do „tworzenia singletonu”. Ale tak, masz absolutną rację.
Colin Barrett

1
Przykładowy kod Apple jest poprawny, jeśli chcesz „prawdziwego” singletonu (tj. Obiektu, który można utworzyć tylko raz, zawsze), ale jak mówi Chris, rzadko jest to, czego chcesz lub potrzebujesz, podczas gdy pewnego rodzaju ustawiana instancja współdzielona jest tym, czego potrzebujesz zwykle chcę.
Luke Redpath

Oto makro dla powyższej metody: gist.github.com/1057420 . Tego używam.
Kobski

1
Pomijając testy jednostkowe, nic nie przemawia przeciwko temu rozwiązaniu, prawda? I to jest szybkie i bezpieczne.
LearnCocos2D

58

Ponieważ Kendall opublikował bezpieczny wątek, który próbuje uniknąć kosztów blokowania, pomyślałem, że też go podrzucę:

#import <libkern/OSAtomic.h>

static void * volatile sharedInstance = nil;                                                

+ (className *) sharedInstance {                                                                    
  while (!sharedInstance) {                                                                          
    className *temp = [[self alloc] init];                                                                 
    if(!OSAtomicCompareAndSwapPtrBarrier(0x0, temp, &sharedInstance)) {
      [temp release];                                                                                   
    }                                                                                                    
  }                                                                                                        
  return sharedInstance;                                                                        
}

OK, pozwól mi wyjaśnić, jak to działa:

  1. Szybki przypadek: w normalnym wykonaniu sharedInstancezostała już ustawiona, więc whilepętla nigdy nie jest wykonywana, a funkcja powraca po zwykłym sprawdzeniu istnienia zmiennej;

  2. Wolna sprawa: jeśli sharedInstancenie istnieje, to instancja jest przydzielana i kopiowana do niej za pomocą funkcji porównania i zamiany („CAS”);

  3. Przypadek sporny: jeśli dwa wątki próbują zadzwonić sharedInstancew tym samym czasie ORAZ sharedInstance nie istnieje w tym samym czasie, oba zainicjują nowe wystąpienia singletonu i podejmą próbę umieszczenia go w CAS. Cokolwiek wygrywa, CAS natychmiast zwraca, cokolwiek traci, zwalnia właśnie przydzieloną instancję i zwraca (teraz ustawione) sharedInstance. Singiel OSAtomicCompareAndSwapPtrBarrierdziała zarówno jako bariera zapisu dla wątku ustawiającego, jak i bariera odczytu dla wątku testowego.


18
Jest to całkowita nadwyżka, co najmniej raz, kiedy może się to zdarzyć podczas życia aplikacji. Niemniej jednak jest on precyzyjny, a technika porównywania i wymiany jest przydatnym narzędziem, o którym należy wiedzieć, więc +1.
Steve Madsen

Dobra odpowiedź - warto wiedzieć o rodzinie OSAtomic
Bill

1
@Louis: Niesamowita, naprawdę pouczająca odpowiedź! Jedno pytanie: co moja initmetoda powinna zrobić w twoim podejściu? sharedInstanceZgadzam się, że zgłoszenie wyjątku podczas inicjalizacji nie jest dobrym pomysłem. Co zatem zrobić, aby użytkownik nie dzwonił initwiele razy bezpośrednio?
matm

2
Na ogół nie przeszkadzam. Często istnieją uzasadnione powody, aby zezwolić na zwielokrotnianie instancji tego, co zwykle jest singletonem, najbardziej wspólne są dla niektórych rodzajów testów jednostkowych. Gdybym naprawdę chciał wymusić pojedynczą instancję, prawdopodobnie sprawdziłbym metodę init, aby sprawdzić, czy glob istnieje, a jeśli tak, to muszę go zwolnić i zwrócić globalny.
Louis Gerbarg,

1
@Tony nieco opóźniony w odpowiedzi, ale OSAtomicCompareAndSwapPtrBarrier wymaga niestabilności. Być może zmienne słowo kluczowe ma powstrzymać kompilator przed optymalizacją testu? Zobacz: stackoverflow.com/a/5334727/449161 i developer.apple.com/library/mac/#documentation/Darwin/Reference/…
Ben Flynn

14
static MyClass * sharedInst = zero;

+ (id) sharedInstance
{
    @synchronize (self) {
        if (sharedInst == zero) {
            / * sharedInst skonfigurowane w init * /
            [[samodzielny przydział] init];
        }
    }
    return sharedInst;
}

- (id) init
{
    if (sharedInst! = zero) {
        [Podniesienie NSException: NSInternalInconsistencyException
            format: @ "Nie można wywołać [% @% @]; zamiast tego użyj + [% @% @]"],
            NSStringFromClass ([self class]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([self class]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } else if (self = [super init]) {
        sharedInst = self;
        / * Bez względu na klasę tutaj * /
    }
    return sharedInst;
}

/ * Te prawdopodobnie nic nie robią
   aplikacja GC. Utrzymuje singleton
   jako faktyczny singleton w
   aplikacja inna niż CG
* /
- (NSUInteger) retainCount
{
    return NSUIntegerMax;
}

- Zwolnienie (w jedną stronę)
{
}

- (id) zachowaj
{
    return sharedInst;
}

- (id) autorelease
{
    return sharedInst;
}

3
Zauważyłem, że brzęk skarży się na wyciek, jeśli nie przypiszesz wyniku [[self alloc] init]do shareInst.
pix0r

Takie podważanie init jest dość brzydkim podejściem IMO. Nie zadzieraj z init i / lub faktycznym stworzeniem obiektu. Jeśli zamiast tego zdecydujesz się na kontrolowany punkt dostępu do współużytkowanej instancji, nie będąc trudnym do wypalenia singletonem w obiekcie, będziesz miał więcej czasu później, jeśli będziesz pisać testy itp. Twarde singletony są zdecydowanie zbyt nadużywane.
occulus

12

Edycja: Ta implementacja jest przestarzała z ARC. Zobacz, jak zaimplementować singleton Objective-C zgodny z ARC? dla prawidłowego wdrożenia.

Wszystkie implementacje inicjalizacji, które przeczytałem w innych odpowiedziach, mają wspólny błąd.

+ (void) initialize {
  _instance = [[MySingletonClass alloc] init] // <----- Wrong!
}

+ (void) initialize {
  if (self == [MySingletonClass class]){ // <----- Correct!
      _instance = [[MySingletonClass alloc] init] 
  }
}

Dokumentacja Apple zaleca sprawdzenie typu klasy w bloku inicjalizacji. Ponieważ podklasy domyślnie wywołują inicjalizację. Istnieje nieoczywisty przypadek, w którym podklasy można tworzyć pośrednio za pośrednictwem KVO. Bo jeśli dodasz następujący wiersz w innej klasie:

[[MySingletonClass getInstance] addObserver:self forKeyPath:@"foo" options:0 context:nil]

Cel C domyślnie utworzy podklasę MySingletonClass, co spowoduje drugie wyzwolenie +initialize.

Możesz pomyśleć, że niejawnie powinieneś sprawdzić duplikat inicjalizacji w swoim bloku init jako taki:

- (id) init { <----- Wrong!
   if (_instance != nil) {
      // Some hack
   }
   else {
      // Do stuff
   }
  return self;
}

Ale zastrzelicie się w stopę; lub, co gorsza, dać innemu deweloperowi możliwość zastrzelenia się w stopę.

- (id) init { <----- Correct!
   NSAssert(_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self){
      // Do stuff
   }
   return self;
}

TL; DR, oto moja implementacja

@implementation MySingletonClass
static MySingletonClass * _instance;
+ (void) initialize {
   if (self == [MySingletonClass class]){
      _instance = [[MySingletonClass alloc] init];
   }
}

- (id) init {
   ZAssert (_instance == nil, @"Duplication initialization of singleton");
   self = [super init];
   if (self) {
      // Initialization
   }
   return self;
}

+ (id) getInstance {
   return _instance;
}
@end

(Zamień ZAssert na własne makro asercji; lub po prostu NSAssert.)


1
Chciałbym po prostu żyć prościej i całkowicie uniknąć inicjalizacji.
Tom Andersen


9

Mam ciekawą odmianę sharedInstance, która jest bezpieczna dla wątków, ale nie blokuje się po inicjalizacji. Nie jestem jeszcze wystarczająco pewny, aby zmodyfikować najwyższą odpowiedź zgodnie z żądaniem, ale przedstawiam ją do dalszej dyskusji:

// Volatile to make sure we are not foiled by CPU caches
static volatile ALBackendRequestManager *sharedInstance;

// There's no need to call this directly, as method swizzling in sharedInstance
// means this will get called after the singleton is initialized.
+ (MySingleton *)simpleSharedInstance
{
    return (MySingleton *)sharedInstance;
}

+ (MySingleton*)sharedInstance
{
    @synchronized(self)
    {
        if (sharedInstance == nil)
        {
            sharedInstance = [[MySingleton alloc] init];
            // Replace expensive thread-safe method 
            // with the simpler one that just returns the allocated instance.
            SEL origSel = @selector(sharedInstance);
            SEL newSel = @selector(simpleSharedInstance);
            Method origMethod = class_getClassMethod(self, origSel);
            Method newMethod = class_getClassMethod(self, newSel);
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return (MySingleton *)sharedInstance;
}

1
+1 to naprawdę intrygujące. Mógłbym użyć, class_replaceMethodby przekształcić sharedInstancesię w klon simpleSharedInstance. W ten sposób nie będziesz musiał martwić się o @synchronizedponowne uzyskanie blokady.
Dave DeLong,

To ten sam efekt, użycie exchangeImplementations oznacza, że ​​po inicjacji, gdy wywołujesz sharedInstance, naprawdę wywołujesz simpleSharedInstance. Właściwie zacząłem od replaceMethod, ale zdecydowałem, że lepiej będzie po prostu zmieniać implementacje, aby oryginał istniał, jeśli zajdzie taka potrzeba ...
Kendall Helmstetter Gelner

W dalszych testach nie mogłem zmusić replaceMethod do pracy - w powtarzanych wywołaniach kod nadal wywoływał pierwotną sharedInstance zamiast simpleSharedInstance. Myślę, że może tak być, ponieważ oba są metodami na poziomie klasy ... Zamiennik, którego użyłem to: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); i niektóre ich odmiany. Mogę zweryfikować kod, który opublikowałem, a simpleSharedInstance jest wywoływany po pierwszym przejściu przez sharedInstance.
Kendall Helmstetter Gelner

Możesz stworzyć wersję bezpieczną dla wątków, która nie będzie ponosić kosztów blokowania po inicjalizacji bez robienia mnóstwa runtime, opublikowałem poniżej implementację.
Louis Gerbarg,

1
+1 świetny pomysł. Uwielbiam te rzeczy, które można zrobić w środowisku uruchomieniowym. Ale w większości przypadków jest to prawdopodobnie przedwczesna optymalizacja. Gdybym naprawdę musiał pozbyć się kosztów synchronizacji, prawdopodobnie użyłbym wersji bez blokady Louis.
Sven

6

Krótka odpowiedź: fantastycznie.

Długa odpowiedź: coś w stylu ....

static SomeSingleton *instance = NULL;

@implementation SomeSingleton

+ (id) instance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (instance == NULL){
            instance = [[super allocWithZone:NULL] init];
        }
    });
    return instance;
}

+ (id) allocWithZone:(NSZone *)paramZone {
    return [[self instance] retain];
}

- (id) copyWithZone:(NSZone *)paramZone {
    return self;
}

- (id) autorelease {
    return self;
}

- (NSUInteger) retainCount {
    return NSUIntegerMax;
}

- (id) retain {
    return self;
}

@end

Przeczytaj nagłówek dispatch / once.h, aby zrozumieć, co się dzieje. W takim przypadku komentarze nagłówka są bardziej odpowiednie niż dokumenty lub strona podręcznika man.


5

Przekształciłem singletona w klasę, aby inne klasy mogły dziedziczyć właściwości singletonu.

Singleton.h:

static id sharedInstance = nil;

#define DEFINE_SHARED_INSTANCE + (id) sharedInstance {  return [self sharedInstance:&sharedInstance]; } \
                               + (id) allocWithZone:(NSZone *)zone { return [self allocWithZone:zone forInstance:&sharedInstance]; }

@interface Singleton : NSObject {

}

+ (id) sharedInstance;
+ (id) sharedInstance:(id*)inst;

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst;

@end

Singleton.m:

#import "Singleton.h"


@implementation Singleton


+ (id) sharedInstance { 
    return [self sharedInstance:&sharedInstance];
}

+ (id) sharedInstance:(id*)inst {
    @synchronized(self)
    {
        if (*inst == nil)
            *inst = [[self alloc] init];
    }
    return *inst;
}

+ (id) allocWithZone:(NSZone *)zone forInstance:(id*)inst {
    @synchronized(self) {
        if (*inst == nil) {
            *inst = [super allocWithZone:zone];
            return *inst;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)retain {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    //do nothing
}

- (id)autorelease {
    return self;
}


@end

A oto przykład jakiejś klasy, w której chcesz zostać singlem.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

Jedynym ograniczeniem dotyczącym klasy Singleton jest to, że jest to podklasa NSObject. Ale przez większość czasu używam singletonów w kodzie, w rzeczywistości są to podklasy NSObject, więc ta klasa naprawdę ułatwia mi życie i czyni kod czystszym.


Możesz użyć innego mechanizmu blokującego, ponieważ @synchronizedjest on strasznie wolny i należy go unikać.
DarkDust

2

Działa to również w środowisku bez śmieci.

@interface MySingleton : NSObject {
}

+(MySingleton *)sharedManager;

@end


@implementation MySingleton

static MySingleton *sharedMySingleton = nil;

+(MySingleton*)sharedManager {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            [[self alloc] init]; // assignment not done here
        }
    }
    return sharedMySingleton;
}


+(id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedMySingleton == nil) {
            sharedMySingleton = [super allocWithZone:zone];
            return sharedMySingleton;  // assignment and return on first allocation
        }
    }
    return nil; //on subsequent allocation attempts return nil
}


-(void)dealloc {
    [super dealloc];
}

-(id)copyWithZone:(NSZone *)zone {
    return self;
}


-(id)retain {
    return self;
}


-(unsigned)retainCount {
    return UINT_MAX;  //denotes an object that cannot be release
}


-(void)release {
    //do nothing    
}


-(id)autorelease {
    return self;    
}


-(id)init {
    self = [super init];
    sharedMySingleton = self;

    //initialize here

    return self;
}

@end

2

Czy nie powinno to być bezpieczne dla wątków i uniknąć kosztownego blokowania po pierwszym połączeniu?

+ (MySingleton*)sharedInstance
{
    if (sharedInstance == nil) {
        @synchronized(self) {
            if (sharedInstance == nil) {
                sharedInstance = [[MySingleton alloc] init];
            }
        }
    }
    return (MySingleton *)sharedInstance;
}

2
Zastosowana tutaj technika podwójnie sprawdzonej blokady jest często prawdziwym problemem w niektórych środowiskach (patrz aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf lub Google it). Dopóki nie zostanie pokazane inaczej, zakładam, że Cel C nie jest odporny. Zobacz także wincent.com/a/knowledge-base/archives/2006/01/… .
Steve Madsen


2

Co powiesz na

static MyClass *gInstance = NULL;

+ (MyClass *)instance
{
    if (gInstance == NULL) {
        @synchronized(self)
        {
            if (gInstance == NULL)
                gInstance = [[self alloc] init];
        }
    }

    return(gInstance);
}

Więc unikasz kosztów synchronizacji po inicjalizacji?


Zobacz dyskusje na temat podwójnego sprawdzania blokady w innych odpowiedziach.
i_am_jorf


1

KLSingleton to:

  1. Podklasa (do n-tego stopnia)
  2. Kompatybilny z ARC
  3. Bezpiecznie z allociinit
  4. Załadowane leniwie
  5. Bezpieczny dla wątków
  6. Bez blokady (używa + inicjalizacji, a nie @synchronize)
  7. Bez makr
  8. Swizzle-free
  9. Prosty

KLSingleton


1
Używam twojego NSSingleton do mojego projektu i wydaje się, że jest on niezgodny z KVO. Chodzi o to, że KVO tworzy podklasę dla każdego obiektu KVO z prefiksem NSKVONotifying_ MyClass . I sprawia, że ​​MyClass + inicjuje i -init metody, które mają być wywoływane dwukrotnie.
Oleg Trakhman

Przetestowałem to na najnowszym Xcode i nie miałem żadnych problemów z rejestracją lub odbieraniem wydarzeń KVO. Możesz to sprawdzić za pomocą następującego kodu: gist.github.com/3065038 Jak wspomniałem na Twitterze, metody inicjalizacji + są wywoływane raz dla NSSingleton i raz dla każdej podklasy. Jest to właściwość Objective-C.
kevinlawler

Jeśli dodasz NSLog(@"initialize: %@", NSStringFromClass([self class]));do +initializemetody, możesz sprawdzić, czy klasy są inicjowane tylko raz.
kevinlawler

NSLog (@ „initialize:% @”, NSStringFromClass ([self class]));
Oleg Trakhman

Możesz również chcieć, aby był zgodny z IB. Mój to: stackoverflow.com/questions/4609609/…
Dan Rosenstark,

0

Nie chcesz synchronizować na sobie ... Ponieważ obiekt self jeszcze nie istnieje! W rezultacie blokujesz tymczasową wartość identyfikatora. Chcesz mieć pewność, że nikt inny nie będzie mógł uruchamiać metod klasowych (sharedInstance, alokacja, alokacjaWithZone: itd.), Więc zamiast tego musisz zsynchronizować obiekt klasy:

@implementation MYSingleton

static MYSingleton * sharedInstance = nil;

+( id )sharedInstance {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ [ MYSingleton alloc ] init ];
    }

    return sharedInstance;
}

+( id )allocWithZone:( NSZone * )zone {
    @synchronized( [ MYSingleton class ] ) {
        if( sharedInstance == nil )
            sharedInstance = [ super allocWithZone:zone ];
    }

    return sharedInstance;
}

-( id )init {
    @synchronized( [ MYSingleton class ] ) {
        self = [ super init ];
        if( self != nil ) {
            // Insert initialization code here
        }

        return self;
    }
}

@end

1
Pozostałe metody, metody dostępu, metody mutatora itp. Powinny synchronizować się na sobie. Wszystkie metody i inicjalizatory klasy (+) (i prawdopodobnie -dealloc) powinny zsynchronizować się z obiektem klasy. Możesz uniknąć konieczności ręcznej synchronizacji, jeśli używasz właściwości Objective-C 2.0 zamiast metod accessor / mutator. Wszystkie object.property i object.property = foo są automatycznie synchronizowane z samym sobą.
Rob Dotson

3
Wyjaśnij, dlaczego uważasz, że selfobiekt nie istnieje w metodzie klasy. Środowisko wykonawcze określa, która implementacja metody ma zostać wywołana na podstawie dokładnie takiej samej wartości, jaką podaje selfdla każdej metody (klasy lub instancji).
dreamlax

2
Wewnątrz metody klasy self znajduje się obiekt klasy. Spróbuj sam:#import <Foundation/Foundation.h> @interface Eggbert : NSObject + (BOOL) selfIsClassObject; @end @implementation Eggbert + (BOOL) selfIsClassObject { return self == [Eggbert class]; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSLog(@"%@", [Eggbert selfIsClassObject] ? @"YES" : @"NO"); [pool drain]; return 0; }
jscs 27.09.11

0

Chciałem to zostawić tutaj, żeby tego nie zgubić. Zaletą tego jest to, że można go używać w InterfaceBuilder, co jest OGROMNĄ zaletą. To pochodzi z innego pytania, które zadałem :

static Server *instance;

+ (Server *)instance { return instance; }

+ (id)hiddenAlloc
{
    return [super alloc];
}

+ (id)alloc
{
    return [[self instance] retain];
}


+ (void)initialize
{
    static BOOL initialized = NO;
    if(!initialized)
    {
        initialized = YES;
        instance = [[Server hiddenAlloc] init];
    }
}

- (id) init
{
    if (instance)
        return self;
    self = [super init];
    if (self != nil) {
        // whatever
    }
    return self;
}

0
static mySingleton *obj=nil;

@implementation mySingleton

-(id) init {
    if(obj != nil){     
        [self release];
        return obj;
    } else if(self = [super init]) {
        obj = self;
    }   
    return obj;
}

+(mySingleton*) getSharedInstance {
    @synchronized(self){
        if(obj == nil) {
            obj = [[mySingleton alloc] init];
        }
    }
    return obj;
}

- (id)retain {
    return self;
}

- (id)copy {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    if(obj != self){
        [super release];
    }
    //do nothing
}

- (id)autorelease {
    return self;
}

-(void) dealloc {
    [super dealloc];
}
@end

0

Wiem, że jest wiele komentarzy na temat tego „pytania”, ale nie widzę wielu osób sugerujących użycie makra do zdefiniowania singletonu. To taki powszechny wzorzec, a makro znacznie upraszcza singleton.

Oto makra, które napisałem na podstawie kilku implementacji Objc, które widziałem.

Singeton.h

/**
 @abstract  Helps define the interface of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the implementation.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonInterface(TYPE, NAME) \
+ (TYPE *)NAME;


/**
 @abstract  Helps define the implementation of a singleton.
 @param  TYPE  The type of this singleton.
 @param  NAME  The name of the singleton accessor.  Must match the name used in the interface.
 @discussion
 Typcially the NAME is something like 'sharedThing' where 'Thing' is the prefix-removed type name of the class.
 */
#define SingletonImplementation(TYPE, NAME) \
static TYPE *__ ## NAME; \
\
\
+ (void)initialize \
{ \
    static BOOL initialized = NO; \
    if(!initialized) \
    { \
        initialized = YES; \
        __ ## NAME = [[TYPE alloc] init]; \
    } \
} \
\
\
+ (TYPE *)NAME \
{ \
    return __ ## NAME; \
}

Przykład zastosowania:

MyManager.h

@interface MyManager

SingletonInterface(MyManager, sharedManager);

// ...

@end

MyManager.m

@implementation MyManager

- (id)init
{
    self = [super init];
    if (self) {
        // Initialization code here.
    }

    return self;
}

SingletonImplementation(MyManager, sharedManager);

// ...

@end

Po co makro interfejsu, gdy jest prawie puste? Spójność kodu między nagłówkiem a plikami kodu; łatwość konserwacji w przypadku, gdy chcesz dodać więcej automatycznych metod lub zmienić je.

Korzystam z metody inicjalizacji, aby utworzyć singleton, tak jak jest to stosowane w najpopularniejszej odpowiedzi tutaj (w momencie pisania).


0

Dzięki metodom klasy Objective C możemy po prostu uniknąć używania wzorca singletonu w zwykły sposób, z:

[[Librarian sharedInstance] openLibrary]

do:

[Librarian openLibrary]

przez zawinięcie klasy w inną klasę, która ma tylko metody klasowe , w ten sposób nie ma szansy na przypadkowe utworzenie duplikatów, ponieważ nie tworzymy żadnej!

Bardziej szczegółowy blog napisałem tutaj :)


Twój link już nie działa.
i_am_jorf

0

Aby rozszerzyć przykład z @ robbie-hanson ...

static MySingleton* sharedSingleton = nil;

+ (void)initialize {
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        sharedSingleton = [[self alloc] init];
    }
}

- (id)init {
    self = [super init];
    if (self) {
        // Member initialization here.
    }
    return self;
}

0

Moja droga jest prosta:

static id instanceOfXXX = nil;

+ (id) sharedXXX
{
    static volatile BOOL initialized = NO;

    if (!initialized)
    {
        @synchronized([XXX class])
        {
            if (!initialized)
            {
                instanceOfXXX = [[XXX alloc] init];
                initialized = YES;
            }
        }
    }

    return instanceOfXXX;
}

Jeśli singleton jest już zainicjowany, blok LOCK nie zostanie wprowadzony. Drugim sprawdzeniem, czy (! Zainicjowano) jest upewnienie się, że nie jest jeszcze inicjalizowane, gdy bieżący wątek nabywa LOCK.


Nie jest jasne, czy oznaczenie initializedjako volatilewystarczające. Zobacz aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf .
i_am_jorf

0

Nie przeczytałem wszystkich rozwiązań, więc wybacz, jeśli ten kod jest zbędny.

To moim zdaniem najbardziej bezpieczna implementacja wątków.

+(SingletonObject *) sharedManager
{
    static SingletonObject * sharedResourcesObj = nil;

    @synchronized(self)
    {
        if (!sharedResourcesObj)
        {
            sharedResourcesObj = [[SingletonObject alloc] init];
        }
    }

    return sharedResourcesObj;
}

-4

Zwykle używam kodu z grubsza podobnego do tego w odpowiedzi Bena Hoffsteina (który również dostałem z Wikipedii). Używam go z powodów podanych przez Chrisa Hansona w jego komentarzu.

Czasami jednak muszę umieścić singletona w NIB, a w takim przypadku używam:

@implementation Singleton

static Singleton *singleton = nil;

- (id)init {
    static BOOL initialized = NO;
    if (!initialized) {
        self = [super init];
        singleton = self;
        initialized = YES;
    }
    return self;
}

+ (id)allocWithZone:(NSZone*)zone {
    @synchronized (self) {
        if (!singleton)
            singleton = [super allocWithZone:zone];     
    }
    return singleton;
}

+ (Singleton*)sharedSingleton {
    if (!singleton)
        [[Singleton alloc] init];
    return singleton;
}

@end

Implementację -retain(itp.) Pozostawiam czytelnikowi, chociaż powyższy kod jest wszystkim, czego potrzebujesz w środowisku śmieci.


2
Twój kod nie jest bezpieczny dla wątków. Używa zsynchronizowanego w metodzie alokacji, ale nie w metodzie init. Sprawdzanie zainicjowanego boola nie jest bezpieczne dla wątków.
Mecki

-5

Przyjęta odpowiedź, choć się kompiluje, jest niepoprawna.

+ (MySingleton*)sharedInstance
{
    @synchronized(self)  <-------- self does not exist at class scope
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

Dokumentacja według Apple:

... Możesz zastosować podobne podejście, aby zsynchronizować metody klas powiązanych klas, używając obiektu Class zamiast self.

Nawet jeśli korzystam z self działa, nie powinno, a to wygląda na błąd kopiowania i wklejania. Prawidłowa implementacja metody klasy fabryki to:

+ (MySingleton*)getInstance
{
    @synchronized([MySingleton class]) 
    {
        if (sharedInstance == nil)
            sharedInstance = [[MySingleton alloc] init];
    }
    return sharedInstance;
}

6
samo z całą pewnością nie istnieje to zakres klasy. Odnosi się do klasy zamiast do instancji klasy. Klasy są (głównie) obiektami pierwszej klasy.
schwa

Dlaczego umieścisz @synchroninzowany W metodzie?
user4951

1
Schwa jak już wspomniałem, self jest wewnątrz klasy obiektu z metody klasy. Zobacz mój komentarz do krótkiego fragmentu, który to pokazuje.
jscs

selfistnieje, ale użycie go jako przekazanego identyfikatora @synchronizedzsynchronizuje dostęp do metod instancji. Jak wskazuje @ user490696, istnieją przypadki (takie jak singletony), w których preferowane jest użycie obiektu klasy. Z Przewodnika po programowaniu Obj-C:You can take a similar approach to synchronize the class methods of the associated class, using the class object instead of self. In the latter case, of course, only one thread at a time is allowed to execute a class method because there is only one class object that is shared by all callers.
stłumić
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.