Objective-C GCD dispatch_once kullanarak singleton oluşturun


341

İOS 4.0 veya üstünü hedefleyebiliyorsanız

GCD kullanmak, Objective-C (thread safe) içinde singleton oluşturmanın en iyi yolu mudur?

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

2
Sınıf kullanıcılarının tahsis / kopya çağırmasını engellemenin bir yolu var mı?
Nicolas Miari

3
dispatch_once_t ve dispatch_once, 4.1 sürümünde değil 4.0 sürümünde kullanıma sunulmuş gibi görünüyor (bkz: developer.apple.com/library/ios/#documentation/Performance/… )
Ben Flynn

1
İnit tekil nesnenin kullanılmasını gerektiriyorsa bu yöntem sorunlu hale gelir. Matt Gallagher'ın kodu benim için birkaç kez çalıştı. cocoawithlove.com/2008/11/…
greg

1
Bu örnekte bunun önemsiz olduğunu biliyorum; ama neden insanlar daha 'yeni' kullanmıyorlar. dispatch_once (& kez ^ {sharedInstance = [kendinden yeni];} sadece biraz daha derli toplu görünüyor O alloc + init eşdeğerdir..
Chris Hatton

3
Dönüş türünü kullanmaya başladığınızdan emin olun instancetype. Bunun yerine kodun tamamlanması çok daha iyidir id.
Bay Rogers

Yanıtlar:


215

Bu, sınıfınızın bir örneğini oluşturmanın mükemmel kabul edilebilir ve iş parçacığı açısından güvenli bir yoludur. Teknik olarak bir "singleton" olmayabilir (bu nesnelerden yalnızca 1 tanesi olabilir), ancak yalnızca [Foo sharedFoo]nesneye erişmek için yöntemi kullandığınız sürece , bu yeterince iyidir.


4
Yine de nasıl serbest bırakıyorsun?
samvermette

65
@samvermette yapmazsın. bir singletonun amacı her zaman var olacağıdır. böylece, onu serbest bırakmazsınız ve işlem çıkışları ile bellek geri kazanılır.
Dave DeLong

6
@Dave DeLong: Bence singleton sahibi olma amacı ölümsüzlüğünün kesinliği değil, tek bir örneğimiz olduğu kesinliğidir. Ya bu singleton bir semaforu düşürürse? Her zaman var olacağını sadece keyfi olarak söyleyemezsiniz.
jacekmigacz

4
@ schooleyhoop Evet, belgelerinde . "Aynı anda birden çok iş parçacığından çağrılırsa, bu işlev blok tamamlanana kadar senkronize olarak bekler."
Kevin

3
@ WalterMartinVargas-Pena güçlü referans statik değişken tarafından tutulur
Dave DeLong

36

instancetype

instancetype, Objective-Cher yeni sürümle daha fazlası eklenerek birçok dil uzantısından sadece biridir .

Bil, sev.

Ve bunu, düşük seviyeli ayrıntılara dikkat etmenin, Objective-C'yi dönüştürmek için nasıl güçlü yeni yollar haline getirebileceğinin bir örneğini ele alalım.

Buraya bakın: instancetype


+ (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
inanılmaz ipucu, teşekkürler! instancetype, bir yöntemin ilgili sonuç türünü döndürdüğünü belirtmek için sonuç türü olarak kullanılabilen bağlamsal bir anahtar kelimedir. ... instancetype ile derleyici türü doğru olarak çıkarır.
Fattie

1
İki parçacığın burada ne anlama geldiği bana açık değil, birbirlerine eşdeğer mi? Biri diğerine tercih edilir mi? Yazar bunun için biraz açıklama ekleyebilirse iyi olurdu.
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

Başlangıç ​​nasıl mevcut değil? En azından biri için uygun değil initmi?
Bal

2
Singleton yalnızca bir erişim noktasına sahip olmalıdır. Ve bu nokta paylaşıldıInstance. * .H dosyasında init yöntemimiz varsa, başka bir singleton örneği oluşturabilirsiniz. Bu bir singletonun tanımıyla çelişir.
Sergey Petruk

1
@ asma22 __attribute __ ((unavailable ()) bu yöntemleri kullanamaz duruma getirir.Başka bir programcı kullanılamaz olarak işaretlenmiş yöntemi kullanmak isterse hata alır
Sergey Petruk

1
Tamamen alıyorum ve yeni bir şey öğrendiğim için mutluyum, cevabınızla ilgili yanlış bir şey yok, sadece yeni başlayanlar için biraz kafa karıştırıcı olabilir ...
Bal

1
Bu sadece MySingleton, örneğin MySingleton.mben arıyorum[super alloc]
Sergey Petruk

6

Sınıfın, ayırma yönteminin üzerine yazılmasıyla ayrılmasını önleyebilirsiniz.

@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
Bu, yukarıdaki yorumlarda sorumu cevaplıyor. Defansif programlama için çok fazla değilim, ama ...
Nicolas Miari

5

Dave haklı, gayet iyi. Sınıflar sharedFoo yöntemini KULLANMAMAYI seçerse yalnızca bir tanesinin oluşturulabilmesini sağlamak için diğer yöntemlerden bazılarını uygulama ipuçları için Apple'ın dokümanlarını kontrol etmek isteyebilirsiniz .


8
eh ... bu bir singleton yaratmanın en büyük örneği değil. Bellek yönetimi yöntemlerinin geçersiz kılınması gerekli değildir.
Dave DeLong

19
Bu, ARC kullanılarak tamamen geçersizdir.
logancautrell

Atıfta bulunulan doküman kullanımdan kaldırıldı. Ayrıca, yalnızca harici içeriğe bağlantı olan cevaplar genellikle zayıf SO yanıtlarıdır. En azından cevabınızdaki ilgili bölümleri alın. Eski yolun gelecek kuşaklar için saklanmasını istemiyorsanız buraya zahmet etmeyin.
araç çubuğu

4

[[Sınıfım tahsis] init] öğesinin sharedInstance ile aynı nesneyi döndürdüğünden emin olmak istiyorsanız (bence gerekli değildir, ancak bazı kişiler bunu ister), bu ikinci bir dispatch_once kullanılarak çok kolay ve güvenli bir şekilde yapılabilir:

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

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

    return sharedInstance;
}

Bu, [[MyClass tahsis] init] ve [MyClass sharedInstance] birleşimlerinin aynı nesneyi döndürmesini sağlar; [MyClass sharedInstance] biraz daha verimli olurdu. Nasıl çalışır: [MyClass sharedInstance] bir kez [[MyClass separa] init] öğesini çağırır. Diğer kodlar da bunu birçok kez çağırabilir. İlk başlatan kişi "normal" başlatmayı yapacak ve singleton nesnesini init yönteminde saklayacaktır. Daha sonra yapılan init çağrıları, hangi ayırmanın döndürdüğünü tamamen yoksayar ve aynı sharedInstance değerini döndürür; tahsis sonucu serbest bırakılacaktır.

+ SharedInstance yöntemi her zamanki gibi çalışır. [[Sınıfım tahsis] init] 'i çağıran ilk arayan değilse, init sonucu ayrılan çağrının sonucu değildir, ama sorun değildir.


2

Bunun "singleton oluşturmanın en iyi yolu" olup olmadığını soruyorsunuz.

Birkaç düşünce:

  1. İlk olarak, evet, bu iş parçacığı için güvenli bir çözümdür. Bu dispatch_oncemodel Objective-C'de tek ton üretmenin modern, iş parçacığı açısından güvenli bir yoludur. Endişeye gerek yok.

  2. Yine de, bunu yapmanın "en iyi" yolu olup olmadığını sordunuz. Ancak şunu kabul etmeliyiz ki instancetypeve[[self alloc] init] birlikte, tekillerle birlikte kullanıldığında potansiyel olarak yanıltıcı .

    Bunun yararı instancetype, sınıfın, iddün yaptığımız gibi bir tür başvurmadan alt sınıflara ayrılabileceğini açıklamanın açık bir yoludur .

    Ancak staticbu yöntemde alt sınıflandırma zorlukları ortaya çıkar. Ya ImageCacheve BlobCachesingletonların her ikisi de Cachekendi sharedCacheyöntemlerini uygulamadan bir üst sınıftan alt sınıflarsa ?

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

    Bunun işe yaraması için, alt sınıfların kendi sharedInstance(veya kendi sınıfınız için ne derseniz söyleyin) yöntemini uyguladığından emin olmanız gerekir.

    Alt satırda, orijinaliniz alt sınıfları destekleyecek gibi sharedInstance görünüyor , ancak olmayacak. Alt sınıflamayı desteklemek istiyorsanız, en azından gelecekteki geliştiricileri bu yöntemi geçersiz kılmaları konusunda uyaran belgeleri ekleyin.

  3. Swift ile en iyi birlikte çalışabilirlik için, muhtemelen bunu bir sınıf yöntemi değil, bir özellik olarak tanımlamak istersiniz, örneğin:

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

    Sonra devam edip bu özellik için bir alıcı yazabilirsiniz (uygulama dispatch_onceönerdiğiniz kalıbı kullanır ):

    + (Foo *)sharedFoo { ... }

    Bunun yararı, bir Swift kullanıcısı kullanmaya başlarsa, şöyle bir şey yapmalarıdır:

    let foo = Foo.shared

    Not, hayır (), çünkü onu bir özellik olarak uyguladık. Swift 3'ten başlayarak, genellikle tektonlara bu şekilde erişilir. Dolayısıyla onu bir özellik olarak tanımlamak, birlikte çalışabilirliği kolaylaştırmaya yardımcı olur.

    Bir kenara, Apple'ın kendi tektonlarını nasıl tanımladığına bakarsanız, bu benimsedikleri kalıptır, örneğin NSURLSessiontekilleri aşağıdaki gibi tanımlanır:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. Swift ile birlikte çalışabilirlik konusundaki bir diğer önemli husus, singleton'un adıydı. Bunun yerine türün adını dahil edebiliyorsanız en iyisidir sharedInstance. Örneğin, sınıf buysa Foo, singleton özelliğini olarak tanımlayabilirsiniz sharedFoo. Veya sınıf olsaydı DatabaseManager, özelliği çağırabilirsiniz sharedManager. Ardından Swift kullanıcıları şunları yapabilir:

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

    Açıkçası, gerçekten kullanmak istiyorsanız sharedInstance, istediğiniz zaman Swift adını her zaman bildirebilirsiniz:

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

    Açıkça, Objective-C kodu yazarken, Swift birlikte çalışabilirliğinin diğer tasarım hususlarından daha ağır basmasına izin vermemeliyiz, ancak yine de, her iki dili de zarif bir şekilde destekleyen bir kod yazabilirsek, bu tercih edilir.

  5. Bunun, geliştiricilerin kendi örneklerini (yanlışlıkla) somutlaştıramayacağı / kazaramayacağı gerçek bir singleton olmasını istiyorsanız, unavailableniteleyicinin initve newihtiyatlı olduğuna dikkat çeken diğerlerine katılıyorum .


0

İş parçacığı güvenli singleton oluşturmak için şunları yapabilirsiniz:

@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

ve bu blog objeyi / kakaodaki singleton'u çok iyi singleton'ları açıklıyor


OP en modern uygulama hakkında özellikler isterken çok eski bir makaleye bağlantı veriyorsunuz.
vikingosegundo

1
Soru belirli bir uygulama ile ilgilidir. Başka bir uygulama yayınlıyorsunuz. Burada, soruyu cevaplamaya bile çalışmayın.
vikingosegundo

1
@vikingosegundo Asker hava durumu GCD bir Konu güvenli singleton oluşturmak için en iyi yol olduğunu sorar, cevabım başka bir seçenek vermek.
Hancock_Xu

asker belirli bir uygulamanın evre güvenli olup olmadığını sorar. seçenek istemiyor.
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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.