Seperti apa bentuk singleton Objective-C saya? [Tutup]


334

Metode pengakses singleton saya biasanya beberapa varian:

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Apa yang bisa saya lakukan untuk memperbaiki ini?


27
Apa yang Anda miliki baik-baik saja, meskipun Anda bisa memindahkan deklarasi variabel global ke metode + instance Anda (satu-satunya tempat yang perlu digunakan, kecuali Anda mengizinkannya diatur juga) dan menggunakan nama seperti + defaultMyClass atau + sharedMyClass untuk metode Anda. + instance tidak mengungkapkan maksud.
Chris Hanson

Karena tidak mungkin 'jawaban' untuk pertanyaan ini akan berubah dalam waktu dekat, saya menempatkan kunci historis pada pertanyaan tersebut. Dua alasan 1) Banyak tampilan, suara, dan konten yang bagus 2) Untuk mencegah Anda membuka / menutup. Itu adalah pertanyaan yang bagus untuk waktunya, tetapi pertanyaan-pertanyaan semacam ini tidak sesuai untuk Stack Overflow. Kami sekarang memiliki Tinjauan Kode untuk memeriksa kode kerja. Silakan bawa semua diskusi tentang pertanyaan ini ke pertanyaan meta ini .
George Stocker

Jawaban:


207

Pilihan lain adalah menggunakan +(void)initializemetode ini. Dari dokumentasi:

Runtime mengirimkan initializeke setiap kelas dalam suatu program tepat satu kali tepat sebelum kelas, atau kelas apa pun yang mewarisi darinya, dikirim pesan pertama dari dalam program. (Dengan demikian metode ini mungkin tidak pernah dipanggil jika kelas tidak digunakan.) Runtime mengirimkan initializepesan ke kelas dengan cara yang aman. Superclasses menerima pesan ini sebelum subclass mereka.

Jadi Anda bisa melakukan sesuatu yang mirip dengan ini:

static MySingleton *sharedSingleton;

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

7
Jika runtime hanya akan memanggil ini sekali, apa yang dilakukan BOOL? Apakah itu tindakan pencegahan jika seseorang memanggil fungsi ini secara eksplisit dari kode mereka?
Aftermathew

5
Ya, ini adalah tindakan pencegahan karena fungsinya juga dapat dipanggil langsung.
Robbie Hanson

33
Ini juga diperlukan karena mungkin ada subkelas. Jika mereka tidak mengabaikan +initializeimplementasi superclasses mereka akan dipanggil jika subclass pertama kali digunakan.
Sven

3
@ Paul Anda dapat mengganti releasemetode dan membuatnya kosong. :)

4
@aryaxt: Dari dokumen yang tercantum, ini sudah aman untuk thread. Jadi, panggilan itu sekali per runtime - periode. Ini tampaknya menjadi solusi yang benar, aman, dan efisien secara optimal.
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

[Sumber]


7
Ini semua yang biasanya harus Anda gunakan untuk lajang. Antara lain, menjaga kelas Anda terpisah instantiable membuat mereka lebih mudah untuk diuji, karena Anda dapat menguji instance terpisah daripada memiliki cara untuk mengatur ulang keadaan mereka.
Chris Hanson

3
Stig Brautaset: Tidak, tidak boleh meninggalkan @sinkronisasi dalam contoh ini. Itu ada untuk menangani kemungkinan kondisi perlombaan dari dua utas yang menjalankan fungsi statis ini pada saat yang sama, keduanya melewati uji "if (! SharedSingleton)" pada saat yang sama, dan dengan demikian menghasilkan dua [alokasi MySingleton]. .. @synchronized {scope block} memaksa utas kedua hipotetis untuk menunggu utas pertama keluar dari {scope block} sebelum diizinkan melanjutkan ke dalamnya. Saya harap ini membantu! =)
MechEthan

3
Apa yang menghentikan seseorang untuk tetap membuat instance objeknya sendiri? MySingleton *s = [[MySingelton alloc] init];
lindon fox

