LoginSignup
16
14

More than 5 years have passed since last update.

シングルトンとして扱われるクラスが継承されている際に気をつけるべきこと

Last updated at Posted at 2016-02-18

はじめに

あるObjective-CのライブラリをSwiftで使っているとき(たぶんObjective-Cで使っても同じ)に、ライブラリ内のシングルトンがよくありがちな原因でクラッシュするという現象に遭遇しました。
原因は難しいことではないのですが、意外と見落としやすいので備忘録のような形でメモしておきます。

Objective-Cでのシングルトン

最近は下記のように書くことが多いかと思います。

@interface SampleClass: NSObject
@end

@implementation SampleClass
static SampleClass *_sharedInstance = nil;

+ (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self alloc] init];
    });
    return _sharedInstance;
}
@end

そして、上記のシングルトンを持ったクラスを継承したクラスを以下のものとします。

@interface SampleSubClass: SampleClass
- (NSString *)hogeString;
@end

@implementation SampleSubClass
- (NSString *)hogeString {
    return @"hogeString";
}
@end

SampleClassSampleSubClassBridge-Header.hを通して、Swiftで使用するとなると
SampleClass.sharedInstance()SampleSubClass.sharedInstance()になり,
それぞれのsharedInstance()メソッドで返却される型は、SampleClass!SampleSubClass!になります。

実行してみる

Sample①
print(SampleClass.sharedInstance())     //"SampleClass: 0x7fd58d8030c0"
print(SampleSubClass.sharedInstance())  //"SampleClass: 0x7fd58d8030c0"

結果はSampleClassの同じインスタンスとなります。
順番を逆に呼び出してみると

Sample②
print(SampleSubClass.sharedInstance())  //"SampleSubClass: 0x7f972a70fc80"
print(SampleClass.sharedInstance())     //"SampleSubClass: 0x7f972a70fc80"

結果はSampleSubClassの同じインスタンスとなります。

はじめにsharedInstance()にアクセスしたクラスのインスタンスがstatic SampleClass *_sharedInstance
1度だけ代入されるため、2回目以降にsharedInstance()にアクセスした場合は上記のような結果になります。

Sample①の順序で以下のような呼び出し方をすると、sharedInstance()SampelClassのインスタンスを
返してくるためクラッシュします。

print(SampleClass.sharedInstance())                  //"SampleClass: 0x7fd58d8030c0"
print(SampleSubClass.sharedInstance().hogeString())  //"unrecognized selector sent to instance 0x7fd58d8030c0"

Sample②の順序では問題なく動きます。

Sample②
print(SampleSubClass.sharedInstance().hogeString())  //"hoge"
print(SampleClass.sharedInstance())                  //"SampleSubClass: 0x7f972a70fc80"

あえてSwift

Objective-Cと似たようなことをしようとするとこうなります。

class SampleClass: NSObject {
    private static var _sharedInstance: SampleClass!
    class func sharedInstance() -> Self {
        struct Static {
            static var onceToken: dispatch_once_t = 0
        }
        return sharedInstanceHelper(&Static.onceToken)
    }

    private class func sharedInstanceHelper<T>(onceToken: UnsafeMutablePointer<dispatch_once_t>) -> T {
        dispatch_once(onceToken) {
            _sharedInstance = self.init()
        }
        return _sharedInstance as! T
    }

    required override init() {
        super.init()
    }
}
class SampleSubClass: SampleClass {
    func hogeString() -> String {
        return "hoge"
    }
}
print(SampleClass.sharedInstance())                  //"SampleClass: 0x7fd58d8030c0"
print(SampleSubClass.sharedInstance().hogeString())  //"Could not cast value of type 'SampleClass' to 'SampleSubClass'."

SampleSubClassに強制的にキャストしようとしてるので、return _sharedInstance as! Tで落ちます。

最後に

上記の結果を踏まえるとシングルトンとして扱われるクラスは、やはりfinal classにすべきだと考えています。

final class SampleClass {
    let sharedInstance = SampleClass()
}
16
14
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
14