Objective-C singletonum nasıl olmalı? [kapalı]


334

Singleton erişimci yöntemim genellikle aşağıdakilerin bir çeşididir:

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Bunu geliştirmek için ne yapabilirim?


27
Genel değişken bildirimini + örnek yönteminize taşıyabilirsiniz (ayarlanmasına izin vermedikçe kullanılması gereken tek yer) ve + defaultMyClass veya + gibi bir ad kullanabilirsiniz. yönteminiz için sharedMyClass. + örneği niyeti açığa çıkarmaz.
Chris Hanson

Bu sorunun 'cevabının' yakında değişmesi pek olası olmadığından, soruya tarihsel bir kilit yerleştiriyorum. İki neden 1) Çok sayıda görüş, oy ve iyi içerik 2) Açık / kapalı hareket etmeyi önlemek. Zamanı için harika bir soruydu, ancak bu tür sorular Yığın Taşması için uygun değil. Artık çalışma kodunu kontrol etmek için Kod İncelememiz var . Lütfen bu sorunun tüm tartışmalarını bu meta soruya yönlendirin .
George Stocker

Yanıtlar:


207

Başka bir seçenek de +(void)initializeyöntemi kullanmaktır . Belgelerden:

Çalışma zamanı initialize, bir programdaki her sınıfa, sınıftan tam olarak bir kez önce veya ondan devralan herhangi bir sınıfa ilk iletisini programın içinden gönderir. (Bu nedenle, sınıf kullanılmazsa yöntem asla çağrılmayabilir.) Çalışma zamanı, initializeiletiyi sınıflara iş parçacığı için güvenli bir şekilde gönderir . Üst sınıflar bu mesajı alt sınıflarından önce alır.

Böylece buna benzer bir şey yapabilirsiniz:

static MySingleton *sharedSingleton;

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

7
Çalışma zamanı bunu yalnızca bir kez arayacaksa, BOOL ne işe yarar? Birisinin bu işlevi kodlarından açıkça çağırması durumunda bir önlem mi?
Aftermathew

5
Evet, bir önlemdir, çünkü işlev doğrudan da çağrılabilir.
Robbie Hanson

33
Bu ayrıca gereklidir çünkü alt sınıflar olabilir. Eğer geçersiz +initializekılmazlarsa, alt sınıf ilk kez kullanılırsa üst sınıf uygulamaları çağrılır.
Sven

3
@Paul releaseyöntemi geçersiz kılabilir ve boş bırakabilirsiniz . :)

4
@aryaxt: Listelenen dokümanlarda bu zaten güvenli bir iş parçacığı. Bu nedenle, çağrı çalışma zamanı başına bir kez yapılır. Bu doğru, ipliğe karşı güvenli, optimum düzeyde verimli bir çözüm gibi görünmektedir.
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

[Kaynak]


7
Genellikle singleton için kullanmanız gereken tek şey budur. Diğer şeylerin yanı sıra, sınıflarınızı ayrı ayrı anlık tutmak onları test etmeyi kolaylaştırır, çünkü durumlarını sıfırlamanın bir yolu yerine ayrı örnekleri test edebilirsiniz.
Chris Hanson

3
Stig Brautaset: Hayır, bu örnekte senkronize olmayanı bırakmak doğru değildir. Bu statik işlevi aynı anda yürüten iki iş parçacığının olası yarış durumunu ele almak, hem "if (! SharedSingleton)" testini aynı anda geçmek ve böylece iki [MySingleton tahsisi] elde etmek mümkündür. .. @synchronized {scope block}, varsayımsal ikinci iş parçacığının, ilerlemesine izin verilmeden önce ilk iş parçacığının {scope block} 'dan çıkmasını beklemesini sağlar. Umarım bu yardımcı olur! =)
MechEthan

3
Birinin hala kendi nesne örneğini yapmasını engelleyen nedir? MySingleton *s = [[MySingelton alloc] init];
Lindon tilki

1
@lindonfox Sorunuzun cevabı nedir?
Raffi Khatchadourian

