Objective-Cシングルトンはどのように見えるべきですか?[閉まっている]


334

私のシングルトンアクセサーメソッドは通常、以下のバリアントです。

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

これを改善するために私は何ができますか?


27
あなたが持っているものは問題ありませんが、グローバル変数宣言を+ instanceメソッドに移動して(同様に設定することを許可しない限り、それを使用する必要がある唯一の場所)、+ defaultMyClassまたは+メソッドのsharedMyClass。+インスタンスは意図を明らかにするものではありません。
Chris Hanson、

この質問への「回答」がすぐに変わる可能性は低いので、私はこの質問に歴史的なロックをかけています。2つの理由1)たくさんの意見、投票、そして良い内容2)オープン/クローズのヨーヨーを防ぐため。当時はすばらしい質問でしたが、これらのタイプの質問はスタックオーバーフローには適していません。これで、動作しているコードをチェックするためのコードレビューがあります。この質問のすべての議論は、このメタ質問にどうぞ
ジョージストッカー2013年

回答:


207

別のオプションは、+(void)initializeメソッドを使用することです。ドキュメントから:

ランタイムinitializeは、プログラム内の各クラスに、クラスまたはそのクラスから継承するクラスにプログラムの最初のメッセージが送信される直前に1回だけ送信します。(したがって、クラスが使用されていない場合、メソッドが呼び出されることはありません。)ランタイムinitializeは、スレッドセーフな方法でクラスにメッセージを送信します。スーパークラスは、サブクラスの前にこのメッセージを受け取ります。

だからあなたはこれに似た何かをすることができます:

static MySingleton *sharedSingleton;

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

7
ランタイムがこれを一度しか呼び出さない場合、BOOLは何をしますか?誰かがコードからこの関数を明示的に呼び出す場合の予防策ですか?
Aftermathew

5
はい、関数を直接呼び出すこともできるため、予防策です。
ロビーハンソン、

33
サブクラスが存在する可能性があるため、これも必要です+initializeそれらがスーパークラスをオーバーライドしない場合、サブクラスが最初に使用されたときに実装が呼び出されます。
Sven

3
@Paul releaseメソッドをオーバーライドして空にすることができます。:)

4
@aryaxt:リストされているドキュメントから、これはすでにスレッドセーフです。したがって、呼び出しはランタイムごとに1回、つまり期間です。これは、正しいスレッドセーフで最適な効率的なソリューションのようです。
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

[ソース]


7
これは、通常、シングルトンに使用する必要があるすべてです。特に、クラスを個別にインスタンス化できるようにしておくと、状態をリセットする方法を使用する代わりに個別のインスタンスをテストできるため、クラスを簡単にテストできます。
Chris Hanson、

3
Stig Brautaset:いいえ、この例では@synchronizedを省略してもかまいません。この静的関数を同時に実行する2つのスレッドの可能な競合状態を処理し、両方が同時に「if(!sharedSingleton)」テストを通過するため、2つの[MySingleton alloc]が生成されます。 .. @synchronized {scope block}は、仮想の2番目のスレッドが、最初のスレッドが{scope block}を出て、そこに進むことを許可されるまで待機することを強制します。これが役に立てば幸いです!=)
MechEthan 2011

3
誰かがオブジェクトの独自のインスタンスをまだ作成できないのはなぜですか?MySingleton *s = [[MySingelton alloc] init];
リンドンフォックス'28

1
@lindonfoxあなたの質問に対する答えは何ですか?
Raffi Khatchadourian、2011

1
@Raffi-申し訳ありませんが、私の答えを貼り付けるのを忘れたに違いないと思います。とにかく、私は本を手に入れました、Pro Objective-C Design Patterns for iOSそしてそれはあなたがどのように「厳格な」シンゲルトンを作るかを説明します。基本的に、開始メソッドをプライベートにすることはできないため、メソッドallocおよびcopyをオーバーライドする必要があります。したがって、次のような[[MySingelton alloc] init]ことをしようとすると、ランタイムエラーが発生します(残念ながらコンパイル時エラーは発生しません)。オブジェクト作成のすべての詳細がどのようになっているのかはわかりません+ (id) allocWithZone:(NSZone *)zoneが、呼び出されたものを実装していますsharedSingleton
lindon fox

