Singletonを本当にこのまま使い続けてよいのかと思った方へ
iOS開発をしていると、Singletonを一度も使ったことがない人はいないでしょう。私もそうでした。SomeManager.shared このようなコードが、どのプロジェクトにも必ず数個はあったものです。
ところが、ある時からこの.sharedが少しずつ扱いづらくなりました。テスト時に状態が絡み合い、チームメンバーがあちこちから自由にアクセスしてしまうのです。
そんな中で知ったのがMonostateパターンです。今回は、SwiftにおけるMonostateとは何か、そして本当にSingletonの代替になり得るのか、実際に使ってみた経験を共有します。
先に結論を言うと、Monostateは「状態は1つに共有しながら、インスタンスは複数作れるようにする」パターンです。目的はSingletonと同じですが、アプローチは正反対です。ただし万能ではなく、状況によって向き不向きがあります。以下で順に見ていきましょう。
Monostateパターンとは一体何ですか?
Singletonの核心は「インスタンスを1つだけ作る」ことです。イニシャライザを制限し、sharedだけを使うようにします。
Monostateはその反対です。インスタンスはいくつでも作れます。その代わり、内部の状態(データ)をすべてstaticで共有します。
つまり、オブジェクトAを作ってもBを作っても、両者が見るデータは同じです。外からは普通のオブジェクトに見えますが、内部では1つの状態につながっています。
Swiftで簡単に書くと、次のような形です。
struct Settings {
private static var _volume = 50 // 状態を staticで共有
var volume: Int { // インスタンスのように使う
get { Settings._volume }
set { Settings._volume = newValue }
}
}
// 異なるインスタンスだが状態は1つ
var a = Settings(); a.volume = 80
print(Settings().volume) // 80
Settings()を新しく作っても、volumeは80になります。インスタンスは複数でも状態は1つ。これがMonostateのすべてです。
Singletonとは何が違うのでしょうか?
最も大きな違いは、利用する側のコードです。
Singletonでは、利用側が常に.sharedを意識する必要があります。「これはSingletonだ」と考えながら使うわけです。Monostateなら、普通のオブジェクトのようにSettings()を作って使うだけなので、利用側が内部実装を知る必要はありません。
表にまとめると、次のようになります。
| 区分 | Singleton | Monostate |
|---|---|---|
| インスタンス数 | 1つだけ | 複数作成可能 |
| 共有するもの | インスタンス自体 | 状態(staticデータ) |
| 利用側 | .sharedを明示 |
普通のオブジェクトのように使う |
| 継承 | 難しい | 比較的自由 |
特に面白いのが継承です。Singletonは継承しづらい一方、Monostateは通常の型なので、より柔軟に拡張できます。
では、Singletonの代替になり得るのでしょうか?
正直に言うと、なる場合もあれば、ならない場合もあります。
まずはメリットです。利用側のコードがすっきりし、.sharedというグローバルなアクセスポイントがコードのあちこちに現れません。インターフェースも普通のオブジェクトと同じなので、後から構造を変える負担も小さくなります。
Monostateは「グローバル状態を隠す」パターンです。便利そうに見えますが、隠れたグローバル状態はそれ自体が諸刃の剣です。
問題はここです。普通のオブジェクトに見えるため、チームメンバーが「これは単なるローカルオブジェクトだろう」と作ったところ、実は状態がグローバルに共有されていた、ということが起こります。かえって混乱しやすいのです。
また、staticで共有するため、マルチスレッド環境では並行処理の問題にも同じように注意が必要です。この点はSingletonと変わりません。
私の経験では、最近のSwiftなら、Monostateより先に依存性注入を検討することをおすすめします。テストがしやすく、依存関係も明確になるからです。
では、どんなときに使うとよいのでしょうか?
まとめると、次のような場合です。
- 既存のSingletonコードが多すぎて、段階的に取り除きたいとき
- 利用側のインターフェースを、普通のオブジェクトのようにすっきり保ちたいとき
- 継承や拡張が必要なグローバル状態を扱うとき
反対に、新しいプロジェクトをゼロから作るのであれば、Monostateよりも依存性注入や明示的な状態管理を先に検討することをおすすめします。
結局のところ、Monostateは「Singletonの完全な上位互換」ではなく、「性格の異なるもう1つの選択肢」です。道具箱に入れておき、状況に合うときに使えばよいのです。
今日の内容が、.sharedの乱用に疲れた方への小さなヒントになればうれしいです。むやみに乗り換えるのではなく、自分たちのプロジェクトに何が合うのか、一度考えてみてください。

