Edited at

GCDでトランザクション

More than 5 years have passed since last update.

@adachi_cです。

GCDを使うことでObjective-Cプログラミングが一気に楽しくなります。簡単に書くと、


  • マルチスレッド化

  • 非同期I/O

  • 排他制御

このあたりのことがカジュアルに実装できるんですね。@synchronizedや、pthreadに比較して早いと。しかし、iPhone4かそれ以前だとシングルコアなので、性能が出ないのではと思うこと、あるいは実行したいタイミングであるコンテキストが実行されない、なども多々あり…ということの留意も必要です。

さて、今日はもっと基本の話で、23日目にBlocksで、モデルをシングルトンにして、UI更新をBlocksにするといいよという話をしました。

モデルをシングルトンにするとは、以下のようなことで、AVAudioPlayerManagerというクラスを用意して、このクラスはシングルトンなので、静的に一つだけインスタンスが作られる。


AVAudioPlayerManager.m

+ (AVAudioPlayerManager*)sharedAVAudioPlayerManager;



AVAudioPlayerManager.m

@implementation AVAudioPlayerManager

+ (AVAudioPlayerManager*)sharedAVAudioPlayerManager
{
static dispatch_once_t pred = 0;
__strong static id _vrObject = nil;
dispatch_once(&pred, ^{
_vrObject = [[self alloc] init];
});
return _vrObject;
}


あらゆるビューなどからは、AudioPlayer関連の操作は全部このインスタンスにアクセスさせて、このシングルトンの状態をきっちりトランザクションする、ということであちこちにAudioPlayerインスタンスを作って、同期させる必要がなくなるし、メモリ確保、解放で落ちる心配もありません。排他制御が複雑でどうしようもなくなるという心配もありません。

ではトランザクションさせるのはどうやるかというと、シングルキューでやる。


AVAudioPlayerManager.m


//トランザクション用シングルキュー
static dispatch_queue_t transaction_queue;
static dispatch_queue_t get_transaction_queue() {
if (transaction_queue == NULL) {
transaction_queue = dispatch_queue_create("transaction_queue", NULL);
}
return transaction_queue;
}


状態の種類を用意する。


AVAudioPlayerManager.h


typedef enum{
NotReady = 0 ,
Ready,
Running
} PMStat;

@interface AVAudioPlayerManager : NSObject <AVAudioPlayerDelegate>
{
AVAudioPlayer *player;

PMStat runningStatus;
}

-(void)prepare; //NotReady -> Ready
-(void)start; // Ready -> Running
-(void)teardown; // Running -> NotReady


で、このシングルトンの状態を3つ定義しました。NotReady,Ready,Runnningです。つまり、シングルトンを操作するために、prepare, start, teardownという基本的なメソッドが3つ必要になるという話になると思います。


AVAudioPlayerManager.m


-(void)prepare{
dispatch_sync(get_transaction_queue(),^{
if(runningStatus != NotReady){
return;
}
/*準備処理、AudioUnitまわりの初期化やAVAudioPlayerにURLを設定するなど*/
runningStatus = Ready;//うまくいったら、最後に状態かえる
});
}

-(void)start{
dispatch_sync(get_transaction_queue(),^{
if(runningStatus != Ready){
return;
}
/*再生処理など*/
runningStatus = Running;//うまくいったら、最後に状態かえる
});
}

-(void)teardown{
dispatch_sync(get_transaction_queue(),^{
if(runningStatus != Running){
return;
}
/*AudioUnitまわりの終了処理など*/
runningStatus = NotReady;//うまくいったら、最後に状態かえる
});
}


という感じで、トランザクションさせる。状態がおかしければ、実行されないことが保証される。で、ここから、たとえばメソッドをなるべく拡張したくないが、拡張する場合も、どの状態なら受け付けて、どういう状態に遷移するのか、きっちり設計したうえで、上記のような実装で実装すればOK。

外から呼び出すときは、


AVAudioPlayerManager.m


[[AVAudioManager sharedAVAudioPlayerManager] prepare];
[[AVAudioManager sharedAVAudioPlayerManager] start];
[[AVAudioManager sharedAVAudioPlayerManager] teardown];


みたいな感じでどこからでも気軽に呼び出すことができる。トランザクションされているので、排他制御できてないことによる矛盾や不正なメモリ領域の参照で落ちることはない。

こういうトランザクション的なことをきちんとやっておけば、あとあとかなり混乱が防げるし、23日で書いたBlocksでUI更新する方法と組み合わせるのがまぁオススメですねという話でした。

以上、25日目でした。ありがとうございます。