1
@ lindonfox Apa jawaban untuk pertanyaan Anda?
Raffi Khatchadourian

1
@Raffi - maaf saya pikir saya pasti lupa menempelkan jawaban saya. Ngomong-ngomong, saya mendapatkan buku Pro Objective-C Design Patterns for iOSitu dan menjelaskan bagaimana Anda membuat singelton "ketat". Pada dasarnya karena Anda tidak dapat membuat metode inisiasi pribadi, Anda perlu mengganti metode alokasi dan salin. Jadi, jika Anda mencoba dan melakukan sesuatu seperti [[MySingelton alloc] init]Anda akan mendapatkan error run time (meskipun bukan kesalahan waktu kompilasi). Saya tidak mengerti bagaimana semua detail dari penciptaan objek, tetapi Anda menerapkan + (id) allocWithZone:(NSZone *)zoneyang disebut insharedSingleton
lindon fox

59

Per jawaban saya yang lain di bawah ini, saya pikir Anda harus melakukan:

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

6
Jangan repot-repot dengan semua yang Anda lakukan di atas. Jadikan (semoga sangat sedikit) lajang Anda secara terpisah-instantiable, dan cukup gunakan metode berbagi / default. Apa yang telah Anda lakukan hanya diperlukan jika Anda benar-benar, HANYA menginginkan satu contoh kelas Anda. Yang tidak Anda lakukan, khususnya. untuk pengujian unit.
Chris Hanson

Masalahnya adalah ini adalah kode sampel Apple untuk "menciptakan singleton". Tapi ya, Anda memang benar.
Colin Barrett

1
Kode sampel Apple benar jika Anda menginginkan singleton "benar" (yaitu objek yang hanya dapat dipakai satu kali, selamanya) tetapi seperti yang dikatakan Chris, ini jarang yang Anda inginkan atau butuhkan sedangkan beberapa jenis contoh bersama yang dapat diatur adalah yang Anda biasanya mau.
Luke Redpath

Berikut ini adalah makro untuk metode di atas: gist.github.com/1057420 . Inilah yang saya gunakan.
Kobski

1
Selain unit test, tidak ada yang menentang solusi ini, benar? Dan itu cepat dan aman.
LearnCocos2D

58

Karena Kendall memposting threadsafe singleton yang berusaha untuk menghindari biaya penguncian, saya pikir saya akan melemparkannya juga:

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

Oke, izinkan saya menjelaskan cara kerjanya:

  1. Kasus cepat: Dalam eksekusi normal sharedInstancetelah ditetapkan, sehingga whileloop tidak pernah dieksekusi dan fungsi kembali setelah hanya menguji keberadaan variabel;

  2. Kasing lambat: Jika sharedInstancetidak ada, maka sebuah instance dialokasikan dan disalin ke dalamnya menggunakan Compare And Swap ('CAS');

  3. Contended case: Jika dua utas keduanya mencoba menelepon sharedInstancepada waktu yang sama DAN sharedInstance tidak ada pada saat yang sama, maka keduanya akan menginisialisasi instance baru dari singleton dan mencoba untuk CAS ke posisi. Yang mana saja yang memenangkan CAS, segera kembali, yang mana yang kalah akan melepaskan instance yang baru saja dialokasikan dan mengembalikan (yang sudah ditetapkan) sharedInstance. Single ini OSAtomicCompareAndSwapPtrBarrierbertindak sebagai penghalang tulis untuk utas pengaturan dan pembatas baca dari utas pengujian.


18
Ini adalah kerja keras yang lengkap untuk paling banyak satu kali itu bisa terjadi selama masa aplikasi. Namun demikian, ini tepat, dan teknik bandingkan-dan-tukar adalah alat yang berguna untuk diketahui, jadi +1.
Steve Madsen

Jawaban yang bagus - keluarga OSAtomic adalah hal yang baik untuk diketahui
Bill

