6

これは、シングルトンの文献を熟読した後、私が作成したものです。私は何かを忘れましたか?

@implementation MySingleton

static MySingleton *mySharedInstance = nil;

//called by atexit on exit, to ensure all resources are freed properly (not just memory)  
static void singleton_remover()
{
    //free resources here
}

+ (MySingleton*) sharedInstance{
    return mySharedInstance;
}

+ (void)initialize {
    if (self == [MySingleton class]) {
        mySharedInstance = [[super allocWithZone:NULL] init];
    atexit( singleton_remover );    
    }
}

+ (id)allocWithZone:(NSZone *)zone
{
    return [self sharedInstance];   
}

- (id)copyWithZone:(NSZone *)zone
{
    return self;    
}

- (id)retain
{
    return self;    
}

- (NSUInteger)retainCount
{
    return NSUIntegerMax;  //denotes an object that cannot be released  
}

- (void)release
{
    //do nothing    
}

- (id)autorelease
{
    return self;    
}
4

5 に答える 5

2

ほとんどの場合、同期ロックを回避します

ソフトウェアの信頼性を高めたい場合は、「ほとんどの場合」機能する構造を避けてください。

http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html

表4.1。ダブルチェックロック

ダブルチェックロックは、ロックを取得する前にロック基準をテストすることにより、ロックを取得するオーバーヘッドを削減する試みです。ダブルチェックされたロックは潜在的に安全ではないため、システムはそれらを明示的にサポートしておらず、それらの使用は推奨されていません。

于 2010-02-19T01:55:38.453 に答える
1

いくつかの提案 (iPhone よりも Mac Cocoa 向けですが、objective-c タグを検索する他の人には役立つかもしれません):

  • -allocWithZone:NULL を気にしないでください。単純な -alloc で十分です。
  • 利用可能な場合は、dispatch_once() または pthread_once() の使用を検討してください
  • atexit の使用法は賢いですが、高速アプリ終了と互換性がない可能性があります (これが iPhone に当てはまるかどうかは不明です)。

もう 1 つの楽しいパターン:

+ (Foo *)sharedFoo {
    static Foo *sharedInstance = NULL;
    if (!sharedInstance) {
        Foo *temp = [[Foo alloc] init]; //NOTE: This MUST NOT have side effects for it to be threadsafe
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &sharedInstance)) {
            [temp release];
        }
    }
    return sharedInstance;
}
于 2010-02-19T02:15:53.650 に答える
0

singleton_remover何もしないようにオーバーライドしたため、関数は何もしreleaseません。メソッドは、共有インスタンスallocWithZone:に送信するときにも同様のノーオペレーションを実行retainします (そして、指定されたゾーンでの割り当てを完全に無視します)。おそらく、共有インスタンスが無敵 (つまり解放不可) かどうかを切り替えるフラグが必要です。

いずれにせよ、OS はとにかくすべてのメモリをクリーンアップします。ドキュメントには、OS がすべてのメモリを一度に再利用する方が、アプリケーションが少しずつメモリをゆっくりと返すよりも高速であると記載されています。

アプリケーションの終了時に常にクリーンアップする必要があるリソースを共有インスタンスが管理している場合はUIApplicationWillTerminateNotification、 を受信するように登録し、そこでクリーンアップを実行する必要があります。

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(performCleanup:)
                                             name:UIApplicationWillTerminateNotification
                                           object:nil];
于 2010-02-19T02:17:16.833 に答える
0

編集

これを上に含めます。下に、私の歴史的な元の質問と実装を示します。ただし、ロックのオーバーヘッドなしで 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 orig = @selector(sharedInstance);
            SEL new = @selector(simpleSharedInstance);
            Method origMethod = class_getClassMethod(self, orig);
            Method newMethod = class_getClassMethod(self, new);
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return (MySingleton *)sharedInstance;
}

そして、初期化に関する歴史的な議論:

ロックの外側のインスタンスをチェックすることを除いて、元のコードは実際には私のコード (以下) にかなり似ていました。

新しい + (void) 初期化メソッドは興味深いものですが、これの方が好きかどうかはわかりません。シングルトンインスタンスを取得するには、常に呼び出す必要があるようです。

MySingleton instance = [[MySingleton alloc] init];

それは正しくありませんか?それは奇妙に感じます。initialize の呼び出しが既にロックされている場合は、より効率的ですか? 二重ロック方法は、このユースケースでは問題なく機能するようですが、ロックも回避します (複数のスレッドが if を通過する可能性があるため、二重割り当ての潜在的なコストがかかると思います)。

奇妙に思えるもう 1 つの点は、initialize メソッドが本当に好まれるのであれば、なぜ他の場所で見られないのでしょうか? Objective-C は長い間存在しており、公開されているほぼすべての例とは異なる基本的なメカニズムに警戒しています。

現在使用している私のコード(この回答を含め、他の場所で見たものを反映しています):

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

+ (id)allocWithZone:(NSZone *)zone 
{
    @synchronized(self) 
    {
        if (sharedInstance == nil) 
        {
            sharedInstance = [super allocWithZone:zone];
            return sharedInstance;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}
于 2010-02-19T02:54:25.717 に答える
0

あなたの実装はスレッドセーフであり、すべてのベースをカバーしているように見えます (+initialize はランタイムによってスレッドセーフに送信されます)。

編集: 多くのコードは、関数中に呼び出すのは安全ではありませんatexit。メインスレッドに登録するUIApplicationWillTerminateNotification方が安全です。

edit2: マクロに使用するパターンを抽出して洗練させました。-init最初に呼び出されると呼び出され、アプリケーションの終了時+sharedInstance-dealloc呼び出されます。

#define IMPLEMENT_UIAPP_SINGLETON(class_name) \
static class_name *shared ## class_name; \
+ (void)cleanupFromTerminate \
{ \
    class_name *temp = shared ## class_name; \
    shared ## class_name = nil; \
    [temp dealloc]; \
} \
+ (void)registerForCleanup \
{ \
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanupFromTerminate) name:UIApplicationWillTerminateNotification object:nil]; \
} \
+ (void)initialize { \
    if (self == [class_name class]) { \
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \
        if ([NSThread isMainThread]) \
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cleanupFromTerminate) name:UIApplicationWillTerminateNotification object:nil]; \
        else \
            [self performSelectorOnMainThread:@selector(registerForCleanup) withObject:nil waitUntilDone:NO]; \
        shared ## class_name = [[super allocWithZone:NULL] init]; \
        [pool drain]; \
    } \
} \
+ (class_name *)sharedInstance \
{ \
    return shared ## class_name; \
} \
+ (id)allocWithZone:(NSZone *)zone \
{ \
    return shared ## class_name; \
} \
- (id)copyWithZone:(NSZone *)zone \
{ \
    return self; \
} \
- (id)retain \
{ \
    return self; \
} \
- (NSUInteger)retainCount \
{ \
    return NSUIntegerMax; \
} \
- (void)release \
{ \
} \
- (id)autorelease \
{ \
    return self; \
}
于 2010-02-19T04:16:19.107 に答える