59

以下の私の他の答えに従って、あなたはやるべきだと思います:

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

6
上記で行っているすべてのことを気にしないでください。(できれば非常に少ない)シングルトンを個別にインスタンス化できないようにし、共有/デフォルトのメソッドを用意します。あなたがしたことは、本当に本当に本当に、クラスの単一のインスタンスが必要な場合にのみ必要です。特にそうではありません。ユニットテスト用。
Chris Hanson、

これは、「シングルトンを作成する」ためのAppleサンプルコードです。しかし、ええ、あなたは絶対的に正しいです。
コリンバレット

1
Appleのサンプルコードは、「真の」シングルトン(つまり、インスタンスを1回しか作成できないオブジェクト)が必要な場合は正しいですが、Chrisが言うように、これはほとんどの場合、必要または必要なものではありませんが、設定可能な共有インスタンスの種類は、通常欲しい。
ルークレッドパス

上記のメソッドのマクロは次のとおりです:gist.github.com/1057420。これは私が使用するものです。
コブスキー2012年

1
単体テストはさておき、この解決策に反対することは何もありませんよね?そして、それは速くて安全です。
LearnCocos2D 2012

58

Kendallはロックコストを回避しようとするスレッドセーフなシングルトンを投稿したので、私も1つ投げるつもりでした。

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

さて、これがどのように機能するかを説明しましょう:

  1. 速い場合:通常の実行でsharedInstanceはすでに設定されているため、whileループは実行されず、変数の存在をテストした後に関数が戻ります。

  2. スローケース:sharedInstance存在しない場合は、インスタンスが割り当てられ、Compare And Swap( 'CAS')を使用してインスタンスにコピーされます。

  3. コールへの2つのスレッドが試み両方の場合:ケースを競合sharedInstance同時に sharedInstance同時に存在していない、彼らはシングルトンの新しいインスタンスを初期化し、所定の位置にCASにそれをしようと両方。CASを勝ち取った方はすぐに戻り、負けた方は割り当てたインスタンスを解放して(現在設定されている)を返しますsharedInstance。シングルOSAtomicCompareAndSwapPtrBarrierは、設定スレッドの書き込みバリアとテストスレッドの読み取りバリアの両方として機能します。


18
これは、アプリケーションの存続期間中に発生する可能性のある、せいぜい1回の完全な過剰です。それにもかかわらず、それは正解であり、比較とスワップの手法は知っておくと便利なツールなので、+ 1を行います。
Steve Madsen

いい答えです-OSAtomicファミリーは知っておくと良いことです
Bill

1
@ルイ:すごい、本当に啓発的な答え!ただし、1つの質問:私のinit方法はあなたのアプローチで何をすべきか?sharedInstance初期化時に例外をスローするのは良い考えではないと思います。ユーザーがinit何度も直接電話をかけないようにするにはどうすればよいですか?
マット

2
私は通常それを防ぎません。多くの場合、一般的にシングルトンを多重にインスタンス化できるようにする正当な理由があります。最も一般的なのは、特定の種類の単体テストです。単一のインスタンスを強制したい場合は、おそらくグローバルが存在するかどうかを確認するためにinitメソッドをチェックし、存在する場合はそれを解放してグローバルを返します。
Louis Gerbarg、2009

1
@Tonyは応答が少し遅れますが、OSAtomicCompareAndSwapPtrBarrierは揮発性を必要とします。おそらくvolatileキーワードは、コンパイラがチェックを最適化しないようにするためのものですか?参照:stackoverflow.com/a/5334727/449161developer.apple.com/library/mac/#documentation/Darwin/Reference/...
ベン・フリン

14
静的MyClass * sharedInst = nil;

+(id)sharedInstance
{
    @synchronize(self){
        if(sharedInst == nil){
            / * initに設定されたsharedInst * /
            [[self alloc] init];
        }
    }
    sharedInstを返します。
}

