シングルトン(Carlo Chung著「Pro Objective-C Design Patterns for iOS」の「Singleton」の英語の章から翻訳)

数学と論理では、シングルトンは「正確に1つの要素を含むセット」と定義されます。 したがって、バッグの大きさに関係なく、ボールを取り出そうとするたびに、同じものが手に入ります。 シングルトンはプログラミングでどのような状況を必要としますか? コピーできないが共有できるリソースを考えてください。 たとえば、単一のGPSモジュールがiPhoneにインストールされていて、彼だけが現在の座標を決定できます。 CLLocationManager



フレームワークのCLLocationManager



クラスは、すべてのGPSモジュールサービスへの単一のエントリポイントを提供します。 誰かが考えるかもしれません: CLLocationManager



コピーを作成できる場合、アプリケーションの追加のGPSサービスのセットを取得することは可能ですか? 幻想のように聞こえます-1つのハードウェアの価格で2つのGPSソフトウェアを作成しました。 しかし実際には、iPhoneにはGPSが1つしかないため、一度に1つのGPSしか取得できません。これは衛星との実際の接続を作成します。 そのため、2つの別々のGPS接続を同時に操作できるスーパーアプリケーションを作成し、それを友人に自慢したい場合は、よく考えてください。



オブジェクト指向アプリケーションのシングルトンクラスは、常に同じインスタンスを返します。 クラスオブジェクトが提供するリソースにグローバルアクセスポイントを提供します。 この機能を持つパターンはシングルトンと呼ばれます。

この章では、Objective-CおよびCocoa Touch iOSフレームワークでSingletonパターンを実装および使用する可能性を探ります。



シングルトンパターンはどのようなものですか?


シングルトンパターンは、おそらく最も単純なパターンです。 その目的は、クラスオブジェクトをシステム内の唯一のインスタンスにすることです。 まず、クラスの複数のインスタンスの作成を禁止する必要があります。 これを行うには、ファクトリメソッド(第4章)を使用できます。これは、クラスのインスタンスが別の単一インスタンスを作成することを許可する意味がないため、静的である必要があります。 図7-1は、シングルトンクラスの構造を示しています。



画像

図7-1。 シングルトンパターンの静的構造。



静的なuniqueInstance



は、 Singleton



クラスの唯一のインスタンスであり、静的sharedInstance



メソッドがクライアントに返すクラス変数として表されます。 通常、 uniqueInstance



uniqueInstance



uniqueInstance



されているかどうかを確認しuniqueInstance



。 そうでない場合、メソッドは返す前に作成します。



ご注意 シングルトンパターン:クラスのインスタンスが1つしかないことを確認し、単一のアクセスポイントを提供します。

* GoFの「デザインパターン」(Addison-Wesley、

1994)。



シングルトンパターンはいつ使用できますか?


次の場合、Singletonパターンの使用を検討するのは理にかなっています。



シングルトンパターンは、共有リソースにアクセスする便利な方法で一意のインスタンスを作成するための使い慣れた方法を提供します。 静的なグローバルオブジェクトへの参照を使用する方法は、同じクラスの別のインスタンスの作成を妨げません。 クラスメソッドを使用するアプローチは、グローバルアクセスポイントを提供できますが、コード分離の柔軟性に欠けます。

静的グローバル変数には、クラスのインスタンスへの単一の参照が含まれます。 アクセスできる別のクラスまたはメソッドは、同じ変数を使用する他のクラスまたはメソッドと実際に同じコピーを共有します。 必要なもののように見えます。 アプリケーション全体で同じグローバル変数が使用されている限り、すべてが素晴らしく見えます。 したがって、実際には、Singletonパターンは必要ありません。 しかし、待ってください! あなたのチームの誰かがあなたのコードであなたと同じグローバル変数を定義したらどうしますか? その後、1つのアプリケーションに同じグローバルオブジェクトの2つのコピーがあります。したがって、グローバル変数は実際に問題を解決しません。



クラスメソッドは、オブジェクトを作成せずに分割する機能を提供します。 クラスメソッドでは、リソースの単一インスタンスがサポートされています。 ただし、クラスがより多くの機能を提供するために継承を必要とする場合、このアプローチには柔軟性がありません。



