2012年10月18日

[Q&A]画面間でオブジェクトを受け渡す方法

前回のエントリー
[Q&A]別画面で再生したAVAudioPalyerを停める方法で質問を頂いた方から、追加で質問を受けました。

ひとまず、演奏中の音楽が停止できない問題は回避できたようですが、


(3)AVAudioPlayerのインスタンスを親で持つ

に関しまして、何度かコードを反映させようと試みたのですが、今の私にはお手上げのメッセージやエラーが出ましたので少し挫折気味でございます。


という部分でうまく行っていないようです。ここは確かに初心者には難しいところです。具体的なコードで示していないので自分のプロジェクトに応用しにくい部分もあるでしょう。

ところで、質問者のやりたいことは以下のようなものだそうです。


ポイントは

・親ビューに複数のボタンを設置
・各ボタンはそれらの子ビュープレーヤーに遷移する
・異なる子ビューで再生される音楽は常に1つで、後発優先にしたい
・任意子ビューの任意音楽が終了した時点で、タイマーを初期化したい


以前、
画面間でのデータの受け渡しに付いて
で記述した画面間でのデータ受け渡しについてまとめると

・グローバル変数
・シングルトンオブジェクト/アプリケーションデリゲート
・外部ファイル/ユーザーデフォルト
・画面オブジェクトのインスタンス変数へ直接アクセスする。
・画面オブジェクトのデリゲート/プロトコル経由でインスタンス変数へアクセスする。

という方法を示しました。そしてこの時には基本型で示し、Objective-Cのオブジェクトではやりませんでした。メモリ管理について考慮する必要があり、その説明が大変だったからです。

そこで今回はObjective-Cのオブジェクトを受け渡す部分について書こうと思います。

今回の質問者の目的では以下の二つは適切ではないので省きます。
・「グローバル変数」での実装はメモリ管理を間違える可能性が高くなり、問題がある。
・ファイルを使うやり方は情報のみを受け渡したい場合にしか使えない。

残った中で「シングルトン」を使う方法が一番簡単なのでこちらを使って説明します。前の記事では「(3)AVAudioPlayerのインスタンスを親で持つ」が理想的な方法だと思うのでその方法を勧めていますが。実装技術に不安がある場合には、実力にあった方法を使う方がいいでしょう。

今回はシングルトンとしてApplication Delegateを使います。

この方法のメリットはメモリ管理をシングルトン側に委任できること。既にあるApplication Delegateを使うので実装の手間が少ないことです。そして既にARCを使うようにしていれば、メモリ管理も設定を書くだけで済みます。

まずアプリケーションデリゲートのヘッダでアプリケーション実行中に保持したいオブジェクトの宣言をします。
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@class DAViewController;

@interface DAAppDelegate : UIResponder

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) DAViewController *viewController;
@property (retain, nonatomic) AVAudioPlayer *sound; // 追加

@end


最新の開発環境であれば、プロパティ宣言を一行書けばインスタンス変数宣言もsynthesize宣言も必要ないので楽だ。

つぎにこれを使う側、子ビューコントローラー。こちらではビューコントローラーで管理していたAVAudioPlayerを使わず、直接Application Delegateが保持しているオブジェクトを参照するので宣言を削除する。そして使う時に参照できるようにヘッダをインポートして参照箇所を修正する。

#import "DANextViewController.h"
#import "DAAppDelegate.h"

@interface DANextViewController ()
// @property(nonatomic,retain) AVAudioPlayer *sound; // 削除する
@end


- (IBAction)startAction:(id)sender {
DAAppDelegate *appDelegate = (DAAppDelegate*)[UIApplication sharedApplication].delegate;
NSURL *soundFile = [NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:@"1" ofType:@"mp3"]];
appDelegate.sound = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFile error:nil];
appDelegate.sound.delegate = self;

appDelegate.sound.numberOfLoops = -1;
[appDelegate.sound prepareToPlay];
[appDelegate.sound play];
}

- (IBAction)stopAction:(id)sender {
DAAppDelegate *appDelegate = (DAAppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate.sound stop];
}


一見なんのメモリ管理も考えていないように見えるが、これでちゃんと管理できているから不思議。ARCの威力です。

#本当はセットする前にnilでなければ停止する処理とか書いた方がいいけど、なくてもいけているので簡単のため。

一応説明しておくと、Application Delegateはアプリケーションのデリゲートとして参照されている。アプリケーションインスタンスはアプリケーション実行中はどのクラスからでも[UIApplication sharedApplication]で参照可能。デリゲートもそのプロパティとして参照できる。

AVAudioPlayerのインスタンス変数をretainでプロパティ宣言しているので、セットアクセスすると必ず保持される。セット側でalloc-initしているので二重のリテインになるように見えるが、ARCを使っている場合にはコンパイラが自動的にautoreleaseを設定してくれるので開放されているのだ。これが非ARC下であればautoreleaseまたはreleaseのコードを各必要がある。

なお、ARC使用時にはstrong(またはweak,assign,unsafe_unretainedなど)で宣言するのが推奨されています。というかretainと書くとstrongとして認識されているのでどちらで書いても同じなんですが。私がretainを使うのは、copyと明確に区別したいためです(IBOutletはstrong,weakを使っています)。私はこれは好みの問題だと思っているので、retainでもstrongでもどちらでもよいでしょう。

デリゲート/プロトコルを使ったやり方についてはいずれ時間があれば説明を書こうと思います。

#質問者さんへのメッセージ
記事中ではクラス名などを質問者さんのコードのものとは違う物にしている場合があります。
自分のプロジェクトに移す時にそのままコピー&ペーストするのではなく、クラス名などは自分のものに会わせて修正してください。ビルド時に警告が出ているのは、名前が違っているのが原因です。
(例:DANextView → DANextViewController)

posted by 永遠製作所 at 16:33| 東京 ☔| Comment(1) | TrackBack(0) | iPhone/iPod touch | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
質問者石黒です。
この度はアドバイスを頂き、大変ありがとうございました!少し理解できた様な気がします。。まさかこんなに親切なコメントを頂けるとは思ってもいませんでした。iPhone開発の本は殆ど揃えたのですが、この事について書かれた本が一冊も無く、諦めかけていた矢先にコメントを頂きました。少しずつ理解して行きたいと思います。どうも有り難うございました!また質問させて頂くかもしれませんが、どうぞ宜しくお願い致します。
Posted by いしぐろ ようこ at 2012年10月19日 01:21
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

※ブログオーナーが承認したコメントのみ表示されます。

この記事へのトラックバック