-(id)init
{
    if(sharedInst!= nil){
        [NSExceptionレイズ:NSInternalInconsistencyException
            形式:@ "[%@%@]は呼び出せません。代わりに+ [%@%@]を使用してください"]、
            NSStringFromClass([self class])、NSStringFromSelector(_cmd)、 
            NSStringFromClass([self class])、
            NSStringFromSelector(@selector(sharedInstance) "];
    } else if(self = [super init]){
        sharedInst = self;
        / *ここで特定のクラス* /
    }
    sharedInstを返します。
}

/ *これらはおそらく何もしません
   GCアプリ。シングルトンを維持
   の実際のシングルトンとして
   非CGアプリ
* /
-(NSUInteger)retainCount
{
    NSUIntegerMaxを返します。
}

-(一方向無効)リリース
{
}

-(id)retain
{
    sharedInstを返します。
}

-(id)autorelease
{
    sharedInstを返します。
}

3
[[self alloc] init]sharedInstに結果を割り当てないと、clangがリークについて不平を言うことに気付きました。
pix0r 2009年

このようにinitを破壊することは、かなり醜いアプローチのIMOです。initやオブジェクトの実際の作成を変更しないでください。代わりに、共有インスタンスへのアクセスの制御されたポイントに移動し、オブジェクトへのシングルトンのハードベイクを行わない場合、テストなどを作成すると、後で幸せな時間が得られます。ハードシングルトンは使いすぎです。
オクルス2013年

12

編集:この実装はARCで廃止されました。ARCと互換性のあるObjective-Cシングルトンの実装方法をご覧ください正しい実装のために。

他の回答で読んだすべての初期化の実装には、共通のエラーがあります。

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

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

Appleのドキュメントでは、initializeブロックのクラスタイプを確認することをお勧めしています。サブクラスはデフォルトで初期化を呼び出すためです。サブクラスがKVOを介して間接的に作成される可能性のある自明ではないケースが存在します。別のクラスに次の行を追加した場合:

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

Objective-Cは、MySingletonClassのサブクラスを暗黙的に作成し、2回目のトリガーを引き起こし+initializeます。

次のように、initブロックで初期化の重複を暗黙的にチェックする必要があると考えるかもしれません。

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

しかし、あなたは足で自分を撃ちます。またはさらに悪いことに、他の開発者に自分の足を撃つ機会を与える。

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

TL; DR、これが私の実装です

@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

(ZAssertを独自のアサーションマクロ、または単にNSAssertに置き換えます。)


1
私は単純に生きて、初期化を完全に避けるだけです。
トムアンデルセン


9

スレッドセーフですが、初期化後にロックされないsharedInstanceに興味深いバリエーションがあります。要求に応じて上位の回答を変更するのに十分かどうかはまだわかりませんが、さらに議論するために提示します。

// 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本当に興味深い。のクローンclass_replaceMethodに変換sharedInstanceするために使用する場合がありsimpleSharedInstanceます。そうすれば、@synchronized再度ロックを取得することを心配する必要がなくなります。
Dave DeLong、2010

これは同じ効果です。exchangeImplementationsを使用すると、初期化後にsharedInstanceを呼び出すと、実際にはsimpleSharedInstanceが呼び出されます。私は実際にreplaceMethodから始めましたが、必要に応じて元のものがまだ存在するように実装を切り替えるだけの方が良いと判断しました...
Kendall Helmstetter Gelner '19 / 02/19

さらなるテストでは、replaceMethodを機能させることができませんでした-繰り返しの呼び出しで、コードは依然としてsimpleSharedInstanceではなく元のsharedInstanceを呼び出しました。どちらもクラスレベルのメソッドである可能性があります...私が使用した置換は次のとおりです。およびそのいくつかのバリエーション。投稿したコードを確認でき、SharedInstanceを最初に通過した後にsimpleSharedInstanceが呼び出されます。
Kendall Helmstetter Gelner、2010

ランタイムマッキングを行わずに、初期化後にロックコストを支払わないスレッドセーフバージョンを作成できます。以下に実装を掲載しました。
Louis Gerbarg、2010年

1
+1素晴らしいアイデア。ランタイムで実行できることが大好きです。しかし、ほとんどの場合、これはおそらく時期尚早の最適化です。同期コストを取り除く必要がある場合は、おそらくLouisのロックレスバージョンを使用します。
Sven

6

短い答え:すばらしい。

長い答え:次のようなもの...

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

何が起こっているのかを理解するには、必ずdispatch / once.hヘッダーを読んでください。この場合、ヘッダーコメントは、ドキュメントやマニュアルページよりも適切です。


5

シングルトンをクラスにまとめたので、他のクラスはシングルトンプロパティを継承できます。

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

そして、これがシングルトンになりたいクラスの例です。

#import "Singleton.h"

@interface SomeClass : Singleton {

}

@end

@implementation SomeClass 

DEFINE_SHARED_INSTANCE;

@end

シングルトンクラスに関する唯一の制限は、それがNSObjectサブクラスであることです。しかし、ほとんどの場合、コードでシングルトンを使用します。シングルトンは実際にはNSObjectのサブクラスであるため、このクラスを使用すると、私の生活が非常に簡単になり、コードがすっきりします。


非常に@synchronized遅いため、他のロックメカニズムを使用することをお勧めします。
DarkDust 2012

2

これはガベージコレクションされていない環境でも機能します。

@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

これはスレッドセーフであり、最初の呼び出し後の高価なロックを回避しないのですか?

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

2
ここで使用されるダブルチェックのロック手法は、一部の環境では実際の問題であることがよくあります(aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdfまたはGoogle itを参照)。他に示されない限り、私はObjective-Cは免疫がないと思います。また、wincent.com / a / knowledge-base / archives / 2006/01 /…も参照してください。
Steve Madsen

2

ここに私がまとめたマクロがあります:

http://github.com/cjhanson/Objective-C-Optimized-Singleton

これはMatt Gallagherによるここの作業に基づいていますが、GoogleのDave MacLachlanによってここで説明 されているように、メソッドのスウィズリングを使用するように実装を変更しています

コメント/貢献を歓迎します。


リンクが壊れているようです-どこでそのソースを入手できますか?
amok

2

いかがですか

static MyClass *gInstance = NULL;

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

    return(gInstance);
}