1
@Raffi - Üzgünüm, cevabımı yapıştırmayı unutmam gerektiğini düşünüyorum. Her neyse, kitabı aldım Pro Objective-C Design Patterns for iOSve nasıl "katı" bir singelton yaptığınızı anlatıyor. Temel olarak başlatma yöntemlerini özel yapamadığınız için, ayırma ve kopyalama yöntemlerini geçersiz kılmanız gerekir. Eğer denemek ve gibi bir şey yapmak yani [[MySingelton alloc] init](ne yazık ki bir derleme zamanı hatası olmasa da) bir çalışma zamanı hatası alırsınız. Nesne yaratmanın tüm detaylarının nasıl olduğunu anlamıyorum, ama + (id) allocWithZone:(NSZone *)zonedenirsharedSingleton
lindon fox

59

Aşağıdaki diğer cevabım uyarınca, bence yapmalısınız:

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

6
Yukarıda yaptığınız şeylerle uğraşmayın. (Umarım son derece az) singletonlarınızı ayrı ayrı somut hale getirin ve sadece paylaşılan / varsayılan bir yönteme sahip olun. Yaptığınız şey sadece SADECE sınıfınızın tek bir örneğini gerçekten istiyorsanız. Hangi yapmazsan, esp. birim testleri için.
Chris Hanson

Mesele şu ki "singleton oluşturmak" için Apple örnek kodudur. Ama evet, kesinlikle haklısın.
Colin Barrett

1
Apple örnek kodu, "gerçek" bir singleton (yani, yalnızca bir kez, her zaman somutlaştırılabilen bir nesne) istiyorsanız doğrudur, ancak Chris'in dediği gibi, bu nadiren istediğiniz veya ihtiyacınız olan şeyken, bir çeşit ayarlanabilir paylaşımlı örnek sizin genellikle istiyorum.
Luke Redpath

Yukarıdaki yöntem için bir makro: gist.github.com/1057420 . Ben bunu kullanıyorum.
Kobski

1
Birim testleri bir yana, bu çözüme karşı konuşan hiçbir şey yok, değil mi? Ve hızlı ve güvenlidir.
LearnCocos2D

58

Yana Kendall yayınlanmıştır maliyetleri kilitleme önlemek için girişimlerde tekil bir evre, ben de bir tane atmak düşündüm:

#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;                                                                        
}

Tamam, bunun nasıl çalıştığını açıklayayım:

  1. Hızlı durum: Normal yürütmede sharedInstanceönceden ayarlanmış olduğundan, whiledöngü asla yürütülmez ve değişkenin varlığını test ettikten sonra işlev geri döner;

  2. Yavaş durumda: sharedInstanceYoksa, bir Karşılaştırma ve Değiştirme ('CAS') kullanarak bir örnek atanır ve bu örneğe kopyalanır;

  3. Koşullu durum: İki iş parçacığı sharedInstanceaynı anda aramaya çalışırsa VE aynı anda sharedInstancemevcut değilse, hem singletonun yeni örneklerini başlatır hem de onu CAS konumuna getirmeye çalışır. Hangisi kazanırsa CAS derhal geri döner, hangisi kaybederse az önce tahsis ettiği örneği serbest bırakır ve (şimdi ayarlanmış) döndürür sharedInstance. Tekil OSAtomicCompareAndSwapPtrBarrier, hem ayar dişi için bir yazma bariyeri hem de test dişinden bir okuma bariyeri gibi davranır.


18
Bu, bir uygulamanın ömrü boyunca olabileceği en fazla bir kez tam bir aşırılıktır. Bununla birlikte, yerinde doğrudur ve karşılaştırma ve takas tekniği bilmek için yararlı bir araçtır, bu yüzden +1.
Steve Madsen

Güzel cevap - OSAtomic ailesi hakkında bilmek iyi bir şey
Bill