シングルトンクラスは、単一のクラスオブジェクトを作成してアクセスするための、単一で一貫性のある有名なアクセスポイントを保証できます。 このパターンは、そのサブクラスのいずれかがインスタンス作成メソッドをオーバーライドし、クライアントコードを変更せずにクラスオブジェクトの作成を完全に制御できるような柔軟性を提供します。 さらに良いことに、インスタンス化メソッドの実装は、オブジェクトの作成を動的に処理できます。 クラスの実際のタイプは、正しいオブジェクトが作成されていることを確認するために、実行時に決定できます。 この手法についてはさらに説明します。



シングルトンパターンの柔軟なバージョンもあります。このメソッドでは、ファクトリメソッドは常に同じインスタンスを返しますが、他のインスタンスを割り当てて初期化することもできます。 この制限の少ないパターンのバージョンについては、この章で後述する「NSFileManagerクラスの使用」セクションで説明します。



Objective-Cでのシングルトン実装


シングルトンクラスを正しく設計することを検討する価値があります。 最初の質問は、クラスのインスタンスを1つだけ作成できるようにする方法です。 C ++やJavaなどの他のオブジェクト指向言語で記述されたアプリケーションのクライアントは、そのコンストラクターがprivateとして宣言されている場合、クラスオブジェクトを作成できません。 Objective-Cはどうですか?



Objective-Cのメソッドはすべて開かれており、言語自体は動的に型指定されているため、どのクラスでも、コンパイル時に重要なチェックなしでメッセージを別のクラス(C ++およびJavaのメソッド呼び出し)に送信できます(メソッドが宣言されていない場合はコンパイラー警告のみ)。 Cocoaフレームワーク(Cocoa Touchを含む)は、参照カウントによるメモリ管理を使用して、メモリ内のオブジェクトのライフタイムを維持します。 これらの機能はすべて、Objective-Cでのシングルトンの実装をかなり困難にします。

デザインパターンの元の例では、シングルトンC ++の例はリスト7-1のようになりました。



リスト7-1。 本Design PatternsのSingletonパターンの単一のC ++サンプル。



 class Singleton { public: static Singleton *Instance(); protected: Singleton(); private: static Singleton *_instance; }; Singleton *Singleton::_instance = 0; Singleton *Singleton::Instance() { if (_instance == 0) { _instance = new Singleton; } return _instance; }
      
      





本で説明されているように、C ++での実装はシンプルで簡単です。 静的Instance()



メソッドでは、静的_instance



変数の0( NULL



_instance



チェックされNULL



。 その場合、新しいSingleton



オブジェクトが作成され、返されます。 Objective-Cのバージョンはあなたの兄弟とそれほど違わず、リスト7-2および7-3のように見えるはずだと思う人もいるかもしれません。



リスト7–2。 Singleton.hのシングルトンクラス宣言



 @interface Singleton : NSObject { } + (Singleton *) sharedInstance; @end
      
      





リスト7-3。 sharedInstance Singleton.mメソッドの実装



 @implementation Singleton static Singleton *sharedSingleton_ = nil; + (Singleton *) sharedInstance { if (sharedSingleton_ == nil) { sharedSingleton_ = [[Singleton alloc] init]; } return sharedSingleton_; } @end
      
      





その場合、これは非常に単純な章であり、Objective-Cで実装された1つのパターンを既に学習しています。 実際、実際のアプリケーションで使用するのに十分な実装の信頼性を実現するには、いくつかの困難を克服する必要があります。 シングルトンパターンの「厳密な」バージョンが必要な場合、実際のコードで使用できるように解決する必要がある2つの主な問題があります。



リスト7-4は、私たちが目指している実装に近い実装を示しています。 コードは非常に長いので、議論を簡単にするためにいくつかの部分に分けます。



リスト7-4。 Objective-Cでのシングルトンのより適切な実装



 #import "Singleton.h" @implementation Singleton static Singleton * sharedSingleton_ = nil; + (Singleton*) sharedInstance { if (sharedSingleton_ == nil) { sharedSingleton_ = [[super allocWithZone:NULL] init]; } return sharedSingleton_; }
      
      







sharedInstance



メソッド内では、最初の例のように、クラスの単一のインスタンスが作成されているかどうかが最初にチェックされ、そうでない場合は新しいインスタンスが作成されて返されます。 ただし、今回は[[super allocWithZone:NULL] init]



を呼び出して、 alloc



などの他のメソッドを使用する代わりに新しいインスタンスを作成します。 なぜ、 self



ではなくsuper



か? これは、クラス内のオブジェクトにメモリを割り当てるための基本的なメソッドがオーバーライドされているため、この機能をベースクラス(この場合はNSObject



)に「貸し」、メモリを割り当てる低レベルのルーチンを実行する必要があるためです。



 + (id) allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; } - (id) copyWithZone:(NSZone*)zone { return self; }
      
      