1
@ Louis: Luar biasa, jawaban yang benar-benar mencerahkan! Namun satu pertanyaan: apa yang harus saya initlakukan dengan metode saya dalam pendekatan Anda? Melontar pengecualian saat sharedInstancediinisialisasi bukan ide yang baik, saya percaya. Apa yang harus dilakukan untuk mencegah panggilan pengguna initsecara langsung berkali-kali?
matm

2
Saya biasanya tidak mencegahnya. Sering ada alasan yang sah untuk memungkinkan apa yang umumnya tunggal untuk berlipat ganda, yang paling umum adalah untuk jenis pengujian unit tertentu. Jika saya benar-benar ingin menegakkan satu contoh saya mungkin akan memiliki metode init memeriksa untuk melihat apakah ada global, dan jika memang ada saya memilikinya melepaskan diri dan mengembalikan global.
Louis Gerbarg

1
@Tony agak terlambat dari respons, tetapi OSAtomicCompareAndSwapPtrBarrier membutuhkan volatile. Mungkin kata kunci yang mudah menguap adalah untuk menjaga kompiler tidak mengoptimalkan cek? Lihat: stackoverflow.com/a/5334727/449161 dan developer.apple.com/library/mac/#documentation/Darwin/Reference/…
Ben Flynn

14
static MyClass * sharedInst = nil;

+ (id) BerbagiInstance
{
    @sinkronkan (mandiri) {
        if (sharedInst == nil) {
            / * sharedInst diatur di init * /
            [[alokasi mandiri] init];
        }
    }
    return sharedInst;
}

