web-dev-qa-db-ja.com

シングルトンパターンの目的C(iOS)を修正しますか?

GCDを使用してシングルトンクラスを作成するための情報がネットで見つかりました。オーバーヘッドが非常に少なくスレッドセーフであるため、これはすばらしいことです。悲しいことに、私は完全なソリューションを見つけることができず、sharedInstanceメソッドのスニペットのみを見つけることができました。だから私は試行錯誤の方法を使って自分のクラスを作りました-そして出来上がり-以下が出てきました:

@implementation MySingleton

// MARK: -
// MARK: Singleton Pattern using GCD

+ (id)allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; }
- (id)copyWithZone:(NSZone *)zone { return self; }
- (id)autorelease { return self; }
- (oneway void)release { /* Singletons can't be released */ }
- (void)dealloc { [super dealloc]; /* should never be called */ }
- (id)retain { return self; }
- (NSUInteger)retainCount { return NSUIntegerMax; /* That's soooo non-zero */ }

+ (MySingleton *)sharedInstance
{
    static MySingleton * instance = nil;

    static dispatch_once_t predicate;   
    dispatch_once(&predicate, ^{
        // --- call to super avoids a deadlock with the above allocWithZone
        instance = [[super allocWithZone:nil] init];
    });

    return instance;
}

// MARK: -
// MARK: Initialization

- (id)init
{
    self = [super init];
    if (self) 
    {
        // Initialization code here.
    }
    return self;
}

@end

私が何かを見逃したり、何かが完全に間違っている場合はコメントして自由に教えてください;)

乾杯ステファン

29
blackjacx

単純にする:

+(instancetype)sharedInstance
{
    static dispatch_once_t pred;
    static id sharedInstance = nil;
    dispatch_once(&pred, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (void)dealloc
{
    // implement -dealloc & remove abort() when refactoring for
    // non-singleton use.
    abort();
}

それだ。 retainreleaseretainCountをオーバーライドし、残りはバグを隠して不要なコードの行を追加するだけです。コードのすべての行は、発生するのを待っているバグです。実際には、共有インスタンスでdeallocが呼び出されるようにしている場合は、アプリに非常に深刻なバグがあります。そのバグは非表示ではなく修正する必要があります。

このアプローチは、シングルトン以外の使用モードをサポートするためのリファクタリングにも役立ちます。いくつかのリリースを超えて存続するほとんどすべてのシングルトンは、最終的に非シングルトン形式にリファクタリングされます。一部(NSFileManagerなど)は、シングルトンモードをサポートし続けながら、任意のインスタンス化もサポートします。

上記はARCでも「機能する」ことに注意してください。

82
bbum
_// See Mike Ash "Care and Feeding of Singletons"
// See Cocoa Samurai "Singletons: You're doing them wrong"
+(MySingleton *)singleton {
    static dispatch_once_t pred;
    static MySingleton *shared = nil;
    dispatch_once(&pred, ^{
        shared = [[MySingleton alloc] init];
        shared.someIvar = @"blah";
    });
    return shared;
}
_

dispatch_onceは再入不可 なので、dispatch_onceブロック内から自分自身を呼び出すとプログラムがデッドロックすることに注意してください。

自分に対して防御的にコーディングしようとしないでください。フレームワークをコーディングしていない場合は、クラスを通常のように扱い、上記のシングルトンイディオムを使用してください。シングルトンイディオムは、クラスを定義する特性ではなく、便利なメソッドと考えてください。単体テスト中にクラスを通常のクラスとして扱いたいので、アクセス可能なコンストラクターを残してもかまいません。

_allocWithZone:_を使用しないでください。

  • 引数を無視し、allocとまったく同じように動作します。 Objective-Cではメモリゾーンが使用されなくなったため、_allocWithZone:_は古いコードとの互換性のためにのみ保持されます。
  • 動作しません。 Objective-Cでは、NSAllocateObject()およびclass_createInstance()を使用して常により多くのインスタンスを作成できるため、シングルトンの動作を強制することはできません。

シングルトンファクトリメソッドは、常に次の3つのタイプのいずれかを返します。

  • idは、戻り値の型が完全に不明であることを示します(クラスクラスターを構築している場合)。
  • instancetypeは、返されたタイプが外側のクラスのインスタンスであることを示します。
  • わかりやすくするために、クラス名自体(この例ではMySingleton)。

このiOSにタグを付けたので、シングルトンの代わりにivarをアプリデリゲートに保存し、必要に応じて再定義できる便利なマクロを使用します。

_#define coreDataManager() \
        ((AppDelegate*)[[UIApplication sharedApplication] delegate]).coreDataManager
_
19
Jano

シングルトンを単体テストする場合は、モックシングルトンに置き換えたり、通常のシングルトンにリセットしたりできるようにする必要もあります。

@implementation ArticleManager

static ArticleManager *_sharedInstance = nil;
static dispatch_once_t once_token = 0;

+(ArticleManager *)sharedInstance {
    dispatch_once(&once_token, ^{
        if (_sharedInstance == nil) {
            _sharedInstance = [[ArticleManager alloc] init];
        }
    });
    return _sharedInstance;
}

+(void)setSharedInstance:(ArticleManager *)instance {
    once_token = 0; // resets the once_token so dispatch_once will run again
    _sharedInstance = instance;
}

@end
1
ToddH