初期化後の同期コストを回避しますか?


他の回答でデュアルチェックロックの説明を参照してください。
i_am_jorf


1

KLSingletonは:

  1. サブ分類可能(n次まで)
  2. ARC互換
  3. セーフallocinit
  4. 遅延読み込み
  5. スレッドセーフ
  6. ロックフリー(@同期ではなく+ initializeを使用)
  7. マクロフリー
  8. スウィズルフリー
  9. シンプルな

KLSingleton


1
私のプロジェクトではNSSingletonを使用していますが、KVOとの互換性がないようです。問題は、KVOが接頭辞NSKVONotifying_ MyClassを付けて、KVOオブジェクトごとにサブクラスを作成することです。また、MyClassの+ initializeメソッドと-initメソッドが2回呼び出されます。
Oleg Trakhman

これを最新のXcodeでテストしましたが、KVOイベントの登録や受信に問題はありませんでした。これは次のコードで確認できます。gist.github.com / 3065038 Twitterで述べたように、+ initializeメソッドはNSSingletonに対して1回、各サブクラスに対して1回呼び出されます。これはObjective-Cのプロパティです。
kevinlawler 2012

あなたが追加した場合NSLog(@"initialize: %@", NSStringFromClass([self class]));+initializeこの方法、あなたは、クラスが一度だけ初期化されていることを確認することができます。
kevinlawler 2012

NSLog(@ "初期化:%@"、NSStringFromClass([セルフクラス]));
Oleg Trakhman

また、それをIB互換にすることもできます。鉱山は、次のとおりです。stackoverflow.com/questions/4609609/...
ダンRosenstark

0

セルフで同期したくない...セルフオブジェクトがまだ存在しないため!一時的なid値をロックしてしまいます。他のユーザーがクラスメソッド(sharedInstance、alloc、allocWithZone:など)を実行できないようにしたいので、代わりにクラスオブジェクトで同期する必要があります。

@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
残りのメソッド、アクセサメソッド、ミューテータメソッドなどは、自分自身で同期する必要があります。すべてのclass(+)メソッドと初期化子(およびおそらく-dealloc)は、クラスオブジェクトで同期する必要があります。アクセサー/ミューテーターメソッドの代わりにObjective-C 2.0プロパティを使用すると、手動で同期する必要がなくなります。すべてのobject.propertyおよびobject.property = fooは自動的にselfに同期されます。
Rob Dotson、2010年

