はじめに
タイトルにも書いてあるように、シングルトンパターンを使う時の条件を備忘録としてまとめます。
シングルトンパターンとは
デザインパターン(ソフトウェア開発における設計パターン)の一つであり、シングルトン
を使って定義します。
そして、シングルトンパターンはインスタンスが1個しか生成されないことを保証したい時
に使います。
シングルトンの代表例として、UserDefaults
やUIApplication
などがあります。
シングルトンの書き方
swiftでのシングルトンの書き方は、クラス
に以下のコードを記述するだけです。
class Hogehoge {
// 初回アクセスのタイミングで初期化
static let shared = Hogehoge()
// 外部からのインスタンス生成を禁止する
private init() {}
}
// このようにして呼び出すことができる
Hogehoge.shared
因みにですが、struct
に対してシングルトンパターンは用いることが出来ません。
Copy on Write (CoW)
によって暗黙的にコピーを発生させてしまう
からです。
struct Fugafuga {
var str = "original"
static let shared = Fugafuga()
}
var fugafuga = Fugafuga.shared
// ここでCoWによってコピーが発生し、Fugafuga.sharedとは別物になってしまう
fugafuga.str = "copy"
print(fugafuga.str) // copy
print(Fugafuga.shared.str) // original
このようにコピーが発生してしまえば、インスタンスが1個
では無くなってしまい
シングルトンパターンの要件を満たせないからです。
使う時の条件とは
先ほど、シングルトンパターンを使用する場面はインスタンスが1個しか生成されないことを保証したい時
と書きましたが...自分にはあまりピンと来ません。
じゃあ、そういう場面とは?という感じで結局、どこで使用するか分かりません。
自分はあらゆる場所から呼べる便利さ
から何となく使っている感じでした。
・便利だが、危険でもある
しかし、あらゆる場所から呼べる便利さ
ですが危険性もあります。
swiftの機能の大部分は、オブジェクト指向
という考え方で設計されています。
この設計方法には3つの原則があり、その中の1つ「カプセル化
」がシングルトンパターンと真逆の考え方
なんですね。
・カプセル化とシングルトンパターン
カプセル化を意識すると、意図していない場所で値が好きな様に書き換えられてしまうことを防ぐ
ことができます。
例えば、このようなコードがあったとします。
class Hogehoge {
var str = "Hello World!"
}
外部でHogehogeクラスのインスタンス
を作成した場合、今のままだとクラス内に宣言されているstrプロパティ
にアクセス出来てしまいます。
つまり、下記のように外部から好きな値に書き換えることが可能になります。
class Hogehoge {
var str = "Hello World!"
}
let hogehoge = Hogehoge()
print(hogehoge.str) // Hello World!
hogehoge.str = "Goodbye World!"
print(hogehoge.str) // Goodbye World!
では、カプセル化を意識して先ほどのコードを改修してみましょう。
class Hogehoge {
private var str = "Hello World!"
}
let hogehoge = Hogehoge()
print(hogehoge.str) // エラー
hogehoge.str = "Goodbye World!" // エラー
print(hogehoge.str) // エラー
private
をvarの前に付けました。
こうする事によって、外部からアクセスしようとしてもエラーになるので外部から値を好きな様に書き換えられてしまう
ことが無くなりました。
しかし、シングルトンパターン
は色んな場所で呼び出せてしまうので思わぬ場面で大切なデータを書き換えてしまった
等の危険性があります。
なので、適した場面で使用しなくてはいけません
。
シングルトンパターンの正しい使い方
Appleドキュメントで書かれていました。(やはり、公式ドキュメントを読むのが良き)
「シングルトンを使用して、グローバルにアクセス可能なクラスの共有インスタンスを提供します。サウンド効果を再生するオーディオチャンネルや、HTTPリクエストを行うネットワークマネージャのように、アプリ全体で共有されているリソースやサービスへの統一されたアクセスポイントを提供する方法として、独自のシングルトンを作成することができます。」
(引用: Managing a Shared Resource Using a Singleton | Apple Developer Document)
上記のドキュメントを読んで自分なりに解釈する
例えば、ログイン機能をアプリに実装しようと思い、新規登録画面(以下、画面A)
とログイン画面(以下、画面B)
を作ろうと思いました。
1.早速、実装しようとするも画面によってログインに関するインスタンスを生成するのは面倒だし、無駄にメモリを消費してしまう等の問題が...。
↓
2.ならログイン機能を管理するクラス
を作った方がいいな。
↓
3.シングルトンパターンを使用しない
場合。ログイン機能を管理するクラスのインスタンス
を画面A・Bそれぞれに対して生成しなくてはならないし、更にそれぞれで何か変更を加えた際に不整合が起こる可能性があり、管理しにくい
。
シングルトンパターンを使用する
場合。そこで、アプリ内でログイン機能を管理するクラスのインスタンスは1個のみ
を保証するシングルトンパターンを使用すれば、ログイン機能を管理するクラス
が統一されているので不整合が起きにくく、更にはソースコードの可読性が向上し、管理しやすくなる
。
ということでしょうか?
おわりに
まだまだ、まとめた今でもシングルトンパターンを使えるか不安ですが
とりあえず知識は深まりました。
因みに自分の現在開発しているアプリでは、先ほどのログイン機能を管理するクラス
の他にAPI通信を行うクラス
もシングルトンパターンを使用しています。
メンターによると、API通信を行うクラスはシングルトンパターンにするのが主流だそうです。
参考記事
【Swift】Singleton パターンってどういう時に使うの?
Swift におけるシングルトン・staticメソッドとの付き合い方