Objective-C で継承可能な Singleton Class を作る

  • 59
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Objective-C でシングルトンクラスを作るのはよくやることかと思います。よくやる手段としては、以下のような方法ですね。

Singleton.m
@implementation Singleton

+ (Singleton*)sharedInstance {
    static Singleton* _instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[Singleton alloc]
                           initSharedInstance];
    });
    return _instance;
}

- (id)initSharedInstance {
    self = [super init];
    if (self) {
        // do something
    }
    return self;
}

- (id)init {
    [self doesNotRecognizeSelector:_cmd]; // init を直接呼ぼうとしたらエラーを発生させる
    return nil;
}
@end

dispatch_once は、このブロック内の処理はアプリケーションのライフサイクルの中で一度しか呼ばれないというものです。
大抵の場合これで間に合うのですが、継承したクラスを作ろうとした場合にはうまく動きません。たとえば

SingletonA.h
@interface SingletonA : Singleton
-(void)printA;
@end
SingletonB.h
@interface SingletonB : Singleton
-(void)printB;
@end

というクラスを作った場合、以下のコードは、エラーになってしまいます。

SingletonA *s1 = [SingletonA sharedInstance];
[s1 printA]; // 'NSInvalidArgumentException', reason: '-[Singleton printA]: unrecognized selector sent to instance が発生

[SingletonA sharedInstance] が、SingletonA ではなくSingleton クラスのインスタンスを返してしまうからですね。
また、複数のクラスがこの Singleton クラスを継承する可能性があるため、継承元のオブジェクト側でも、それぞれのクラスインスタンスを保持できるようにしなくてはいけません。以下のようにすることで実装ができました。

Singleton.m
@implementation Singleton
static NSMutableDictionary *_instances;

+ (id) sharedInstance {
    __block Singleton *obj;
    @synchronized(self) {
        if ([_instances objectForKey:NSStringFromClass(self)] == nil) {
            obj = [[self alloc] initSharedInstance];
        }
    }
    obj = [_instances objectForKey:NSStringFromClass(self)];
    return obj;
}

+ (id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if ([_instances objectForKey:NSStringFromClass(self)] == nil) {
            id instance = [super allocWithZone:zone];
            if (_instances == nil) {
                _instances = [[NSMutableDictionary alloc] initWithCapacity:0];
            }
            [_instances setObject:instance forKey:NSStringFromClass(self)];
            return instance;
        }
    }
    return nil;
}
- (id)initSharedInstance {
    self = [super init];
    if (self) {
        // do something
    }
    return self;
}

- (id)init {
    [self doesNotRecognizeSelector:_cmd]; // init を直接呼ぼうとしたらエラーを発生させる
    return nil;
}


- (void)print{
    NSLog(@"hoge");
}

@end

static な NSDictionary 、 _instances にクラス名をキーにしてインスタンスを保存しておき、それを返しているわけですね。

これで、以下の様なコードも問題なく動作します。

SingletonA *s1 = [SingletonA sharedInstance];
[s1 printA];
SingletonB *s2 = [SingletonB sharedInstance];
[s2 printB];