Buat singleton menggunakan dispatch_once GCD di Objective-C

341

Jika Anda dapat menargetkan iOS 4.0 atau lebih tinggi

Menggunakan GCD, apakah ini cara terbaik untuk membuat singleton di Objective-C (thread safe)?

+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
Ryan
sumber
2
Apakah ada cara untuk mencegah pengguna kelas memanggil alokasi / salinan?
Nicolas Miari
3
dispatch_once_t dan dispatch_once tampaknya telah diperkenalkan di 4.0, bukan 4.1 (lihat: developer.apple.com/library/ios/#documentation/Performance/… )
Ben Flynn
1
Metode ini menjadi bermasalah jika init membutuhkan penggunaan objek tunggal. Kode Matt Gallagher telah bekerja untuk saya lebih dari beberapa kali. cocoawithlove.com/2008/11/…
greg
1
Saya tahu ini tidak penting dalam contoh ini; tapi mengapa orang tidak menggunakan 'baru' lebih banyak. dispatch_once (& sekali, ^ {sharedInstance = [self new];} hanya terlihat sedikit lebih rapi. Ini sama dengan mengalokasikan + init.
Chris Hatton
3
Pastikan untuk mulai menggunakan jenis pengembalian instancetype. Kelengkapan kode jauh lebih baik saat menggunakan itu daripada id.
Tuan Rogers

Jawaban:

215

Ini adalah cara yang bisa diterima dan aman untuk membuat instance kelas Anda. Secara teknis mungkin bukan "singleton" (dalam hal itu hanya ada 1 objek), tetapi selama Anda hanya menggunakan [Foo sharedFoo]metode untuk mengakses objek, ini cukup baik.

Dave DeLong
sumber
4
Bagaimana Anda melepaskannya?
samvermette
65
@samvermette Anda tidak. Inti dari singleton adalah bahwa ia akan selalu ada. dengan demikian, Anda tidak melepaskannya, dan memori akan direklamasi dengan proses keluar.
Dave DeLong
6
@Dave DeLong: Menurut pendapat saya tujuan memiliki singleton bukanlah kepastian keabadiannya, tetapi kepastian bahwa kita memiliki satu contoh. Bagaimana jika singleton itu menurunkan semaphore? Anda tidak bisa sembarangan mengatakan bahwa itu akan selalu ada.
jacekmigacz
4
@hooleyhoop Ya, dalam dokumentasinya . "Jika dipanggil secara bersamaan dari beberapa utas, fungsi ini menunggu secara sinkron sampai blok selesai."
Kevin
3
@ WalterMartinVargas-Pena referensi kuat dipegang oleh variabel statis
Dave DeLong
36

jenis instancet

instancetypehanyalah salah satu dari banyak ekstensi bahasa Objective-C, dengan lebih banyak ditambahkan dengan setiap rilis baru.

Ketahuilah, menyukainya.

Dan anggap sebagai contoh bagaimana memperhatikan detail tingkat rendah dapat memberi Anda wawasan tentang cara-cara baru yang kuat untuk mengubah Objective-C.

Rujuk di sini: tipe 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;
}
Zelko
sumber
4
tip yang luar biasa, terima kasih! instancetype adalah kata kunci kontekstual yang dapat digunakan sebagai jenis hasil untuk memberi sinyal bahwa suatu metode mengembalikan jenis hasil yang terkait. ... Dengan instancetype, kompiler akan menyimpulkan dengan benar jenisnya.
Fattie
1
Tidak jelas bagi saya apa yang dimaksud kedua cuplikan di sini, apakah keduanya setara satu sama lain? Yang satu lebih disukai dari yang lain? Alangkah baiknya jika penulis dapat menambahkan sedikit penjelasan untuk ini.
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
Sergey Petruk
sumber
Bagaimana init tidak tersedia? Setidaknya itu tidak tersedia untuk satu init?
Sayang
2
Singleton seharusnya hanya memiliki satu titik akses. Dan poin ini adalah shared Instance. Jika kita memiliki metode init dalam file * .h daripada Anda dapat membuat instance singleton lain. Ini bertentangan dengan definisi singleton.
Sergey Petruk
1
@ asma22 __attribute __ ((tidak tersedia ()) membuat tidak tersedia untuk menggunakan metode ini. Jika programmer lain ingin menggunakan metode yang ditandai sebagai tidak tersedia, ia mendapatkan kesalahan
Sergey Petruk
1
Saya benar-benar mengerti dan saya senang telah mempelajari sesuatu yang baru, tidak ada yang salah dengan jawaban Anda, mungkin sedikit membingungkan bagi para pemula ...
Honey
1
Ini hanya berfungsi untuk MySingleton, misalnya dalam MySingleton.mpanggilan saya[super alloc]
Sergey Petruk
6

Anda dapat menghindari bahwa kelas dialokasikan dengan menimpa metode alokasi.

@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;
}
i-pengembang
sumber
1
Ini menjawab pertanyaan saya di komentar di atas. Bukannya aku terlalu banyak untuk pemrograman defensif, tapi ...
Nicolas Miari
5

Dave benar, itu baik-baik saja. Anda mungkin ingin memeriksa dokumen Apple tentang cara membuat singleton untuk kiat menerapkan beberapa metode lain untuk memastikan bahwa hanya satu yang dapat dibuat jika kelas memilih TIDAK untuk menggunakan metode sharedFoo.

Kristen
sumber
8
eh ... itu bukan contoh terbaik menciptakan singleton. Mengganti metode manajemen memori tidak diperlukan.
Dave DeLong
19
Ini sama sekali tidak valid menggunakan ARC.
logancautrell
Dokumen yang dikutip sejak itu telah pensiun. Juga, jawaban yang hanya tautan ke konten eksternal umumnya adalah jawaban SO yang buruk. Pada kutipan minimum, bagian yang relevan dalam jawaban Anda. Jangan repot-repot di sini kecuali Anda ingin cara lama disimpan untuk anak cucu.
toolbear
4

Jika Anda ingin memastikan bahwa [[alokasi MyClass] init] mengembalikan objek yang sama dengan sharedInstance (tidak diperlukan menurut pendapat saya, tetapi beberapa orang menginginkannya), itu dapat dilakukan dengan sangat mudah dan aman menggunakan dispatch_once kedua:

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

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

    return sharedInstance;
}

