Utwórz singletona za pomocą GCD's dispatch_once w Objective-C


341

Jeśli możesz kierować system iOS 4.0 lub nowszy

Czy używając GCD to najlepszy sposób na utworzenie singletonu w Objective-C (wątek bezpieczny)?

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

2
Czy istnieje sposób, aby uniemożliwić użytkownikom klasy wywoływanie funkcji przydzielania / kopiowania?
Nicolas Miari,

3
dispatch_once_t i dispatch_once wydają się być wprowadzone w 4.0, a nie 4.1 (patrz: developer.apple.com/library/ios/#documentation/Performance/… )
Ben Flynn

1
Ta metoda staje się problematyczna, jeśli init wymaga użycia obiektu singleton. Kod Matta Gallaghera działał dla mnie więcej niż kilka razy. cocoawithlove.com/2008/11/…
greg

1
Wiem, że to nie ma znaczenia w tym przykładzie; ale dlaczego ludzie nie używają więcej „nowych”. dispatch_once (& po ^ {sharedInstance = [siebie nowy];} tylko wygląda, że bit schludniejszy Jest to równoważne alloc + init..
Chris Hatton

3
Pamiętaj, aby zacząć używać typu zwrotu instancetype. Uzupełnianie kodu jest znacznie lepsze, gdy używasz go zamiast id.
Pan Rogers

Odpowiedzi:


215

Jest to całkowicie akceptowalny i bezpieczny w wątkach sposób tworzenia instancji swojej klasy. Technicznie może to nie być „singleton” (ponieważ może istnieć tylko jeden z tych obiektów), ale dopóki używasz tylko [Foo sharedFoo]metody dostępu do obiektu, jest to wystarczająco dobre.


4
Jak to zrobić?
samvermette,

65
@samvermette ty nie. chodzi o to, że zawsze będzie istnieć. dlatego nie zwalniasz go, a pamięć zostaje odzyskana po wyjściu z procesu.
Dave DeLong

6
@Dave DeLong: Moim zdaniem celem singletonu nie jest pewność jego nieśmiertelności, ale pewność, że mamy jeden przypadek. Co jeśli ten singleton zmniejsza semafor? Nie możesz po prostu arbitralnie powiedzieć, że zawsze będzie istnieć.
jacekmigacz

4
@hooleyhoop Tak, w dokumentacji . „Jeśli wywoływana jednocześnie z wielu wątków, funkcja ta czeka synchronicznie aż do zakończenia bloku.”
Kevin,

3
@ WalterMartinVargas-Pena silne odniesienie jest utrzymywane przez zmienną statyczną
Dave DeLong

36

typ instancet

instancetypejest tylko jednym z wielu rozszerzeń językowych Objective-C, z każdym nowym dodatkiem dodawanym.

Wiedz to, kochaj to.

I weź to jako przykład tego, w jaki sposób zwracanie uwagi na szczegóły niskiego poziomu może dać ci wgląd w nowe potężne sposoby transformacji Celu C.

Zobacz tutaj: typ instancet


+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

+ (Class*)sharedInstance
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

4
niesamowita wskazówka, dzięki! instancetype jest kontekstowym słowem kluczowym, którego można użyć jako typu wyniku w celu zasygnalizowania, że ​​metoda zwraca powiązany typ wyniku. ... W przypadku instancetype kompilator poprawnie wywnioskuje typ.
Fattie,

1
Nie jest dla mnie jasne, co oznaczają dwa fragmenty, czy są sobie równe? Jedno jest lepsze od drugiego? Byłoby miło, gdyby autor mógł dodać do tego trochę wyjaśnień.
galactica

33

MySingleton.h

@interface MySingleton : NSObject

+(instancetype)sharedInstance;

+(instancetype)alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
-(instancetype)init __attribute__((unavailable("init not available, call sharedInstance instead")));
+(instancetype)new __attribute__((unavailable("new not available, call sharedInstance instead")));
-(instancetype)copy __attribute__((unavailable("copy not available, call sharedInstance instead")));

@end

MySingleton.m

@implementation MySingleton

+(instancetype)sharedInstance {
    static dispatch_once_t pred;
    static id shared = nil;
    dispatch_once(&pred, ^{
        shared = [[super alloc] initUniqueInstance];
    });
    return shared;
}

-(instancetype)initUniqueInstance {
    return [super init];
}

@end

W jaki sposób init nie jest dostępny? Czy to nie jest przynajmniej dostępne dla jednego init?
Honey

2
Singleton powinien mieć tylko jeden punkt dostępu. I ten punkt jest udostępniony. Jeśli mamy metodę init w pliku * .h, możesz utworzyć inną instancję singletonu. Jest to sprzeczne z definicją singletonu.
Sergey Petruk,

1
@ asma22 __attribute __ ((niedostępny ()) nie pozwala na użycie tych metod. Jeśli inny programista chce użyć metody oznaczonej jako niedostępna, pojawia się błąd
Sergey Petruk,

1
Całkowicie rozumiem i cieszę się, że nauczyłem się czegoś nowego, nic złego w twojej odpowiedzi, może być trochę mylące dla początkujących ...
Honey

1
Działa to tylko dla MySingleton, na przykład, MySingleton.mdzwonię[super alloc]
Sergey Petruk,

6

Można uniknąć przydzielenia klasy poprzez zastąpienie metody przydzielania.

@implementation MyClass

static BOOL useinside = NO;
static id _sharedObject = nil;


+(id) alloc {
    if (!useinside) {
        @throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil];
    }
    else {
        return [super alloc];
    }
}

+(id)sharedInstance
{
    static dispatch_once_t p = 0;
    dispatch_once(&p, ^{
        useinside = YES;
        _sharedObject = [[MyClass alloc] init];
        useinside = NO;
    });   
    // returns the same object each time
    return _sharedObject;
}

1
To odpowiada na moje pytanie w powyższych komentarzach. Nie dlatego, że tak bardzo interesuję się programowaniem obronnym, ale ...
Nicolas Miari

5

Dave ma rację, w porządku. Możesz zapoznać się z dokumentami Apple dotyczącymi tworzenia singletonu, aby uzyskać wskazówki dotyczące implementacji niektórych innych metod, aby upewnić się, że tylko jedna może zostać utworzona, jeśli klasy zdecydują się NIE używać metody sharedFoo.


8
eh ... to nie jest najlepszy przykład tworzenia singletonu. Zastąpienie metod zarządzania pamięcią nie jest konieczne.
Dave DeLong,

19
Jest to całkowicie nieprawidłowe przy użyciu ARC.
logancautrell,

Cytowany dokument został wycofany. Również odpowiedzi, które są wyłącznie linkami do treści zewnętrznych, są na ogół słabymi odpowiedziami SO. Przynajmniej fragment odpowiednich fragmentów w odpowiedzi. Nie przejmuj się tutaj, chyba że chcesz zachować stary sposób na potomstwo.
toolbear

4

Jeśli chcesz się upewnić, że [[MyClass przydzielone] init] zwraca ten sam obiekt co sharedInstance (moim zdaniem nie jest to konieczne, ale niektórzy ludzie tego chcą), można to zrobić bardzo łatwo i bezpiecznie za pomocą drugiego dispatch_once:

- (instancetype)init
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        // Your normal init code goes here. 
        sharedInstance = self;
    });

    return sharedInstance;
}