- (id) init
{
    if (sharedInst! = nil) {
        [NSException kenaikan: NSInternalInconsistencyException
            format: @ "[% @% @] tidak dapat dipanggil; gunakan + [% @% @] sebagai gantinya"],
            NSStringFromClass ([kelas mandiri]), NSStringFromSelector (_cmd), 
            NSStringFromClass ([kelas mandiri]),
            NSStringFromSelector (@selector (sharedInstance) "];
    } lain jika (self = [super init]) {
        sharedInst = diri;
        / * Apapun kelas khusus di sini * /
    }
    return sharedInst;
}

/ * Ini mungkin tidak ada artinya
   aplikasi GC. Menyimpan singleton
   sebagai singleton aktual dalam sebuah
   aplikasi non CG
* /
- (NSUInteger) retainCount
{
    mengembalikan NSUIntegerMax;
}

- Pelepasan (oneway void)
{
}

- (id) simpan
{
    return sharedInst;
}

- (id) autorelease
{
    return sharedInst;
}

3
Saya perhatikan bahwa dentang mengeluh tentang kebocoran jika Anda tidak menetapkan hasil [[self alloc] init]untuk sharedInst.
Pix0r

Mengubur init seperti ini adalah pendekatan IMO yang cukup jelek. Jangan main-main dengan init dan / atau pembuatan objek yang sebenarnya. Jika Anda memilih titik terkontrol akses ke instance bersama, sementara tidak menyulitkan singleton ke objek, Anda akan memiliki waktu yang lebih bahagia nanti jika menulis tes dll. Singleton keras terlalu banyak digunakan.
occulus

12

Sunting: Implementasi ini usang dengan ARC. Silakan lihat Bagaimana cara mengimplementasikan singleton Objective-C yang kompatibel dengan ARC? untuk implementasi yang benar.

Semua implementasi inisialisasi yang saya baca di jawaban lain berbagi kesalahan umum.

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

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

Dokumentasi Apple merekomendasikan Anda memeriksa jenis kelas di blok inisialisasi Anda. Karena subclass memanggil inisialisasi secara default. Ada kasus yang tidak jelas di mana subclass dapat dibuat secara tidak langsung melalui KVO. Karena jika Anda menambahkan baris berikut di kelas lain:

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

Objective-C secara implisit akan membuat subkelas MySingletonClass yang menghasilkan pemicu kedua +initialize.

Anda mungkin berpikir bahwa Anda harus secara implisit memeriksa inisialisasi duplikat di blok init Anda seperti itu:

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

Tapi Anda akan menembak diri sendiri di kaki; atau lebih buruk memberi pengembang lain kesempatan untuk menembak diri mereka sendiri.

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

TL; DR, ini implementasi saya

@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

(Ganti ZAssert dengan makro pernyataan kami sendiri; atau hanya NSAssert.)


1
Saya hanya akan hidup lebih sederhana dan menghindari inisialisasi sama sekali.
Tom Andersen


9

Saya memiliki variasi yang menarik tentang sharedInstance yang aman, tetapi tidak mengunci setelah inisialisasi. Saya belum cukup yakin untuk memodifikasi jawaban atas seperti yang diminta, tetapi saya menyajikannya untuk diskusi lebih lanjut:

// 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 benar-benar menarik. Saya mungkin menggunakan class_replaceMethoduntuk berubah sharedInstancemenjadi tiruan dari simpleSharedInstance. Dengan begitu Anda tidak perlu khawatir tentang mendapatkan @synchronizedkunci lagi.
Dave DeLong

Efeknya sama, menggunakan exchangeImplementations berarti bahwa setelah init ketika Anda memanggil sharedInstance, Anda benar-benar memanggil simpleSharedInstance. Saya benar-benar mulai dengan replaceMethod, tetapi memutuskan lebih baik untuk hanya beralih implementasi sehingga yang asli masih ada jika diperlukan ...
Kendall Helmstetter Gelner

Dalam pengujian lebih lanjut, saya tidak bisa mendapatkan replaceMethod untuk bekerja - dalam panggilan berulang, kode masih memanggil sharedInstance asli, bukan simpleSharedInstance. Saya pikir itu mungkin karena mereka berdua metode tingkat kelas ... Pengganti yang saya gunakan adalah: class_replaceMethod (self, origSel, method_getImplementation (newMethod), method_getTypeEncoding (newMethod)); dan beberapa variasi daripadanya. Saya dapat memverifikasi kode yang saya posting berhasil dan simpleSharedInstance dipanggil setelah melewati sharedInstance pertama.
Kendall Helmstetter Gelner

Anda dapat membuat versi aman utas yang tidak membayar biaya penguncian setelah inisialisasi tanpa melakukan banyak penghancuran runtime, saya telah memposting implementasi di bawah ini.
Louis Gerbarg

1
+1 ide bagus. Saya suka hal-hal yang bisa dilakukan dengan runtime. Tetapi dalam kebanyakan kasus ini mungkin adalah optimasi prematur. Jika saya benar-benar harus menyingkirkan biaya sinkronisasi saya mungkin akan menggunakan versi tanpa kunci oleh Louis.
Sven

6

Jawaban singkat: Luar biasa.

Jawaban panjang: Sesuatu seperti ....

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

Pastikan untuk membaca tajuk pengiriman / once.h untuk memahami apa yang terjadi. Dalam hal ini komentar header lebih berlaku daripada halaman dokumen atau manual.


5

Saya telah memasukkan singleton ke dalam sebuah kelas, sehingga kelas-kelas lain dapat mewarisi sifat-sifat singleton.

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

Dan di sini adalah contoh dari beberapa kelas, bahwa Anda ingin menjadi singleton.

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

Satu-satunya batasan tentang kelas Singleton, adalah bahwa itu adalah subclass NSObject. Tetapi sebagian besar waktu saya menggunakan lajang dalam kode saya mereka sebenarnya subclass NSObject, sehingga kelas ini benar-benar memudahkan hidup saya dan membuat kode lebih bersih.


Anda mungkin ingin menggunakan beberapa mekanisme penguncian lain karena @synchronizedsangat lambat dan harus dihindari.
DarkDust

2

Ini juga berfungsi di lingkungan yang dikumpulkan tanpa sampah.

@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

Tidakkah ini akan menjadi threadsafe dan menghindari penguncian yang mahal setelah panggilan pertama?

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

2
Teknik penguncian ganda yang digunakan di sini sering merupakan masalah nyata di beberapa lingkungan (lihat aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf atau Google it). Sampai ditunjukkan sebaliknya, saya berasumsi bahwa Objective-C tidak kebal. Lihat juga wincent.com/a/knowledge-base/archives/2006/01/… .
Steve Madsen


2

Bagaimana tentang

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

Jadi Anda menghindari biaya sinkronisasi setelah inisialisasi?


Lihat diskusi tentang Penguncian Ganda Terdaftar dalam jawaban lain.
i_am_jorf


1

KLSingleton adalah:

  1. Subclassible (ke tingkat ke-n)
  2. Kompatibel dengan ARC
  3. Aman dengan allocdaninit
  4. Dimuat dengan malas
  5. Aman untuk benang
  6. Bebas kunci (menggunakan + inisialisasi, bukan @sinkronkan)
  7. Bebas makro
  8. Bebas dari swizzle
  9. Sederhana

KLSingleton


1
Saya menggunakan NSSingleton Anda untuk proyek saya, dan tampaknya tidak kompatibel dengan KVO. Masalahnya adalah bahwa KVO membuat subkelas untuk setiap objek KVO dengan awalan NSKVONotifying_ MyClass . Dan itu membuat MyClass + menginisialisasi dan metode -init dipanggil dua kali.
Oleg Trakhman

Saya menguji ini pada Xcode terbaru dan tidak mengalami kesulitan mendaftar atau menerima acara KVO. Anda dapat memverifikasi ini dengan kode berikut: gist.github.com/3065038 Seperti yang saya sebutkan di Twitter, metode + inisialisasi dipanggil sekali untuk NSSingleton dan sekali untuk setiap subkelas. Ini adalah properti dari Objective-C.
kevinlawler

Jika Anda menambahkan NSLog(@"initialize: %@", NSStringFromClass([self class]));ke +initializemetode Anda dapat memverifikasi bahwa kelas diinisialisasi hanya sekali.
kevinlawler

NSLog (@ "inisialisasi:% @", NSStringFromClass ([kelas mandiri]));
Oleg Trakhman

Anda mungkin ingin juga itu kompatibel dengan IB.
Milik

0

Anda tidak ingin menyinkronkan diri ... Karena objek diri belum ada! Anda akhirnya mengunci nilai id sementara. Anda ingin memastikan bahwa tidak ada orang lain yang dapat menjalankan metode kelas (sharedInstance, alokasi, alokasiWithZone :, dll), jadi Anda perlu menyinkronkan objek kelas:

@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
Sisa dari metode, metode accessor, metode mutator, dll harus disinkronkan pada diri sendiri. Semua metode kelas dan inisialisasi (dan mungkin -dealloc) harus disinkronkan pada objek kelas. Anda dapat menghindari sinkronisasi secara manual jika Anda menggunakan properti Objective-C 2.0 alih-alih metode accessor / mutator. Semua object.property dan object.property = foo, secara otomatis disinkronkan ke diri.
Rob Dotson

3
Tolong jelaskan mengapa Anda berpikir bahwa selfobjek tidak ada dalam metode kelas. Runtime menentukan implementasi metode mana yang akan dipanggil berdasarkan nilai yang sama persis yang disediakan selfuntuk setiap metode (kelas atau contoh).
dreamlax

2
Di dalam metode kelas, self adalah objek kelas. Coba sendiri:#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

Hanya ingin meninggalkan ini di sini jadi saya tidak kehilangan itu. Keuntungan yang satu ini adalah dapat digunakan di InterfaceBuilder, yang merupakan keuntungan BESAR. Ini diambil dari pertanyaan lain yang saya ajukan :

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

Saya tahu ada banyak komentar tentang "pertanyaan" ini, tetapi saya tidak melihat banyak orang menyarankan menggunakan makro untuk mendefinisikan singleton. Ini adalah pola umum dan makro sangat menyederhanakan singleton.

Berikut adalah makro yang saya tulis berdasarkan beberapa implementasi Objc yang pernah saya lihat.

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

Contoh penggunaan:

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

Mengapa antarmuka makro saat hampir kosong? Konsistensi kode antara file header dan kode; rawatan jika Anda ingin menambahkan lebih banyak metode otomatis atau mengubahnya.

Saya menggunakan metode inisialisasi untuk membuat singleton seperti yang digunakan dalam jawaban paling populer di sini (saat penulisan).


0

Dengan metode kelas Objective C, kita bisa menghindari menggunakan pola singleton dengan cara biasa, dari:

[[Librarian sharedInstance] openLibrary]

untuk:

[Librarian openLibrary]

dengan membungkus kelas di dalam kelas lain yang hanya memiliki Metode Kelas , dengan cara itu tidak ada kesempatan untuk membuat instance duplikat, karena kita tidak membuat instance apa pun!

Saya menulis blog yang lebih rinci di sini :)


