Swift5 + Singleton

今回 Singleton を学ぶにあたってシングルトンを Swift5 で実装する (opens new window)を参考にさせていただいた。

非常にわかりやすかったのでこの記事を読むよりもおすすめしたい。

Singleton とはなにか

要するにたった一つしかないオブジェクトのこと。

アプリやライブラリのコア部分を司る機能を管理しているオブジェクトが常に同じ一つであってほしいという仕組み。そうでないと例えば二つのオブジェクトが同時にデータを書き換えたりしてしまうとデータの不整合が生じたりしていろいろとめんどくさいことになってしまう。

では「オブジェクトをたった一つしかつくられないためにどうしたらいいのか」というのが Singleton の考え方になるわけである。

Singleton の実装

まずは継承を防ぐためにfinal修飾子をつけ、値型のstructではなくclassを使うようにする。

classstructの最も大きな違いの一つに、コピーができるかできないかというのがある。これは Python や Javascript でよくあることなのだが、例えば A の現在のデータをスワップしたりして同じクラスの B を作りたいとしよう。

このときシャローコピーをすると A のコピーを B としてつくると A と B が全く同一のオブジェクトになってしまう。これは鏡写しのようなもので、A をスワップした時点で B の内容も変わってしまうのだ。

これを防ぐためにはディープコピーと呼ばれるコピーの仕方をおこなう必要がある。要するに、クラスの場合はコピーしたとしても実体ではなくメモリのポインタがコピーされるだけなのだ。

final public class AppManager {}
1

更に、外部から呼び出されないようにイニシャライザをプライベートにする。

private init {}
1

最後にオブジェクトをたった一つだけ生成するために、

public static let appManager = AppManager()
1

としてstatic変数で宣言する。

スレッドセーフにする

これについては現状、自分では利用する場面がないが覚えておいて損はないのでメモをしておく。

private let queue = DispatchQueue(label: "AppManager")
private let userDefaults = UserDefaults.standard
func setValue(_ value: Any, forKey key: String) { [weak self]
    queue.sync {
        userDefaults.setValue(value, forKey: key)
    }
}
1
2
3
4
5
6
7
8

ただ、全部の処理をシリアルキューにするとパフォーマンスが低下してしまうので読み込みは同時並行で実行するようにするなどの配慮があると良い。

じゃあ結局どうするの

これは非常に簡単で、Singleton としたいクラスは次のように書けば良い。

final public class AppManager() {
    public static let shared = AppManager()
    private let queue = DispatchQueue(label: "AppManager")
    private let value: Int = 100
    private init() {}
}
1
2
3
4
5
6
7

これを使いたいときは次のようにする。

AppManager.shared.value // -> 100
1

一度クラスを作ってしまえばそれを Singleton にすること自体は難しくないと言えるだろう。