Pozwala to dowolnej kombinacji [[MyClass przydziału] init] i [MyClass sharedInstance] zwrócić ten sam obiekt; [MyClass sharedInstance] byłby tylko trochę bardziej wydajny. Jak to działa: [MyClass sharedInstance] wywoła [inicjację [MyClass]] raz] jeden raz. Inny kod może go również wywołać dowolną liczbę razy. Pierwszy dzwoniący do init wykona „normalną” inicjalizację i zapisze obiekt singletonu z dala od metody init. Wszelkie późniejsze wywołania init będą całkowicie ignorować to, co alokacja zwróciło i zwróci tę samą sharedInstance; wynik alokacji zostanie anulowany.

Metoda + sharedInstance będzie działać jak zawsze. Jeśli nie jest to pierwszy dzwoniący, który wywołuje [[MyClass przydzielone] init], to wynik init nie jest wynikiem połączenia przydzielającego, ale jest w porządku.


2

Pytasz, czy to „najlepszy sposób na stworzenie singletonu”.

Kilka myśli:

  1. Po pierwsze, tak, jest to rozwiązanie bezpieczne dla wątków. Ten dispatch_oncewzór jest nowoczesnym, bezpiecznym dla wątków sposobem generowania singletonów w Objective-C. Nie martw się tam.

  2. Zapytałeś jednak, czy jest to „najlepszy” sposób na zrobienie tego. Należy jednak uznać, że instancetypei [[self alloc] init]jest potencjalnie mylące, gdy jest używane w połączeniu z singletonami.

    Zaletą tego instancetypejest to, że jest to jednoznaczny sposób zadeklarowania, że ​​klasę można podklasować bez uciekania się do pewnego rodzaju id, jak to robiliśmy w przeszłości.

    Ale staticw tej metodzie przedstawia się wyzwania związane z podklasowaniem. Co jeśli ImageCachei BlobCachesingletons były zarówno podklasy ze Cachenadrzędnej bez wdrożenie własnej sharedCachemetody?

    ImageCache *imageCache = [ImageCache sharedCache];  // fine
    BlobCache *blobCache = [BlobCache sharedCache];     // error; this will return the aforementioned ImageCache!!!

    Aby to zadziałało, musisz upewnić się, że podklasy implementują własną sharedInstance(lub jakkolwiek to nazwiesz dla konkretnej klasy) metodę.

    Podsumowując, twój oryginalny sharedInstance wygląd będzie obsługiwał podklasy, ale nie będzie. Jeśli zamierzasz wspierać podklasowanie, dołącz przynajmniej dokumentację, która ostrzega przyszłych programistów, że muszą zastąpić tę metodę.

  3. Aby uzyskać najlepszą interoperacyjność z Swift, prawdopodobnie chcesz zdefiniować to jako właściwość, a nie metodę klasy, np .:

    @interface Foo : NSObject
    @property (class, readonly, strong) Foo *sharedFoo;
    @end

    Następnie możesz napisać getter dla tej właściwości (implementacja dispatch_onceużyłaby zaproponowanego wzorca):

    + (Foo *)sharedFoo { ... }

    Zaletą tego jest to, że jeśli użytkownik Swift z niego skorzysta, zrobiłby coś takiego:

    let foo = Foo.shared

    Uwaga, nie ma (), ponieważ zaimplementowaliśmy go jako właściwość. Począwszy od wersji Swift 3, w ten sposób zazwyczaj uzyskuje się dostęp do singletonów. Tak więc zdefiniowanie go jako właściwości ułatwia tę interoperacyjność.

    Nawiasem mówiąc, jeśli spojrzysz na to, jak Apple definiuje ich singletony, jest to wzorzec, który przyjęli, np. Ich NSURLSessionsingleton jest zdefiniowany w następujący sposób:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. Inną, bardzo niewielką kwestią dotyczącą szybkiej interoperacyjności była nazwa singletonu. Najlepiej, jeśli zamiast tego możesz podać nazwę typu sharedInstance. Na przykład, jeśli klasa była Foo, możesz zdefiniować właściwość singleton jako sharedFoo. Lub jeśli klasa była DatabaseManager, możesz zadzwonić do nieruchomości sharedManager. Następnie użytkownicy Swift mogliby:

    let foo = Foo.shared
    let manager = DatabaseManager.shared

    Oczywiście, jeśli naprawdę chcesz używać sharedInstance, zawsze możesz zadeklarować nazwę Swift, jeśli chcesz:

    @property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);

    Oczywiście, pisząc kod Celu-C, nie powinniśmy pozwolić, aby interoperacyjność Swift przeważała nad innymi względami projektowymi, ale mimo to, jeśli możemy napisać kod, który z wdziękiem obsługuje oba języki, jest to preferowane.

  5. Zgadzam się z innymi, którzy podkreślają, że jeśli chcesz, aby był to prawdziwy singleton, w którym programiści nie mogą / nie powinni (przypadkowo) tworzyć własnych instancji, unavailablekwalifikator jest włączony initi newjest ostrożny.