3
selfオブジェクトがクラスメソッドに存在しないと思う理由を説明してください。ランタイムは、selfすべてのメソッド(クラスまたはインスタンス)に対して提供するまったく同じ値に基づいて、呼び出すメソッド実装を決定します。
dreamlax 2010

2
クラスメソッドの内部にself 、クラスオブジェクトあります。自分で試してください:#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 '27

0

これをここに置いておきたかったので、私はそれを失うことはありません。これの利点は、InterfaceBuilderで使用できることです。これは非常に大きな利点です。これは私が尋ねた別の質問から取られました

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

私はこの「質問」についてたくさんのコメントがあることを知っていますが、シングルトンを定義するためにマクロを使用することを提案する多くの人々を見かけません。これは一般的なパターンであり、マクロはシングルトンを大幅に簡素化します。

これが私が見たいくつかのObjc実装に基づいて私が書いたマクロです。

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

使用例:

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

ほぼ空のインターフェイスマクロを使用するのはなぜですか?ヘッダーとコードファイル間のコードの整合性。自動メソッドを追加したり、変更したりする場合の保守性。

この記事の執筆時点で最も一般的な回答で使用されているように、initializeメソッドを使用してシングルトンを作成しています。


0

Objective Cクラスのメソッドを使用すると、シングルトンパターンを通常の方法で使用することを回避できます。

[[Librarian sharedInstance] openLibrary]

に:

[Librarian openLibrary]

クラスメソッドのみを持つ別のクラス内にクラスをラップすることにより、インスタンスを作成しないため、誤って重複インスタンスを作成する可能性がなくなります。

私はここにもっと詳細なブログを書いた:)


リンクは機能しなくなります。
i_am_jorf

0

@ 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

私の方法は次のように簡単です:

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

シングルトンがすでに初期化されている場合、LOCKブロックは開始されません。2番目のチェックif(!initialized)は、現在のスレッドがLOCKを取得したときにまだ初期化されていないことを確認することです。


initializedとしてマークするだけvolatileで十分かどうかは明確ではありません。aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdfを参照してください。
i_am_jorf 2013年

0

私はすべてのソリューションを読んだわけではないので、このコードが冗長である場合は許してください。

これは私の意見では最もスレッドセーフな実装です。

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

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

    return sharedResourcesObj;
}

-4

私は通常、Ben Hoffsteinの回答とほぼ同じようなコードを使用します(これもWikipediaから取得しました)。私は、Chris Hansonのコメントで述べられた理由でそれを使用します。

ただし、NIBにシングルトンを配置する必要がある場合があり、その場合は以下を使用します。

@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

-retain上記のコードはガベージコレクション環境で必要なすべてですが、私は(など)の実装を読者に任せています。


2
コードはスレッドセーフではありません。allocメソッドでは同期を使用しますが、initメソッドでは使用しません。初期化されたブール値のチェックはスレッドセーフではありません。
メッキー

-5

受け入れられた答えは、コンパイルされていますが、正しくありません。

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

Appleのドキュメントごと:

...同様の方法で、自己の代わりにClassオブジェクトを使用して、関連するクラスのクラスメソッドを同期させることができます。

セルフワークを使用していても、それはすべきではなく、コピーアンドペーストのミスのように見えます。クラスファクトリメソッドの正しい実装は次のとおりです。

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

6
自己は最も確かにない、それクラススコープを存在します。クラスのインスタンスではなく、クラスを参照します。クラスは(ほとんど)ファーストクラスのオブジェクトです。
シュワ

@synchroninzed WITHINをメソッドに入れるのはなぜですか?
user4951 2011年

1
schwaが既に言ったように、self クラスメソッド内のクラスオブジェクトです。これを示すスニペットについては、私のコメント参照してください
jscs '27

self存在しますが、渡された識別子としてそれを使用@synchronizedすると、インスタンスのメソッドへのアクセスが同期されます。@ user490696が指摘しているように、(シングルトンのように)クラスオブジェクトを使用する方が望ましい場合があります。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.
12
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.