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

# はじめに

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

## `Objective-C`でのシングルトン

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

```objc
@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
```

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

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

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

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

## 実行してみる

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

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

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

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

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

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

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

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

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


## あえて`Swift`で

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

```swift
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()
    }
}
```

```swift
class SampleSubClass: SampleClass {
    func hogeString() -> String {
        return "hoge"
    }
}
```

```swift
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`にすべきだと考えています。

```swift
final class SampleClass {
    let sharedInstance = SampleClass()
}
```