Mengapa Apple merekomendasikan untuk menggunakan dispatch_once untuk menerapkan pola tunggal di bawah ARC?

305

Apa alasan yang tepat untuk menggunakan dispatch_once di accessor instance bersama dari singleton di bawah ARC?

+ (MyClass *)sharedInstance
{
    //  Static local predicate must be initialized to 0
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

Bukankah itu ide buruk untuk membuat singleton secara asinkron di latar belakang? Maksud saya apa yang terjadi jika saya meminta instance bersama itu dan segera mengandalkannya, tetapi dispatch_once membutuhkan waktu hingga Natal untuk membuat objek saya? Itu tidak segera kembali kan? Setidaknya itu tampaknya menjadi inti dari Grand Central Dispatch.

Jadi mengapa mereka melakukan ini?

Anggota yang Bangga
sumber
Note: static and global variables default to zero.
ikkentim

Jawaban:

418

dispatch_once()benar-benar sinkron. Tidak semua metode GCD melakukan hal-hal secara asinkron (dalam hal ini, dispatch_sync()sinkron). Penggunaan dispatch_once()menggantikan idiom berikut:

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

Manfaat dispatch_once()lebih dari ini adalah lebih cepat. Ini juga semantik bersih, karena juga melindungi Anda dari beberapa utas yang melakukan alokasi init Instance Anda bersama - jika mereka semua mencoba pada waktu yang tepat. Itu tidak akan membiarkan dua instance dibuat. Gagasan keseluruhan dispatch_once()adalah "melakukan sesuatu sekali dan hanya sekali", itulah tepatnya yang kami lakukan.

Lily Ballard
sumber
4
Demi argumen saya perlu mencatat bahwa dokumentasi tidak mengatakan itu sedang dijalankan secara serempak. Ia hanya mengatakan bahwa beberapa pemanggilan simultan akan diserialisasi.
ahli
5
Anda mengklaim bahwa ini lebih cepat - seberapa cepat? Saya tidak punya alasan untuk berpikir Anda tidak mengatakan yang sebenarnya, tetapi saya ingin melihat tolok ukur sederhana.
Joshua Gross
29
Saya baru saja melakukan benchmark sederhana (pada iPhone 5) dan sepertinya dispatch_once sekitar 2x lebih cepat dari @sinkronisasi.
Joshua Gross
3
@ReneDohan: Jika Anda 100% yakin bahwa tidak ada yang pernah memanggil metode itu dari utas yang berbeda, maka itu berfungsi. Tetapi menggunakan dispatch_once()itu sangat sederhana (terutama karena Xcode bahkan akan secara otomatis melengkapinya menjadi potongan kode lengkap untuk Anda) dan berarti Anda bahkan tidak pernah harus mempertimbangkan apakah metode ini perlu thread-safe.
Lily Ballard
1
@siuying: Sebenarnya, itu tidak benar. Pertama, apa pun yang dilakukan +initializeterjadi sebelum kelas disentuh, bahkan jika Anda belum mencoba membuat instance bersama Anda. Secara umum, inisialisasi malas (menciptakan sesuatu hanya bila diperlukan) lebih baik. Kedua, bahkan klaim kinerja Anda tidak benar. dispatch_once()Dibutuhkan hampir persis jumlah waktu yang sama mengatakan if (self == [MyClass class])dalam +initialize. Jika Anda sudah memiliki +initialize, maka ya membuat instance bersama ada lebih cepat, tetapi sebagian besar kelas tidak.
Lily Ballard
41

Karena itu hanya akan berjalan sekali. Jadi, jika Anda mencoba dan mengaksesnya dua kali dari utas yang berbeda itu tidak akan menimbulkan masalah.

Mike Ash memiliki deskripsi lengkap dalam posting blog Care and Feeding of Singletons .

Tidak semua blok GCD dijalankan secara tidak sinkron.

Abizern
sumber
Lily adalah jawaban yang lebih baik, tetapi saya meninggalkan milik saya untuk menjaga tautan ke pos Mike Ash.
Abizern