考慮する必要があるSingleton



クラスのメモリ管理に適用されるいくつかのメソッドがあります。 allocWithZone:(NSZone *)zone



メソッドは、 sharedInstance



メソッドから返されるクラスのインスタンスを返すだけです。 Cocoa Touchフレームワークでは、クラスメソッドallocWithZone:(NSZone *)zone



を呼び出すと、インスタンスallocWithZone:(NSZone *)zone



メモリが割り当てられ、その参照カウンターが1に設定され、インスタンスが返されます。 alloc



メソッドalloc



多くの状況で使用されることを見てきました。 実際、 alloc



は、ゾーンをNULL



に設定してallocWithZone:



を呼び出し、デフォルトゾーンのインスタンスにメモリを割り当てます。 オブジェクトの作成とメモリの管理の詳細は、本書の範囲外です。 詳細については、ドキュメントを参照してください。



同様に、 copyWithZone:(NSZone*)zone



メソッドをオーバーライドして、インスタンスのコピーを返さないが、同じコピーを返してself



返すようにする必要があります。



 - (id) retain { return self; } - (NSUInteger) retainCount { return NSUIntegerMax; // ,       } - (void) release { //    } - (id) autorelease { return self; } @end
      
      







retain



release



autorelease



などの他のメソッドは、return self



以外は(参照カウントメモリ管理モデルで)何もしないようにオーバーライドされます。 retainCount



メソッドはNSUIntegerMax



(4,294,967,295)を返し、アプリケーションの存続期間中にインスタンスがメモリから削除されないようにします。



Retain Singletonを呼び出す理由

sharedInstance



メソッドからallocWithZone:



戻るシングルトンオブジェクトでretain



を呼び出すことに気付いたかもしれませんが、 retain



オーバーライドされ、実装では実際に無視されます。 これにより、Singletonクラスの「厳密性」を低くすることができます(つまり、追加のインスタンスにメモリを割り当てて初期化できますが、ファクトリのsharedInstance



メソッドは常に同じインスタンスを返すか、Singletonオブジェクトが破壊可能になります)。 サブクラスは、適切なメモリ管理実装を提供するために、 retain



release



およびautorelease



再度オーバーライドできます。

シングルトンパターンの柔軟なバージョンについては、この章で後述する「NSFileManagerクラスの使用」セクションで説明します。



Objective-Cのシングルトンパターンがどのように見えるかについては、すでに十分に詳細に検討しました。 ただし、使用する前に慎重に検討することがもう1つあります。 元のSingleton



クラスを継承する場合はどうなりますか? これを行う方法を検討してください。



シングルトン継承


alloc



呼び出しalloc



super



にリダイレクトされるため、 NSObject



クラスNSObject



オブジェクトのメモリの割り当てを処理します。 変更せずにSingleton



クラスから継承する場合、返されるインスタンスは常にSingleton



型になります。 Singleton



クラスはすべてのインスタンス化メソッドをオーバーライドするため、このクラスから継承するのは簡単ではありません。 しかし、私たちは幸運です。 いくつかのFoundation関数を使用して、クラスタイプに基づいてオブジェクトをインスタンス化できます。 それらの1つはid NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone)



です。 したがって、「シングルトン」というクラスのオブジェクトをインスタンス化する場合、次のことができます。



 Singleton *singleton = [NSAllocateObject ([Singleton class], 0, NULL) init];
      
      







最初のパラメーターは、 Singleton



クラスのタイプです。 2番目のパラメーターは、インデックス付きインスタンス変数の任意の数の追加バイト用に設計されており、常に0です。3番目のパラメーターは、割り当てられたメモリゾーンの指定です。 ほとんどの場合、デフォルトのゾーンが使用されます(パラメーターはNULL



)。 したがって、この関数を使用して、クラスのタイプを知っているオブジェクトをインスタンス化できます。 Singleton



クラスから継承するには何をする必要がありますか? sharedInstance



メソッドの初期バージョンは次のようになっていることを思い出してください。



 + (Singleton*) sharedInstance { if (sharedSingleton_ == nil) { sharedSingleton_ = [[super allocWithZone:NULL] init]; } return sharedSingleton_; }
      
      







NSAllocateObject



トリックを使用してインスタンスを作成すると、次のようになります。



 + (Singleton *) sharedInstance { if (sharedSingleton_ == nil) { sharedSingleton_ = [NSAllocateObject([self class], 0, NULL) init]; } return sharedSingleton_; }
      
      