1
@ Louis: Şaşırtıcı, gerçekten aydınlatıcı cevap! Yine de bir soru: inityaklaşımım yaklaşımınızda ne yapmalı ? sharedInstanceBaşlatıldığında bir istisna atmanın iyi bir fikir olmadığını düşünüyorum. Kullanıcının initdoğrudan birçok kez arama yapmasını önlemek için ne yapmalı ?
matm

2
Genellikle bunu engellemiyorum. Genellikle bir singletonun somutlaştırılmış çarpımına izin vermek için genellikle geçerli nedenler vardır, en yaygın olanı belirli tipte testler içindir. Eğer gerçekten tek bir örneği zorlamak istersem, muhtemelen init yönteminin küresel var olup olmadığını kontrol etmesini isterdim ve eğer öyleyse kendi kendini salıverdim ve küresel olanı döndürürüm.
Louis Gerbarg

1
@Tony biraz yanıt gecikti, ancak OSAtomicCompareAndSwapPtrBarrier uçucu olmasını gerektiriyor. Belki de geçici anahtar kelime derleyicinin kontrolü optimize etmemesini sağlamaktır? Bkz. Stackoverflow.com/a/5334727/449161 ve developer.apple.com/library/mac/#documentation/Darwin/Reference/…
Ben Flynn

14
statik Sınıfım * sharedInst = nil;

+ (id) sharedInstance
{
    @synchronize (self) {
        if (sharedInst == nil) {
            / * sharedInit'te kuruluyor * /
            [[kendi kendine ayrılmış] init];
        }
    }
    dönüş paylaştıInst;
}

