リソースとシングルトン、あるいはマネージャー


対象読者

タイトルのキーワードに興味を持ってくれた方。
Swiftが途中で出てきますが、特にiOSに限った話ではありません。
シングルトンは危険なので使わない方が良いと考えている方(ひょっとしたら役に立つかも?しれません)

注意

記事内で システム という単語がよく出てきますが、 組み込みシステム とか モバイルアプリケーション とか Webシステム などを包括したものと捉えてください。

シングルトン

シングルトンは分かりやすい考え方なので、ご存知の方はとても多いと思いますが、(最近特に近所で)よく「シングルトンは使うと危険だから封印」みたいな話を聞きます。1
しかしながらシングルトンはそれほど忌避すべきもののようには思えません。少しこのあたりから書き始めたいと思います。

まず押さえておきたいのは、シングルトンとシングルトン・パターンは異なるものだということです。

シングルトンは、そのシステムで固有の責務を担ったクラスの唯一のインスタンスという意味です。(かなり個人的な定義です)
シングルトン・パターンは、上記のシングルトンを実装する場合のベストプラクティス、デザインパターンです。2
http://www.techscore.com/tech/DesignPattern/Singleton.html/

シングルトンの危険性は、シングルトンとして使うことを想定した実装なのにそうだと知らずに別のインスタンスを作ってしまうケースがあります。しかしながら、それは言語や実装の問題であってシングルトンの問題ではありません。実はこのような問題を避けるパターンが、シングルトン・パターンです。

例えばSwiftではシングルトン・パターンは下記のようになります。


public final class Singleton
{
    static public let shared = Singleton()
    private init(){}
}

イニシャライザーを呼び出せないことで、shared関数を使ってインスタンシエートするしかありません。
このため、Singletonクラスの別のインスタンスをインスタンシエートすることは不可能です。
ただしこのようなシングルトンは継承が使えないので、必ずfinalになります。

気をつけるべきは、あるインスタンスがそのクラスの唯一のインスタンスであったとしても、他のクラスと責務が重複していればシステムに甚大な障害をもたらす可能性が高いことです。シングルトンがシングルトンたる所以は、排他的な責務をただ一つのインスタンスが負うている ことです。例えばメモリー管理の例で言えば、あるシングルトンのメモリー管理モジュールが、実メモリーを確保してアプリケーションに渡したとしましょう。しかし同時に全く別のクラス・インスタンスが同一アドレスを別の用途に使えば、システムは容易に破壊されます。
まず、適切な責務の分割を考えること(基本設計)がシングルトンを使う上でのポイントです。

また、下記のような実装はアンチパターンです。


public final class Singleton
{
    static public let shared = Singleton()
    private init(){}

    public var value: Int?  // <-- ここ
}

例えばスレッドセーフにするためには、変数に対するCRUD3ではセマフォが必要なことがあります。Swiftの場合は、下記のように書くか、get/set関数で実装しましょう。

ちなみに下記のような変数の処理で事足りる責務で、自分はシングルトンは使わないと思うので、やるとすれば全て関数で実装すると思います。


public final class Singleton
{
    static public let shared = Singleton()
    private let semaphore  = DispatchSemaphore(value: 1) // セマフォ。この場合はバイナリーセマフォまたはMutexと呼ばれる。
    private init(){}

    private var privateValue: Int = -1

    public var value: Int {
        get {
            semaphore.wait()
            defer { semaphore.signal() }
            // 処理アレコレ
            return privateValue
        }
        set {
            semaphore.wait()
            defer { semaphore.signal() }
            // 処理アレコレ
            privateValue = newValue
        }
    }
}

リソース

シングルトンはひとまず置いておいて、次にリソースについて考えてみます。

経営的な視点では、「ヒト、カネ、モノ」がリソースです。いわゆる経営資源という意味のリソースです。

Web的な視点では、URL(またはURI)がリソースです。厳密に言えばURLが指し示している先の事物がリソースです。URLとは「Uniform Resource Locator」の略でURIは「Uniform Resource Identifier」の略です。

いずれにしても、
・有限の事物である
・識別が可能である
という条件には当てはまります。

では、プログラミングの世界におけるリソースは何でしょうか?

ハードウェアリソース

iOSにようなモバイルアプリケーションを考える前に、分かりやすい例として「組み込み」の世界を考えてみます。
「組み込み」の世界ではハードウェアリソースは限定されているので、ほぼ全てのハードウェアがリソースとして認識されます。

  • メモリー
  • CPUの処理能力
  • ディスプレイの数
  • 位置、ジャイロ、加速度など各種のセンサー類
  • グラフィックエンジン
  • WiFi(デバイス)

などがそうです。