Tautan Anda tidak lagi berfungsi.
i_am_jorf

0

Untuk memperluas contoh dari @ 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

Cara saya sederhana seperti ini:

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

Jika singleton sudah diinisialisasi, blok LOCK tidak akan dimasukkan. Pemeriksaan kedua jika (! Diinisialisasi) adalah untuk memastikan belum diinisialisasi ketika utas saat ini memperoleh KUNCI.


Tidak jelas yang menandai initializedsebagai volatilecukup. Lihat aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf .
i_am_jorf

0

Saya belum membaca semua solusinya, jadi maafkan jika kode ini berlebihan.

Ini adalah implementasi yang paling aman menurut saya.

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

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

    return sharedResourcesObj;
}

-4

Saya biasanya menggunakan kode yang kira-kira mirip dengan jawaban Ben Hoffstein (yang juga saya dapatkan dari Wikipedia). Saya menggunakannya untuk alasan yang dinyatakan oleh Chris Hanson dalam komentarnya.

Namun, kadang-kadang saya memiliki kebutuhan untuk menempatkan singleton ke NIB, dan dalam hal ini saya menggunakan yang berikut:

@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

Saya menyerahkan implementasi -retain(dll) kepada pembaca, meskipun kode di atas adalah semua yang Anda butuhkan di lingkungan sampah yang dikumpulkan.