- (id) init
{
    if (sharedInst! = nil) {
        [NSException artışı: NSInternalInconsistencyException
            format: @ "[% @% @] çağrılamaz; bunun yerine + [% @% @] kullanın"],
            NSStringFromClass ([kendi sınıfı]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([kendi sınıfı]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } else if (self = [süper başlangıç]) {
        sharedInst = benlik;
        / * Burada hangi sınıfa özel olursa olsun * /
    }
    dönüş paylaştıInst;
}

/ * Bunlar muhtemelen
   bir GC uygulaması. Singleton'u korur
   gerçek bir singleton olarak
   CG olmayan uygulama
* /
- (NSUInteger) retainCount
{
    dönüş NSUIntegerMax;
}

- (yoldan çıkma) serbest bırakma
{
}

- (kimlik) alıkoyma
{
    dönüş paylaştıInst;
}

- (kimlik) otomatik yayın
{
    dönüş paylaştıInst;
}

3
[[self alloc] init]SharedInst sonucunu atamazsanız clang'ın bir sızıntıdan şikayet ettiğini fark ettim .
pix0r

İnit'i bu şekilde altüst etmek oldukça çirkin bir yaklaşımdır. İnit ve / veya nesnenin gerçek yaratılması ile uğraşmayın. Bunun yerine, paylaşılan bir örneğe kontrollü bir erişim noktası seçerseniz, nesneye sert pişirme yapmazken, testler vb. Yazıyorsanız daha sonra daha mutlu zaman geçireceksiniz.
okülüs

12

Düzenleme: Bu uygulama ARC ile eski. Lütfen bir göz atın ARC ile uyumlu bir Objective-C singletonunu nasıl uygularım? doğru uygulama için.

Diğer cevaplarda okuduğum başlatmanın tüm uygulamaları ortak bir hatayı paylaşır.

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

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

Apple belgeleri, başlatma bloğunuzdaki sınıf türünü kontrol etmenizi önerir. Çünkü alt sınıflar varsayılan olarak başlatmayı çağırır. Alt sınıfların dolaylı olarak KVO aracılığıyla oluşturulabileceği açık olmayan bir durum vardır. Başka bir sınıfa aşağıdaki satırı eklerseniz:

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

Objective-C dolaylı olarak MySingletonClass alt sınıfını oluşturacak ve bunun ikinci bir tetiklenmesine neden olacaktır +initialize.

İnit bloğunuzda yinelenen başlatmayı dolaylı olarak kontrol etmeniz gerektiğini düşünebilirsiniz:

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

Ama kendini ayağından vuracaksın; ya da daha kötüsü, başka bir geliştiriciye kendilerini ayağa vurma fırsatı verin.

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

TL; DR, işte benim uygulamam

@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

(ZAssert'i kendi iddia makamımızla değiştirin veya yalnızca NSAssert'i kullanın.)


1
Ben sadece daha basit yaşayabilir ve tamamen başlatmaktan kaçınırdım.
Tom Andersen


9

Ben iş parçacığı güvenli, ancak başlatma sonra kilit değil sharedInstance ilginç bir varyasyonu var. Henüz üst cevabı istendiği gibi değiştirecek kadar emin değilim, ancak daha fazla tartışma için sunuyorum:

// 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 bu gerçekten ilginç. class_replaceMethodKlonuna sharedInstancedönüştürmek için kullanabilirim simpleSharedInstance. Bu şekilde bir @synchronizeddaha bir kilit almak konusunda endişelenmenize gerek kalmaz .
Dave DeLong

Aynı etki, exchangeImplementations kullanmak, sharedInstance'ı çağırdığınızda init'ten sonra gerçekten simpleSharedInstance'ı çağırdığınız anlamına gelir. Aslında replaceMethod ile başladım, ancak uygulamaları değiştirmenin daha iyi olduğuna karar verdim, böylece orijinal gerekirse hala var oldu ...
Kendall Helmstetter Gelner

Daha fazla testte, replaceMethod'un çalışmasını sağlayamadım - tekrarlanan çağrılarda, kod hala simpleSharedInstance yerine orijinal sharedInstance olarak adlandırıldı. Her ikisi de sınıf düzeyinde yöntemler olabilir düşünüyorum ... Ben kullanılan yerine: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); ve bunların bazı varyasyonları. Çalışmaları gönderdiğim kodu doğrulayabilir ve sharedSans aracılığıyla ilk geçişten sonra simpleSharedInstance çağrılır.
Kendall Helmstetter Gelner

Bir sürü çalışma zamanı mucking yapmadan başlatmadan sonra kilitleme maliyetlerini ödemeyen bir iş parçacığı güvenli sürümü yapabilirsiniz, aşağıda bir uygulama yayınladım.
Louis Gerbarg

1
+1 harika fikir. Ben sadece çalışma zamanı ile yapabileceği şeyleri seviyorum. Ancak çoğu durumda bu muhtemelen erken optimizasyondur. Senkronizasyon maliyetinden gerçekten kurtulmak zorunda kalsaydım, muhtemelen Louis'in kilitsiz versiyonunu kullanırdım.
Sven

6

Kısa cevap: Muhteşem.

Uzun cevap: Gibi bir şey ....

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

Neler olduğunu anlamak için / once.h başlıklarını okuduğunuzdan emin olun . Bu durumda başlık yorumları dokümanlar veya kılavuz sayfasından daha uygulanabilir.


5

Singleton'u bir sınıfa yuvarladım, böylece diğer sınıflar singleton özelliklerini devralabilir.

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

Ve burada tekil olmak istediğiniz bir sınıf örneği var.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

Singleton sınıfıyla ilgili tek sınırlama, NSObject alt sınıfı olmasıdır. Ama çoğu zaman benim kodda singletons kullanın aslında NSObject alt sınıfları, bu yüzden bu sınıf gerçekten hayatımı kolaylaştırmak ve kod temiz yapmak.


Başka bir kilitleme mekanizması kullanmak isteyebilirsiniz çünkü @synchronizedkorkunç derecede yavaştır ve kaçınılmalıdır.
DarkDust

2

Bu, çöp toplanmamış bir ortamda da çalışır.

@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

Bu güvenli değil ve ilk aramadan sonra pahalı kilitlemeden kaçınmalı mıydı?

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

2
Burada kullanılan çift kontrollu kilitleme tekniği bazı ortamlarda genellikle gerçek bir sorundur (bkz. Aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf veya Google bunu). Aksi gösterilinceye kadar, Objective-C'nin bağışıklık olmadığını varsayabilirim. Ayrıca bkz. Wincent.com/a/knowledge-base/archives/2006/01/… .
Steve Madsen


2

Peki ya

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Peki başlatmadan sonra senkronizasyon maliyetinden kaçınıyor musunuz?


Diğer cevaplardaki İkili Kontrol Kilitleme tartışmalarına bakın.
i_am_jorf


1

KLSingleton:

  1. Alt sınıflanabilir (n. Dereceye kadar)
  2. ARC uyumlu
  3. İle güvenli allocveinit
  4. Tembel olarak yüklendi
  5. Konu güvenli
  6. Kilitsiz (@synchronize değil + başlat, kullanır)
  7. Makro-free
  8. Swizzle içermeyen
  9. Basit

KLSingleton


1
Projem için NSSingleton'unuzu kullanıyorum ve KVO ile uyumsuz gibi görünüyor. Mesele KVO, NSKVONotifying_ MyClass ön ekiyle her KVO nesnesi için alt sınıf oluşturuyor . Ve MyClass + 'ın iki kez çağrılacak yöntemleri başlatmasını ve -init olmasını sağlar.
Oleg Trakhman

Bunu en son Xcode üzerinde test ettim ve KVO etkinliklerini kaydetme veya alma konusunda herhangi bir sorun yaşamadım. Bunu aşağıdaki kodla doğrulayabilirsiniz: gist.github.com/3065038 Twitter'da bahsettiğim gibi, + başlatma yöntemleri NSSingleton için bir kez ve her alt sınıf için bir kez çağrılır. Bu Objective-C'nin bir özelliğidir.
kevinlawler

Eğer eklerseniz NSLog(@"initialize: %@", NSStringFromClass([self class]));için +initializeyönteme Eğer sınıflar yalnızca bir kez başlatılır olduğunu doğrulayabilir.
kevinlawler

NSLog (@ "başlat:% @", NSStringFromClass ([kendi sınıfı]));
Oleg Trakhman

Ayrıca IB uyumlu olmasını da isteyebilirsiniz. Benimki: stackoverflow.com/questions/4609609/…
Dan Rosenstark

0

Kendini senkronize etmek istemiyorsun ... Kendinden nesne henüz mevcut olmadığından! Sonunda geçici bir kimlik değerine kilitlenirsiniz. Başka hiç kimsenin sınıf yöntemlerini (sharedInstance, ayırma, investWithZone: vb.) Çalıştıramayacağından emin olmak istersiniz, bu nedenle bunun yerine sınıf nesnesini senkronize etmeniz gerekir:

@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
Diğer yöntemler, erişim yöntemleri, mutasyon yöntemleri vb. Kendiliğinden senkronize olmalıdır. Tüm class (+) yöntemleri ve başlatıcılar (ve muhtemelen -dealloc) sınıf nesnesinde eşitlenmelidir. Accessor / mutator yöntemleri yerine Objective-C 2.0 özelliklerini kullanıyorsanız el ile eşitlemeniz gerekmez. Tüm object.property ve object.property = foo, kendiliğinden otomatik olarak senkronize edilir.
Rob Dotson

3
Lütfen selfnesnenin neden bir sınıf yönteminde olmadığını düşündüğünüzü açıklayın . Çalışma zamanı, hangi yöntemin uygulanacağını self, her yönteme (sınıf veya örnek) sağladığı değerle aynı olarak belirler .
dreamlax

2
Bir sınıf yönteminin içinde, self bir sınıf nesnesi. Kendiniz deneyin:#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

0

Bunu burada bırakmak istedim, bu yüzden kaybetmem. Bunun avantajı, BÜYÜK bir avantaj olan InterfaceBuilder'da kullanılabilir olmasıdır. Bu, sorduğum başka bir sorudan alındı :

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

Bu "soru" hakkında çok fazla yorum olduğunu biliyorum, ama pek çok insan singleton tanımlamak için bir makro kullanmayı öneren görmüyorum. Bu yaygın bir model ve bir makro singletonu büyük ölçüde basitleştiriyor.

İşte gördüğüm birkaç Objc uygulamasına dayanarak yazdığım makrolar.

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; \
}

Kullanım örneği:

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

Neredeyse boş olduğunda neden bir arayüz makrosu? Üstbilgi ve kod dosyaları arasındaki kod tutarlılığı; daha fazla otomatik yöntem eklemek veya bu yöntemi değiştirmek isterseniz

Burada en popüler cevap kullanılan (yazma sırasında) singleton oluşturmak için initialize yöntemini kullanıyorum.


0

Objective C sınıfı yöntemleri ile, sadece singleton desenini olağan şekilde kullanmaktan kaçınabiliriz:

[[Librarian sharedInstance] openLibrary]

için:

[Librarian openLibrary]

Sınıfı yalnızca Sınıf Yöntemleri olan başka bir sınıfın içine sararak , hiçbir şekilde örnek oluşturmadığımız için yanlışlıkla yinelenen örnekler oluşturma şansımız olmaz!

Burada daha ayrıntılı bir blog yazdım :)


