Bagaimana cara menerapkan singleton Objective-C yang kompatibel dengan ARC?


172

Bagaimana cara mengonversi (atau membuat) kelas tunggal yang mengkompilasi dan berperilaku dengan benar ketika menggunakan penghitungan referensi otomatis (ARC) di Xcode 4.2?


1
Baru-baru ini saya menemukan Artikel dari Matt Galloway yang cukup mendalam tentang Singletons untuk lingkungan manajemen memori manual dan ARC. galloway.me.uk/tutorials/singleton-classes
cescofry

Jawaban:


391

Dengan cara yang persis sama dengan yang Anda (seharusnya) sudah lakukan:

+ (instancetype)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

9
Anda hanya tidak melakukan salah satu dari manajemen memori tipu muslihat Apple digunakan untuk merekomendasikan di developer.apple.com/library/mac/documentation/Cocoa/Conceptual/…
Christopher Pickslay

1
@MakingScienceFictionFact, Anda mungkin ingin melihat posting ini
kervich

6
staticVariabel @vida yang dideklarasikan dalam suatu metode / fungsi sama dengan staticvariabel yang dideklarasikan di luar metode / fungsi, mereka hanya valid dalam lingkup metode / fungsi tersebut. Setiap menjalankan terpisah melalui +sharedInstancemetode (bahkan pada utas berbeda) akan 'melihat' sharedInstancevariabel yang sama .
Nick Forge

9
Bagaimana jika seseorang memanggil [[alokasi MyClass] init]? Itu akan membuat objek baru. Bagaimana kita bisa menghindari ini (selain menyatakan MyClass statis * sharedInstance = nil di luar metode).
Ricardo Sanchez-Saez

2
Jika programmer lain mengacaukan dan memanggil init ketika mereka seharusnya memanggil sharedInstance atau serupa, itu kesalahan mereka. Mengubah dasar-dasar, dan kontrak-kontrak dasar, dari bahasa untuk menghentikan orang lain yang berpotensi membuat kesalahan nampaknya cukup salah. Ada lebih banyak diskusi di boredzo.org/blog/archives/2009-06-17/doing-it-wrong
occulus

8

jika Anda ingin membuat instance lain sesuai kebutuhan. lakukan ini:

+ (MyClass *)sharedInstance
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[MyClass alloc] init];
        // Do any other initialisation stuff here
    });
    return sharedInstance;
}

selain itu, Anda harus melakukan ini:

+ (id)allocWithZone:(NSZone *)zone
{
    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}

1
Benar / Salah: dispatch_once()Bit berarti Anda tidak akan mendapatkan instance tambahan, bahkan dalam contoh pertama ...?
Olie

4
@ Olie: Salah, karena kode klien dapat melakukan [[MyClass alloc] init]dan memotong sharedInstanceakses. DongXu, Anda harus melihat artikel Singleton karya Peter Hosey . Jika Anda akan mengganti allocWithZone:untuk mencegah lebih banyak instance dibuat, Anda juga harus mengganti inituntuk mencegah instance yang dibagi diinisialisasi ulang.
jscs

Ok, itu yang saya pikirkan, maka allocWithZone:versinya. Terima kasih.
Olie

2
Ini benar-benar memutus kontrak alokasiWithZone.
occulus

1
singleton hanya berarti "hanya satu objek dalam memori setiap saat", ini adalah satu hal, diinisialisasi ulang adalah hal lain.
DongXu

5

Ini adalah versi untuk ARC dan non-ARC

Cara Penggunaan:

MySingletonClass.h

@interface MySingletonClass : NSObject

+(MySingletonClass *)sharedInstance;

@end

MySingletonClass.m

#import "MySingletonClass.h"
#import "SynthesizeSingleton.h"
@implementation MySingletonClass
SYNTHESIZE_SINGLETON_FOR_CLASS(MySingletonClass)
@end

2

Ini adalah pola saya di bawah ARC. Memenuhi pola baru menggunakan GCD dan juga memenuhi pola pencegahan instantiasi lama Apple.