2
Kode Anda tidak aman untuk thread. Ini menggunakan disinkronkan dalam metode alokasi, tetapi tidak dalam metode init. Memeriksa bool yang diinisialisasi tidak aman untuk thread.
Mecki

-5

Jawaban yang diterima, meskipun dikompilasi, salah.

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

Per dokumentasi Apple:

... Anda dapat mengambil pendekatan serupa untuk menyinkronkan metode kelas dari kelas terkait, menggunakan objek Kelas bukan diri.

Bahkan jika menggunakan karya sendiri, seharusnya tidak dan ini terlihat seperti kesalahan salin dan tempel ke saya. Implementasi yang benar untuk metode pabrik kelas adalah:

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

6
diri tentu saja ada ruang lingkup kelasnya. Itu merujuk ke kelas bukan contoh kelas. Kelas adalah (kebanyakan) objek kelas satu.
schwa

Mengapa Anda memasukkan @sinkronkan dalam suatu metode?
user4951

1
Seperti yang sudah dikatakan schwa, self adalah objek kelas di dalam metode kelas. Lihat komentar saya untuk cuplikan yang menunjukkan ini.
jscs

selfada, tetapi menggunakannya sebagai pengenal yang diteruskan ke @synchronizedakan menyinkronkan akses ke metode instance. Sebagai @ user490696 tunjukkan, ada kasus (seperti lajang) di mana menggunakan objek kelas lebih disukai. Dari Panduan Pemrograman 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.
quellish
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.