Bağlantınız artık çalışmıyor.
i_am_jorf

0

@ Robbie-hanson'dan örneği genişletmek için ...

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

Yolum şöyle basit:

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;
}

Singleton zaten başlatılmışsa, LOCK bloğu girilmez. İkinci kontrol (! Başlatılmış), mevcut iş parçacığı KİLİT aldığında henüz başlatılmadığından emin olmak içindir.


initializedOlarak işaretlemenin volatileyeterli olduğu açık değildir . Bkz. Aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf .
i_am_jorf

0

Ben tüm çözümleri okumadım, bu kod gereksiz ise affet.

Bu benim görüşüme göre en güvenli iş parçacığı uygulamasıdır.

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

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

    return sharedResourcesObj;
}

-4

Genellikle Ben Hoffstein'ın cevabındakine benzer bir kod kullanıyorum (ki bu da Wikipedia'dan çıktım). Chris Hanson'un yorumunda belirtilen nedenlerden dolayı kullanıyorum.

Ancak, bazen bir NIB içine bir singleton koymaya ihtiyacım var ve bu durumda aşağıdakileri kullanıyorum:

@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

-retainYukarıdaki kod çöp toplanmış bir ortamda ihtiyacınız olsa da, (vb.) Uygulamasını okuyucuya bırakıyorum .