0

Aby utworzyć singleton bezpieczny dla wątków, możesz wykonać następujące czynności:

@interface SomeManager : NSObject
+ (id)sharedManager;
@end

/* thread safe */
@implementation SomeManager

static id sharedManager = nil;

+ (void)initialize {
    if (self == [SomeManager class]) {
        sharedManager = [[self alloc] init];
    }
}

+ (id)sharedManager {
    return sharedManager;
}
@end

i ten blog bardzo dobrze tłumaczy singletony w objc / cocoa


łączysz się z bardzo starym artykułem, podczas gdy OP pyta o cechy dotyczące najnowocześniejszego wdrożenia.
vikingosegundo,

1
Pytanie dotyczy konkretnej implementacji. Po prostu publikujesz kolejną implementację. Tam, bo nawet nie próbujesz odpowiedzieć na pytanie.
vikingosegundo

1
@vikingosegundo Pytający pytający pogodę GCD jest najlepszym sposobem na stworzenie singletonu Bezpiecznego wątku, moja odpowiedź daje inny wybór. Coś nie tak?
Hancock_Xu

pytający pyta, czy określona implementacja jest bezpieczna dla wątków. nie pyta o opcje.
vikingosegundo

0
//Create Singleton  
  +( instancetype )defaultDBManager
    {

        static dispatch_once_t onceToken = 0;
        __strong static id _sharedObject = nil;

        dispatch_once(&onceToken, ^{
            _sharedObject = [[self alloc] init];
        });

        return _sharedObject;
    }


//In it method
-(instancetype)init
{
    self = [super init];
  if(self)
     {
   //Do your custom initialization
     }
     return self;
}

0
@interface className : NSObject{
+(className*)SingleTonShare;
}

@implementation className

+(className*)SingleTonShare{

static className* sharedObj = nil;
static dispatch_once_t once = 0;
dispatch_once(&once, ^{

if (sharedObj == nil){
    sharedObj = [[className alloc] init];
}
  });
     return sharedObj;
}
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.