しかし、モバイルやPCのエンジニアは特殊な例を除いて メモリーCPUの処理能力を気にするエンジニアはいないかもしれません。それはCPUに備わった仕組みやOS、コンパイラによって、エンジニアがさほど意識しないでも容易に扱えるからです。このような場合はメモリー容量CPUの処理能力はリソースでは無いと考えてよいと思います。本当は限りがあるけど、あたかも無限に存在するかのように見える上、ほぼ管理の必要が無いからです。4

ハードウェアリソース管理のようにリソースが限定されており、排他的に実施すべき処理をシングルトンとして実装することはとても理にかなっていると考います。(これとは反対の意見もあるようです5

ソフトウェアリソース

システムには、またソフトウェアリソースも存在します。
システム全体にまたがってサービスを提供するようなインスタンスは、排他的責務を持ち、かつ1つのインスタンスつまりシングルトンとしても良さそうです。例えば、オブザーバーパターンを用いてシステム全体にサービスを提供する NotificationCenter などは好例です。

NotificationCenterは、それ自体が限定されたリソースではなく、NotificationというIdentifiedされた情報、つまりソフトウェア的リソースを管理するためにあります。NotificationCenterはNotificationを扱うというシステム全体のサービスであり、排他的責務を持つためにシングルトンとして存在することができます。

iOSでシングルトンと思われるフレームワーク

Framework 説明 シングルトンの観点
UIApplication The centralized point of control and coordination for apps running in iOS. シングルトンなのは当然のような気がする
NotificationCenter A notification dispatch mechanism that enables the broadcast of information to registered observers. メッセージのブロードキャストというシステム全体のサービスはシングルトンにマッチしている
UserDefaults An interface to the user’s defaults database, where you store key-value pairs persistently across launches of your app. UserDefaults.standardで呼び出すように正確にはシングルトンである必要はないが、システム全体のサービスであるため、事実上のシングルトン
ABAddressBook The ABAddressBook class provides a programming interface to the Address Book アドレスデータベースにアクセスするためにシングルトンか?しかしデータベース自体が排他制御できる場合はシングルトンの意味はなさそう。実際新しいContactsフレームワークはシングルトンではない。

iOSでシングルトンと思われるフレームワークをピックアップしてみました。
他にもいくつかありますが、調べてみると意外にシングルトン実装は少ないです。
CLLocationManagerなどはシングルトンであっても違和感はないですが、シングルトンではありません。
これは、iOS開発で多用されるDelegateパターン、クロージャなどに理由がありそうです。

下記の2つの記事は以前に書いたものですが、その理由はここからも推察できます。

iOSにおけるSingletonとDelegate/Observer
軽量なRxSwiftもどきを自作してみた

CLLocationManagerのようにdelegateプロパティを実装する場合、Protocolで委譲されるインスタンスが1つに限られるためにこのパターンを利用しようとするとシングルトンにするのはちょっと躊躇われます。かといって、NotificationCenterでグローバルにNotificationメッセージを発行するのも嫌です。
RxSwift(あるいはモドキ)を使った実装のように、容易にオブザーバーパターンを用いることができれば、シングルトン化の問題は軽減されると思いますが、このような手段がAppleから標準で提供されない限りは、Appleのフレームワークでシングルトンが採用されることは少ないように思います。

結論

  • 責務が他のクラスと重複しないこと
  • 責務の対象が、限定されたリソースであること
  • 責務がシステム全体に渡るものであること
  • シングルトン・パターンを適用すること
  • スレッドセーフの必要性を検討すること
  • オブザーバー・パターンによる通知機構が必要な場合がある

を考慮して設計・実装することでシングルトンを活用することができるのではないでしょうか。

マネージャー

最後にマネージャーについてひとこと。
xxxxManagerという命名ははシングルトンの実装と同じくらい忌避されているようですが、個人的には正しいシングルトンに対して命名するのはアリだと思っています。要は命名のルールをはっきりすることです。

参考記事

シングルトンの賢い使用法


  1. これは参考までに→シングルトンパターンの誘惑に負けない 

  2. singleton とは一枚札のことです。一枚札とはトランプの一組に唯一のカードです。Singleton パターンとは、このような唯一の存在を保証するためのパターンです。www.techscore.comより引用 

  3. CRUD(クラッド)とは、ほとんど全てのコンピュータソフトウェアが持つ永続性の4つの基本機能のイニシャルを並べた用語。 その4つとは、Create(生成)、Read(読み取り)、Update(更新)、Delete(削除)である。 ユーザインタフェースが備えるべき機能(情報の参照/検索/更新)を指す用語としても使われる。Wikipediaより引用 

  4. 面白いことにユーザを何十万人、何百万人規模で扱うクラウドシステムでは、ハードウェアに対して処理規模のインパクトが大きすぎるために、メモリーやCPUの処理能力を無視できなくなります。AWSなどではコンピューティング・リソースという概念は重要なもので、負荷やレイテンシーに対する考え方も組み込みの世界に通じるところが多いと感じます。 

  5. Singletonパターンはどのようなときに使うのか?