シングルトン(Singleton)とは?
インスタンスが同時に一つしか存在せず、それが担保されているクラスのことを示します。
つまり、アプリ内で同時に一つしか存在すべきでないものをシングルトンで実装するとそれが保証されるということです。
同時に一つしか存在すべきでないものとは
- アプリを利用しているユーザー
- APIのリクエストを取り扱うインスタンス
- アプリ全体に影響する設定
NotificationCenterも裏側でSingletonを使っているおかげでViewControllerをまたぎイベントのPost・Observeが可能になってます。
実装例
/// Singletonは継承されてはいけない。継承を防ぐためclassにfinalをつける
final public class Singleton {
/// 外部からのアクセスはsharedを介して行う。よってstaticでインスタンスを共通化する
public static let shared = Singleton()
private var userName = "Taro Yamada"
init() {}
public var firstName: String {
let names = userName.components(separatedBy: " ")
return names[0]
}
}
/// sharedを介してメソッドを呼び出す
print(Singleton.shared.firstName)
結果
Taro
このようにシンプルなコードで簡単に、グローバルで一つしか存在しないクラスを作ることができます。
シングルトンのデメリット
コードの可読性・運用性を下げる
↓sample2.swiftのようなコードがあるとします。
class B は singleton である class A がないと perform() が実行できません。すなわち class B は class A に依存している関係です。
ただ、class B のコードが何百行とあって、その中の一つのメソッドで呼ばれているとすれば、class B は class A 無しには機能しないこと(依存関係にあること)に気づくことは難しいと思います。
この関係性が明らかでないと、複数人や中長期での開発で、思わぬ副作用が生まれる可能性を引き上げてしまいます。
final public class A {
public static let shared = A()
private var userName = "Taro Yamada"
init() {}
func printName() {
print(userName)
}
}
class B {
let a: A
init(a: A = .shared) {
self.a = a
}
func perform() {
a.printName()
}
}
let b = B.init(a: A.shared)
b.perform()
結果
Taro Yamada
ライフサイクルの制限
Singletonのライフサイクルは基本的にアプリのライフサイクルと同じ。
(アプリ起動してたらSingletonクラスがインスタンス化されアクセス可能に、アプリのタスクキルされるとSingletonも消える)。
Singletonがこのライフサイクルに添えない場合、Singleton以外の方法を考えるべきです。
まとめ
このように、Singletonの使用には、本記事で挙げたデメリットやテストコードの難易度など懸念点がたくさんあることは事実です。
しかし、インスタンスの単一性の担保が必要なクラスの実装では大きな効果を発揮します。
そういったケースに当てはまる場合、Singletonの使用を検討してみてはいかがでしょうか?