Singleton



クラスまたはそのサブクラスをインスタンス化するかどうかは関係ありません。このバージョンはすべてを正しく行います。



スレッドセーフ


この例のSingleton



クラスは、一般的な使用にのみ適しています。 マルチスレッド環境でシングルトンオブジェクトを使用する場合は、スレッドセーフにする必要があります。 これを行うには、 @synchronized()



ブロックを挿入するか、 sharedSingleton_



インスタンスの静的変数のnil



チェックの周りにNSLock



インスタンスを使用します。 保護する必要がある他のプロパティがある場合、それらをatomic



することができます。



Cocoa Touch Frameworkでのシングルトーンの使用


Cocoa Touch Frameworkのドキュメントに慣れると、多くの異なるクラスのシングルトーンに遭遇することになります。 このセクションでは、 UIApplication



UIAccelerometer



NSFileManager



一部についてUIAccelerometer



NSFileManager







UIApplicationクラスの使用


フレームワークで最も一般的に使用されるシングルトンクラスのUIApplication



は、 UIApplication



クラスです。 これは、iOSアプリケーションの制御と調整の一元化されたポイントを提供します。



各アプリケーションには、 UIApplication



単一のインスタンスがありUIApplication



。 これは、アプリケーションの起動時にUIApplicationMain



関数によってシングルトンオブジェクトとして作成され、 sharedApplication



クラスのメソッドを介して実行時に使用可能になります。



UIApplication



オブジェクトは、着信ユーザーメッセージの初期ルーティングや、 UIControl



クラスのオブジェクトのアクティビティメッセージを対応するターゲットオブジェクトにディスパッチするなど、プログラムの多くのユーティリティタスクを実行します。 開いているすべてのUIWindow



オブジェクトのリストを保持します。 アプリケーションオブジェクトはUIApplicationDelegate



アプリケーションデリゲートオブジェクトに関連付けられており、起動、メモリ不足の警告、アプリケーションの終了、バックグラウンドプロセスなどのプログラム実行中の重要なイベントが通知されます。 これらのイベントのハンドラーにより、デリゲートはアプリケーションの動作をカスタマイズできます。



UIAccelerometerクラスの使用


Cocoa Touchフレームワークのもう1つの一般的なシングルトンは、 UIAccelerometer



です。 UIAccelerometer



クラスを使用すると、アプリケーションは、iOSデバイスの組み込みの加速度計から加速度関連データを受信するようにサブスクライブできます。 アプリケーションは、3次元空間の主軸に沿った線形加速度の変化に関するデータを使用して、デバイスの現在の向きと瞬間的な向きの変化の両方を判断できます。

UIAccelerometer



クラスはシングルトンなので、そのオブジェクトを明示的に作成することはできません。 唯一のインスタンスにアクセスするには、 sharedAccelerometer



クラスsharedAccelerometer



呼び出す必要があります。 さらに、 updateInterval



プロパティとdelegate



プロパティを独自のデリゲートオブジェクトに設定して、シングルトンインスタンスから報告された加速データを受信できます。



NSFileManagerクラスの使用


NSFileManager



クラスは、かつてMac OS X 10.5およびiOS 2.0より前のシングルトンパターンの「厳密な」実装でした。 そのinit



メソッドを呼び出しても何も行われず、その唯一のインスタンスを作成でき、 defaultManager



クラスdefaultManager



介してのみアクセスできます。 ただし、シングルトン実装はスレッドセーフではないため、このセキュリティを確保するにはNSFileManager



新しいインスタンスを作成する必要があります。 このアプローチは、ファクトリメソッドが常に同じインスタンスを返すシングルトンのより柔軟な実装と見なされますが、追加のインスタンスを選択して初期化することもできます。



「厳密な」シングルトンを実装する場合は、前のセクションで説明した例と同様の実装が必要です。 それ以外の場合、 allocWithZone:



およびその他の関連メソッドをオーバーライドしないでください。



結論


シングルトンパターンは、iOSだけでなく、ほぼすべての種類のアプリケーションで最も広く使用されているパターンの1つです。

シングルトンは、アプリケーションサービスを調整するために集中型クラスが必要な場合に役立ちます。

この章では、オブジェクトの作成に関するこのパートの終わりを示します。 次のパートでは、異なるインターフェイスを持つオブジェクトの適応/統合に焦点を当てたいくつかのデザインパターンを見ていきます。



All Articles