@implementation AAA
+ (id)alloc
{
    return  [self allocWithZone:nil];
}
+ (id)allocWithZone:(NSZone *)zone
{
    [self doesNotRecognizeSelector:_cmd];
    abort();
}
+ (instancetype)theController
{
    static AAA* c1  =   nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        c1  =   [[super allocWithZone:nil] init];

        // For confirm...       
        NSLog(@"%@", NSStringFromClass([c1 class]));    //  Prints AAA
        NSLog(@"%@", @([c1 class] == self));            //  Prints 1

        Class   real_superclass_obj =   class_getSuperclass(self);
        NSLog(@"%@", @(real_superclass_obj == self));   //  Prints 0
    });

    return  c1;
}
@end

1
Bukankah ini c1akan menjadi contoh dari AAAsuperclass? Anda perlu memanggil +allocpada self, bukan pada super.
Nick Forge

@NickForge superbukan berarti objek kelas-super. Anda tidak bisa mendapatkan objek kelas super Ini hanya berarti merutekan pesan ke versi metode kelas super. supermasih menunjuk selfkelas. Jika Anda ingin mendapatkan objek super-kelas, Anda perlu mendapatkan fungsi refleksi runtime.
eonil

@NickForge Dan -allocWithZone:metode hanyalah rantai sederhana untuk fungsi alokasi runtime untuk menawarkan poin utama. Jadi pada akhirnya, selfpointer == objek kelas saat ini akan diteruskan ke pengalokasi, dan akhirnya AAAinstance akan dialokasikan.
eonil

Anda benar, saya lupa seluk-beluk cara superbekerja dalam metode kelas.
Nick Forge

Ingatlah untuk menggunakan #import <objc / objc-runtime.h>
Ryan Heitner

2

Baca jawaban ini dan kemudian pergi dan baca jawaban lainnya.

Anda harus terlebih dahulu tahu apa arti Singleton dan apa saja persyaratannya, jika Anda tidak memahaminya, maka Anda tidak akan mengerti solusinya - sama sekali!

Untuk membuat Singleton berhasil, Anda harus dapat melakukan 3 hal berikut:

  • Jika ada kondisi lomba , maka kami tidak boleh mengizinkan beberapa instance SharedInstance Anda dibuat pada saat yang sama!
  • Ingat dan simpan nilainya di antara banyak pemanggilan.
  • Buat hanya sekali. Dengan mengendalikan titik masuk.

dispatch_once_tmembantu Anda untuk memecahkan kondisi balapan dengan hanya membiarkan bloknya dikirim satu kali.

Staticmembantu Anda untuk "mengingat" nilainya di sejumlah doa. Bagaimana cara mengingatnya? Itu tidak mengizinkan instance baru dengan nama persis dari sharedAnstance Anda untuk dibuat lagi, itu hanya bekerja dengan yang baru saja dibuat.

Tidak menggunakan panggilan alloc init(yaitu kita masih memiliki alloc initmetode karena kita adalah subkelas NSObject, meskipun kita TIDAK boleh menggunakannya) pada kelas sharedInstance kami, kami mencapai ini dengan menggunakan +(instancetype)sharedInstance, yang terikat hanya dimulai sekali , terlepas dari beberapa upaya dari berbagai utas. pada saat yang sama dan ingat nilainya.

Beberapa sistem Singletons yang paling umum yang datang dengan Cocoa sendiri adalah:

  • [UIApplication sharedApplication]
  • [NSUserDefaults standardUserDefaults]
  • [NSFileManager defaultManager]
  • [NSBundle mainBundle]
  • [NSOperations mainQueue]
  • [NSNotificationCenter defaultCenter]

Pada dasarnya segala sesuatu yang perlu memiliki efek terpusat akan perlu mengikuti semacam pola desain Singleton.


1

Atau, Objective-C menyediakan metode inisialisasi + (void) untuk NSObject dan semua sub-kelasnya. Itu selalu dipanggil sebelum metode kelas apa pun.