Ini memungkinkan setiap kombinasi [[alokasi MyClass] init] dan [MyClass sharedInstance] untuk mengembalikan objek yang sama; [MyClass sharedInstance] hanya akan sedikit lebih efisien. Cara kerjanya: [MyClass sharedInstance] akan memanggil [[alokasi MyClass] init] sekali. Kode lain bisa memanggilnya juga, berapa kali. Penelepon pertama ke init akan melakukan inisialisasi "normal" dan menyimpan objek tunggal dalam metode init. Setiap panggilan selanjutnya ke init akan sepenuhnya mengabaikan alokasi apa yang dikembalikan dan mengembalikan Instansi yang sama; hasil alokasi akan dibatalkan alokasi.

Metode + sharedInstance akan berfungsi seperti biasa. Jika itu bukan penelepon pertama yang memanggil [[alokasi MyClass] init], maka hasil dari init bukanlah hasil dari panggilan alokasi, tetapi itu OK.

gnasher729
sumber
2

Anda bertanya apakah ini "cara terbaik untuk membuat singleton".

Beberapa pemikiran:

  1. Pertama, ya, ini adalah solusi yang aman. dispatch_oncePola ini adalah cara modern yang aman untuk menghasilkan lajang di Objective-C. Jangan khawatir di sana.

  2. Namun Anda bertanya, apakah ini cara "terbaik" untuk melakukannya. Orang harus mengakui, bahwa, instancetypedan [[self alloc] init]berpotensi menyesatkan ketika digunakan bersama dengan lajang.

    Keuntungannya instancetypeadalah bahwa ini adalah cara yang tidak ambigu untuk menyatakan bahwa kelas dapat subkelas tanpa menggunakan jenis id, seperti yang harus kita lakukan di masa lalu.

    Tetapi staticdalam metode ini menyajikan tantangan subclassing. Bagaimana jika ImageCachedan BlobCachelajang keduanya subclass dari Cachesuperclass tanpa menerapkan sharedCachemetode mereka sendiri ?

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

    Agar ini berfungsi, Anda harus memastikan subclass mengimplementasikan sharedInstancemetode mereka sendiri (atau apa pun namanya untuk kelas Anda).

    Intinya, sumber asli Anda sharedInstance sepertinya akan mendukung subclass, tetapi tidak. Jika Anda bermaksud mendukung subclassing, paling tidak sertakan dokumentasi yang memperingatkan pengembang masa depan bahwa mereka harus mengganti metode ini.

  3. Untuk interoperabilitas terbaik dengan Swift, Anda mungkin ingin mendefinisikan ini sebagai properti, bukan metode kelas, misalnya:

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

    Kemudian Anda dapat melanjutkan dan menulis getter untuk properti ini (implementasinya akan menggunakan dispatch_oncepola yang Anda sarankan):

    + (Foo *)sharedFoo { ... }

    Manfaatnya adalah jika pengguna Swift menggunakannya, mereka akan melakukan sesuatu seperti:

    let foo = Foo.shared

    Catatan, tidak ada (), karena kami menerapkannya sebagai properti. Mulai Swift 3, beginilah cara lajang umumnya diakses. Jadi mendefinisikannya sebagai properti membantu memfasilitasi interoperabilitas itu.

    Sebagai tambahan, jika Anda melihat bagaimana Apple mendefinisikan lajang mereka, ini adalah pola yang telah mereka adopsi, misalnya NSURLSessionlajang mereka didefinisikan sebagai berikut:

    @property (class, readonly, strong) NSURLSession *sharedSession;
  4. Pertimbangan interoperabilitas Swift yang sangat kecil adalah nama singleton. Lebih baik jika Anda dapat memasukkan nama tipe, daripada sharedInstance. Misalnya, jika kelasnya adalah Foo, Anda mungkin mendefinisikan properti singleton sebagai sharedFoo. Atau jika kelasnya adalah DatabaseManager, Anda dapat menghubungi properti sharedManager. Kemudian pengguna Swift dapat melakukan:

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

    Jelas, jika Anda benar-benar ingin menggunakan sharedInstance, Anda selalu dapat mendeklarasikan nama Swift jika Anda ingin:

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

    Jelas, ketika menulis kode Objective-C, kita seharusnya tidak membiarkan interoperabilitas Swift lebih besar daripada pertimbangan desain lainnya, tetapi tetap saja, jika kita dapat menulis kode yang dengan anggun mendukung kedua bahasa, itu lebih disukai.

  5. Saya setuju dengan orang lain yang menunjukkan bahwa jika Anda ingin ini menjadi singleton sejati di mana pengembang tidak dapat / tidak seharusnya (secara tidak sengaja) membuat instantiate instance mereka sendiri, unavailablekualifikasi initdan newbijaksana.

rampok
sumber
0

Untuk membuat thread safe singleton, Anda dapat melakukannya seperti ini:

@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

dan blog ini menjelaskan singleton dengan sangat baik di objc / cocoa

Hancock_Xu
sumber
Anda menautkan ke artikel yang sangat lama sementara OP meminta karakteristik tentang implementasi paling modern.
vikingosegundo
1
Pertanyaannya adalah tentang implementasi spesifik. Anda cukup memposting implementasi lain. Di sana-untuk Anda bahkan tidak berusaha menjawab pertanyaan.
vikingosegundo
1
@vikingosegundo Penanya bertanya apakah GCD adalah cara terbaik untuk membuat singleton Thread aman, jawaban saya memberi pilihan lain. Ada yang salah?
Hancock_Xu
penanya bertanya apakah implementasi tertentu aman-thread. dia tidak meminta opsi.
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;
}
Rohit Kashyap
sumber
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;
}
Nayab Muhammad
sumber