ARCと互換性のあるObjective-Cシングルトンを実装するにはどうすればよいですか?


172

Xcode 4.2で自動参照カウント(ARC)を使用している場合に、正しくコンパイルおよび動作するシングルトンクラスを変換(または作成)するにはどうすればよいですか?


1
私は最近、マットギャロウェイの記事を、ARCと手動の両方のメモリ管理環境向けのシングルトンについてかなり詳しく説明しました。galloway.me.uk/tutorials/singleton-classes
cescofry

回答:


391

あなたがすでに(すべき)行ってきたのとまったく同じ方法で:

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


1
@MakingScienceFictionFact、あなたはこの投稿を
kervich '24 / 07/24

6
staticメソッド/関数内で宣言された@David 変数は、メソッド/関数static外で宣言された変数と同じであり、それらはそのメソッド/関数のスコープ内でのみ有効です。+sharedInstanceメソッドを個別に実行するたびに(異なるスレッドでも)、同じsharedInstance変数が「参照」されます。
Nick Forge

9
誰かが[[MyClass alloc] init]を呼び出したらどうでしょうか?それは新しいオブジェクトを作成します。これを回避するにはどうすればよいですか(メソッドの外で静的MyClass * sharedInstance = nilを宣言する以外)。
Ricardo Sanchez-Saez

2
他のプログラマーがめちゃくちゃにして、sharedInstanceなどを呼び出す必要があるときにinitを呼び出すと、エラーになります。他の人が間違いを犯す可能性をなくすために、言語の基礎と基本的な契約を覆すことは、かなり間違っているようです。詳細については、boredzo.org
blog /

8

必要に応じて他のインスタンスを作成する場合は、次のようにします。

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

そうでなければ、これを行う必要があります:

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

1
正誤問題:このdispatch_once()ビットは、最初の例でも追加のインスタンスを取得しないことを意味する...?
Olie 2013年

4
@Olie:False、クライアントコードがアクセスを実行[[MyClass alloc] init]およびバイパスできるためsharedInstance。DongXu、Peter Hoseyのシングルトンの記事をご覧ください。上書きallocWithZone:してインスタンスが作成さinitれないようにする場合は、共有インスタンスが再初期化されないように上書きする必要もあります。
jscs 2013年

わかりました、それは私が考えたものであり、それゆえallocWithZone:バージョンです。どうも。
Olie

2
これはallocWithZoneの契約を完全に破ります。
オクルス2013年

1
シングルトンは単に「いつでもメモリ内の1つのオブジェクトのみ」を意味します。これは1つのことであり、再初期化されることは別のことです。
DongXu 2013年

5

これはARCと非ARCのバージョンです

使い方:

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

これはARCでの私のパターンです。GCDを使用して新しいパターンを満たし、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
これc1AAAのスーパークラスのインスタンスになりますか?あなたは呼び出す必要が+allocselfではない上、super
Nick Forge

@NickForge superはスーパークラスのオブジェクトを意味するものではありません。スーパークラスオブジェクトを取得することはできません。これは、メッセージをスーパークラスバージョンのメソッドにルーティングすることを意味します。superまだselfクラスを指しています。スーパークラスのオブジェクトを取得したい場合は、ランタイムリフレクション関数を取得する必要があります。
eonil 2013年

@NickForge And -allocWithZone:メソッドは、オーバーライドポイントを提供するランタイムの割り当て関数への単純なチェーンです。したがって、最終的にはselfポインタ==現在のクラスオブジェクトがアロケータに渡され、最後にAAAインスタンスが割り当てられます。
eonil 2013年

あなたは正しいです、私superはクラスメソッドでどのように機能するかの微妙なことを忘れていました。
Nick Forge

#import <objc / objc-runtime.h>を使用することを忘れないでください
Ryan

2

この答えを読んでから、他の答えを読んでください。

シングルトンが何を意味するのか、そしてそれを理解していない場合、ソリューションを理解できないよりも、その要件が何であるかを最初に知る必要があります。

シングルトンを正常に作成するには、以下を実行できる必要があります3。

  • 競合状態があった場合、SharedInstanceの複数のインスタンスを同時に作成することはできません。
  • 複数の呼び出しの間で値を覚えておいてください。
  • 一度だけ作成してください。エントリポイントを制御する。

dispatch_once_tブロックを1回だけディスパッチできるようにすることで、競合状態を解決するのに役立ちます。

Static任意の数の呼び出しにわたってその値を「記憶」するのに役立ちます。どのように覚えていますか?共有インスタンスの正確な名前を持つ新しいインスタンスを再度作成することはできません。最初に作成されたインスタンスでのみ機能します。

使用していない呼び出しalloc init(つまり、我々はまだ持っているalloc init私たちはNSObjectのサブクラスをしているので、我々はそれらを使用してはならないものの、メソッド)を、当社のsharedInstanceクラスに、私たちが使用してこれを実現する+(instancetype)sharedInstance唯一のことに制限されており、一度開始した別のスレッドからの複数の試みに関係なく、同時に、その価値を思い出してください。

Cocoa自体に付属する最も一般的なシステムシングルトンには、次のものがあります。

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

基本的に、集中効果が必要なものはすべて、ある種のシングルトン設計パターンに従う必要があります。


1

または、Objective-Cは、NSObjectとそのすべてのサブクラスに対して+(void)initializeメソッドを提供します。これは常にクラスのメソッドの前に呼び出されます。

iOS 6でブレークポイントを1つに設定すると、dispatch_onceがスタックフレームに表示されました。


0

シングルトンクラス:いかなる場合でも、どのような方法でも、クラスのオブジェクトを複数作成することはできません。

+ (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
誰かがinitを呼び出すと、initはsharedInstanceを呼び出し、sharedInstanceはinitを呼び出し、initはもう一度sharedInstanceを呼び出してから、クラッシュします。まず、これは無限再帰ループです。2番目に、dispatch_onceの内部から再度呼び出すことができないため、dispatch_onceの2回目の呼び出しはクラッシュします。
Chuck Krutsinger

0

承認された回答には2つの問題があり、目的に関連する場合とそうでない場合があります。

  1. initメソッドから、何らかの理由でsharedInstanceメソッドが再度呼び出された場合(たとえば、シングルトンを使用する他のオブジェクトがそこから構築されるため)、スタックオーバーフローが発生します。
  2. クラス階層の場合、階層内の具象クラスごとに1つのシングルトンではなく、1つのシングルトン(つまり、sharedInstanceメソッドが呼び出された階層内の最初のクラス)しかありません。

次のコードは、これらの問題の両方を処理します。

+ (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

上記のコードがそれを助けることを願っています。


-2

シングルトンを迅速に作成する必要がある場合

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

または

struct Singleton {
    static let sharedInstance = MyClass()
}

class var sharedInstance: MyClass {
    return Singleton.sharedInstance
}

このように使えます

let sharedClass = LibraryAPI.sharedInstance
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.