iOSやAndroid開発でのエラー解決法や便利tipsのメモ
スポンサードリンク
Objectiv-Cでシングルトンクラスを作成するときの作法を調べてみました。
まずはAppleの公式Documentを参考にして実装してみます。
https://developer.apple.com/jp/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_10.html
そのままだと、releaseをオーバーライドしている箇所で以下のような警告が出ます。
“Warning: Conflicting distributed object modifiers on return type in implementation of ‘release'”
戻り値の型にonewayを追加すると解決しました。
1 |
- (oneway void) release {} |
親クラスのメソッドがonewayを指定している場合、子クラスでも同様の指定にする必要があるようです。
Xcode4.2以前はコンパイラーのチェックが緩かったので警告が出なかっただけだそうです。
参考サイト:http://stackoverflow.com/questions/7379470/singleton-release-method-produces-warning
onewayの意味なのですが、メソッドが非同期メッセージとして扱われるだとかなんとかだそうです。
参考サイト:http://d.hatena.ne.jp/Kazzz/20120116/p1
@synchronizedは複数スレッドからアクセスされた場合でもシングルトン状態を維持できるようにするためのキーワードです。
sharedManager内でインスタンスを代入しないのは[[SingletonClass alloc] init]という呼び方をされてもシングルトン状態が保たれるようにするためです。
allocメソッドは内部でallocWithZoneを呼びメモリ領域を確保します。
このメソッドをオーバーライドすることでシングルトンを強制させることができます。
あとはretainやreleaseもオーバーライドして何もしないようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
@implementation SingletonTest static SingletonTest *sharedInstance = nil; + (SingletonTest*)sharedManager { @synchronized(self) { if (sharedInstance == nil) { [[self alloc] init]; // ここでは代入していない } } return sharedInstance; } + (id)allocWithZone:(NSZone *)zone { @synchronized(self) { if (sharedInstance == nil) { sharedInstance = [super allocWithZone:zone]; return sharedInstance; // 最初の割り当てで代入し、返す } } return nil; // 以降の割り当てではnilを返すようにする } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)retain { return self; } - (unsigned)retainCount { return UINT_MAX; // 解放できないオブジェクトであることを示す } - (oneway void) release {} //- (void)release //{ // // 何もしない //} - (id)autorelease { return self; } @end |
iOS4から追加されたGCD(Grand Central Dispatch)という機能のひとつであるdispatch_onceを使ってみます。
dispatch_onceのブロック内での処理はアプリケーションのライフタイムで一度しか呼ばれないことが保証されます。
また、@synchronizedと同様に複数スレッドからアクセスされた場合でもシングルトン状態を維持でき、こちらのほうが処理が速いです。
参考サイト:
http://objective-audio.jp/2009/09/grand-central-dispatch-block.html
http://cocoasamurai.blogspot.jp/2011/04/singletons-your-doing-them-wrong.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
@implementation SingletonTest static SingletonTest *sharedInstance = nil; + (SingletonTest*)sharedManager { //static SingletonTest* sharedSingleton; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[SingletonTest alloc]init]; }); return sharedInstance; } + (id)allocWithZone:(NSZone *)zone { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [super allocWithZone:zone]; }); return sharedInstance; } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)retain { return self; } - (unsigned)retainCount { return UINT_MAX; // 解放できないオブジェクトであることを示す } - (oneway void) release {} - (id)autorelease { return self; } @end |
ARCの場合はretainやreleaseを気にする必要がありません。
Creating iOS 5 Apps: Develop and Designで紹介されていた方法がもっとも簡潔だったのでこれを参考にサンプルコードを書いてみました。
この実装方法の特徴は外部からinitメソッドが呼ばれたらエラーになるようにしていることです。
こうすることでsharedManagerを呼ばずにインスタンスを生成してしまうことを防いでいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@implementation SingletonTest + (SingletonTest*)sharedManager { static SingletonTest* sharedSingleton; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedSingleton = [[SingletonTest alloc] initSharedInstance]; }); return sharedSingleton; } - (id)initSharedInstance { self = [super init]; if (self) { // 初期化処理 } return self; } - (id)init { [self doesNotRecognizeSelector:_cmd]; return nil; } @end |
1 2 3 4 5 6 7 |
SingletonTest *singletonObject1 = [SingletonTest sharedManager]; SingletonTest *singletonObject2 = [SingletonTest sharedManager]; SingletonTest *singletonObject3 = [[SingletonTest alloc] init]; NSLog(@"%@",singletonObject1); NSLog(@"%@",singletonObject2); NSLog(@"%@",singletonObject3); |
3種類の実装方法を紹介しましたが、それぞれsharedManagerを通さずに[[SingletonTest alloc] init];と呼び出してしまったときの対処法が違います。
C++やJavaはコンストラクタをprivateにすることができるのですが、Objective-CではできないのでallocWithZoneまたはinitをオーバーライドすることで対処しています。
nilを返すかSingletonのインスタンスを返すかエラーにするかの違いです。
呼んでほしくない方法で呼ばれてしまったのならそれと分かるようなメッセージを付けてエラーを返してあげるのが親切でしょうか。
cocos2dのCCDirectorクラスを見てみると、allocメソッドをオーバーライドして2度目の呼び出しにはエラーを吐くようにしていました。
1 2 3 4 5 |
+(id)alloc { NSAssert(_sharedDirector == nil, @"Attempted to allocate a second instance of a singleton."); return [super alloc]; } |
release等が呼ばれたときの処理は特に書かれていませんでした。
ちなみにallocWithZoneを使えば一応インスタンスの生成はできてしまいます。
1 |
CCDirector *director3 = [[CCDirector allocWithZone:nil] init]; |
スポンサードリンク