Saya menetapkan breakpoint dalam sekali di iOS 6 dan dispatch_once muncul di stack frames.


0

Singleton Class: Tidak ada yang bisa membuat lebih dari satu objek kelas dalam hal apa pun atau melalui cara apa pun.

+ (instancetype)sharedInstance
{
    static ClassName *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[ClassName alloc] init];
        // Perform other initialisation...
    });
    return sharedInstance;
}
//    You need need to override init method as well, because developer can call [[MyClass alloc]init] method also. that time also we have to return sharedInstance only. 

-(MyClass)init
{
   return [ClassName sharedInstance];
}

1
Jika seseorang memanggil init, init akan memanggil sharedInstance, sharedInstance akan memanggil init, init akan memanggil sharedInstance untuk kedua kalinya, lalu macet! Pertama, ini adalah loop rekursi tak terbatas. Kedua, iterasi kedua memanggil dispatch_once akan macet karena tidak bisa dipanggil lagi dari dalam dispatch_once.
Chuck Krutsinger

0

Ada dua masalah dengan jawaban yang diterima, yang mungkin atau mungkin tidak relevan untuk tujuan Anda.

  1. Jika dari metode init, entah bagaimana metode sharedInstance dipanggil lagi (misalnya karena objek lain dibangun dari sana yang menggunakan singleton) itu akan menyebabkan stack overflow.
  2. Untuk hierarki kelas hanya ada satu singleton (yaitu: kelas pertama dalam hierarki di mana metode sharedInstance dipanggil), bukan satu singleton per kelas konkret dalam hierarki.

Kode berikut menangani kedua masalah ini:

+ (instancetype)sharedInstance {
    static id mutex = nil;
    static NSMutableDictionary *instances = nil;

    //Initialize the mutex and instances dictionary in a thread safe manner
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mutex = [NSObject new];
        instances = [NSMutableDictionary new];
    });

    id instance = nil;

    //Now synchronize on the mutex
    //Note: do not synchronize on self, since self may differ depending on which class this method is called on
    @synchronized(mutex) {
        id <NSCopying> key = (id <NSCopying>)self;
        instance = instances[key];
        if (instance == nil) {
            //Break allocation and initialization into two statements to prevent a stack overflow, if init somehow calls the sharedInstance method
            id allocatedInstance = [self alloc];

            //Store the instance into the dictionary, one per concrete class (class acts as key for the dictionary)
            //Do this right after allocation to avoid the stackoverflow problem
            if (allocatedInstance != nil) {
                instances[key] = allocatedInstance;
            }
            instance = [allocatedInstance init];

            //Following code may be overly cautious
            if (instance != allocatedInstance) {
                //Somehow the init method did not return the same instance as the alloc method
                if (instance == nil) {
                    //If init returns nil: immediately remove the instance again
                    [instances removeObjectForKey:key];
                } else {
                    //Else: put the instance in the dictionary instead of the allocatedInstance
                    instances[key] = instance;
                }
            }
        }
    }
    return instance;
}

-2
#import <Foundation/Foundation.h>

@interface SingleTon : NSObject

@property (nonatomic,strong) NSString *name;
+(SingleTon *) theSingleTon;

@end

#import "SingleTon.h"
@implementation SingleTon

+(SingleTon *) theSingleTon{
    static SingleTon *theSingleTon = nil;

    if (!theSingleTon) {

        theSingleTon = [[super allocWithZone:nil] init
                     ];
    }
    return theSingleTon;
}

+(id)allocWithZone:(struct _NSZone *)zone{

    return [self theSingleTon];
}

-(id)init{

    self = [super init];
    if (self) {
        // Set Variables
        _name = @"Kiran";
    }

    return self;
}

@end

Semoga kode di atas akan membantu.


-2

jika Anda perlu membuat singleton dengan cepat,

class var sharedInstance: MyClass {
    struct Singleton {
        static let instance = MyClass()
    }
    return Singleton.instance
}

atau

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

Anda bisa menggunakan cara ini

let sharedClass = LibraryAPI.sharedInstance
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.