2
Kodunuz iş parçacığı için güvenli değil. Ayrılmış yöntemde senkronize kullanılır, ancak init yönteminde kullanılmaz. Başlatılan boolün kontrolü iplik için güvenli değildir.
Mecki

-5

Kabul edilen cevap, derlenmesine rağmen yanlıştır.

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

Apple belgelerine göre:

... Benzer bir yaklaşımı self yerine Class nesnesini kullanarak ilişkili sınıfın sınıf yöntemlerini senkronize etmek için kullanabilirsiniz.

Kendi kendine çalışıyor olsa bile, olmamalı ve bu bana bir kopyala yapıştır hatası gibi görünüyor. Bir sınıf fabrikası yöntemi için doğru uygulama:

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

6
kendini kesinlikle yapar bunu sınıf kapsamını mevcuttur. Sınıf örneği yerine sınıfa başvurur. Sınıflar (çoğunlukla) birinci sınıf nesnelerdir.
schwa

Neden @synchroninzed WITHIN yöntemini koyuyorsunuz?
user4951

1
Schwa önce de belirttiğimiz gibi, self olduğu bir sınıf yönteminin sınıf nesnesi içinde. Bunu gösteren bir pasaj için yorumuma bakın .
jscs

selfvar, ancak iletilen tanımlayıcı olarak kullanılması @synchronized, örneğin yöntemlerine erişimi senkronize edecektir. @ User490696'nın işaret ettiği gibi, sınıf nesnesinin kullanılmasının tercih edildiği durumlar vardır (teklitonlar gibi). Obj-C Programlama Kılavuzu'ndan: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.
12'de
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.