質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Objective-C

Objective-Cはオブジェクト指向型のプログラミング言語のひとつです。C言語をベースにSmalltalkが取り入れられています。

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Q&A

解決済

2回答

7332閲覧

EXC_BAD_ACCESSを防ぐためにメモリが解放されてるか確認したい

sirosiro

総合スコア26

Objective-C

Objective-Cはオブジェクト指向型のプログラミング言語のひとつです。C言語をベースにSmalltalkが取り入れられています。

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

0グッド

0クリップ

投稿2016/11/19 22:04

編集2016/11/20 03:57

###前提・実現したいこと
メモリが解放されているかを確認するobjective-cの命令などがあれば知りたかったのですが、抜本対応まで含めて考えてくださる方もいらっしゃるので、不足していた説明を追加します。

iTunesのような音楽再生アプリを作っております。
自分用のアプリで、公開する予定もなければ家族も友達も使いません。

アプリ内だけで音楽を再生する分にはご指摘にあったようにAVPlayerItemを使って再生すればいいですし、再生の制御をアプリが行っているので、その際にselfなりに現在再生中の曲情報を持たせてそれを使えばいいです。
ただ、iTunesのようにアプリがバックグラウンドになった状態や、iPadの待ち受け状態で曲が終了時に自動的に次の曲に遷移させるにはAVQueuePlayerを使う必要があります。
このAVQueuePlayerは複数の曲(AVPlayerItem)が入ったプレイリストをArrayの形で渡して再生を任せることができます。曲が終われば自動的に次の曲に遷移してくれます。

ただ、欠点としてAVQueuePlayerには曲が終わったことや始まったことなどを検知するようなDelegateが存在しないので、仕方なしに

objc

1 [avPlayerItem addObserver:self 2 forKeyPath:@"status" 3 options:NSKeyValueObservingOptionInitial 4 context:(__bridge void *)(Playlist)];

このような形でAVQueuePlayerに渡すAVPlayerItemそれぞれにaddObserverでstatusプロパティが変更された時にイベントが発生するように細工をしています。これだけでは渡した複数のAVPlayerItemのどの曲でイベントが発生したのかわからないのでPlaylistという曲情報が入ったオブジェクトも一緒に渡しています。

こうすることで、observeValueForKeyPath:ofObject:change:contextが呼ばれた際に曲情報やアートワークなどを更新してiPadなどで現在再生中の内容を表示させることに成功しました。

###発生している問題・エラーメッセージ
ところが、しばらく使っているとたまにですがプレイリスト切り替え時に

Thread 1:EXC_BAD_ACCESS (code=1, address=0x...)

上記のエラーが発生してアプリが異常終了します。
プレイリストの切り替えは以下のような順で処理をしています。

  1. 再生を停止する
  2. removeObserverでaddObserverしたものを解除
  3. PlaylistとAVPlayerItemを解放(ARCなのでnilセット)
  4. 新しいPlaylistとAVPlayerItemを作成してセット
  5. 再生開始

ここで、removeObserverして解放した曲のイベントがなぜか発生し、当然ながらすでにARCによって解放されているのでPlaylistにアクセスするとBAD_ACCESSとなります。

どういう条件でremoveObserverした後にイベントが発生するのかわかりません。ただ、何度か使っているうちに発生します。1回目で突然発生することは今の所ありません。

###該当のソースコード

objc

1-(void)observeValueForKeyPath:(NSString *)keyPath 2 ofObject:(id)object 3 change:(NSDictionary *)change 4 context:(void *)context { 5 6 if ([NSStringFromClass([object class]) isEqualToString:@"AVPlayerItem"]) { 7 if ([keyPath isEqualToString:@"status"]) { 8 9 AVPlayerItem *objItemNow = object; 10 11 // invest status 12 if (objItemNow.status == AVPlayerStatusReadyToPlay) { 13 // ここで contextに対してBAD_ACCESS 14 [self onDidPlayToStart:(__bridge Playlist *)(context)]; 15 } 16 } 17 } 18}

###教えて欲しいこと
ARC管理下でcontext内のメモリが自動解放された後にAVPlayerItemにメッセージが飛んできているのが原因と思われますが、(タイミングによってはそうなるのかもしれません)その場合はどうせremoveObserverされた後のAVPlayerItemに対して飛んできた謎イベントなので処理をスキップさせようと思っています。

抜本的な解決法(回避法)もあればお願いいたします。

それと、コメントにあったようにあまり手を加えたくはありません。
今時点で特に歪なことはしておらず、公式のサンプルに毛が生えた程度の処理しかしていないので、これを回避するためだけに根本的に全部作り替えるような対応や、複数の状態管理変数をselfに持たせて歪なフラグコントロールをしてまで改善しようとは思いません。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

根本的な解決方法ではないことは重々承知の上で、このようにOS側のタイミングが理由によりBAD_ACCESSになる前に、メモリが割り当てられているか解放されているかを確認する方法はないでしょうか?

もしこの対処だけで済ませたら、解放済みでもたまたまメモリにアクセスできたら続行してしまうわけで、別の用途で確保され直しれている領域にアクセスしても気づかず、余計に原因不明の事態を招く結果になると思います。問題原因の箇所で実行が止まってくれてよかったと思うべきように思います。

コードの断片しか見てないためちゃんとわかっていませんが、
どうしてPlaylistをcontextで引き継ぐ必要があるんでしょうか?
なんとなくコードの名前だけからの想像ですが、Playlistの中に再生曲の一覧が入っていて、
それを順番に再生するコードの断片のように見えました。

だとしたら、監視対象のAVPlayerItemのプロパティと同じ場所にPlaylistを保持するプロパティを定義して
そこにアクセスしたらよさそうに思いました。
もしその場所がselfであれば、self.playlistでアクセスしたらいいですし、何かのタイミングでself.playlistをnilにすることがあるのなら、self.playlistがnilか確認してからアクセスしたらいいです。

(11/21 サンプルコード追記)

Objective

1#import "ViewController.h" 2 3// AVPlayerItem擬似クラス 4@interface AVPlayerItem : NSObject 5@property(nonatomic, strong) NSString *status; 6@end 7 8@implementation AVPlayerItem 9@end 10 11 12@interface ViewController () 13 14@property(nonatomic, strong) NSMutableArray *avPlayerItems; 15@property(nonatomic, strong) NSMutableArray *playLists; 16 17@end 18 19@implementation ViewController 20 21- (void)viewDidLoad { 22 [super viewDidLoad]; 23 24 // AVPlayerItemの配列とPlaylistの配列生成 25 self.avPlayerItems = [NSMutableArray array]; 26 self.playLists = [NSMutableArray array]; 27 for (int i = 0; i < 10; i++) { 28 AVPlayerItem *avPlayerItem = [[AVPlayerItem alloc] init]; 29 [self.avPlayerItems addObject:avPlayerItem]; 30 NSString *playList = [NSString stringWithFormat:@"Title%02d",i]; 31 [self.playLists addObject:playList]; 32 } 33 // オブザーバー設定 34 for (int i = 0; i < 10; i++) { 35 AVPlayerItem *avPlayerItem = self.avPlayerItems[i]; 36 [avPlayerItem addObserver:self 37 forKeyPath:@"status" 38 options:NSKeyValueObservingOptionInitial 39 context:nil]; 40 } 41 42 // 再生中の曲が切り替わるのをタイマーで擬似 43 [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(playNext) userInfo:nil repeats:true]; 44 45} 46 47-(void)playNext { 48 static int index = 0; 49 AVPlayerItem *avPlayerItem = self.avPlayerItems[index]; 50 avPlayerItem.status = @"ready"; 51 index++; 52 index %= self.avPlayerItems.count; 53} 54 55// 再生中の曲が変わった時にタイトルを表示 56-(void)observeValueForKeyPath:(NSString *)keyPath 57 ofObject:(id)object 58 change:(NSDictionary *)change 59 context:(void *)context { 60 if ([NSStringFromClass([object class]) isEqualToString:@"AVPlayerItem"]) { 61 if ([keyPath isEqualToString:@"status"]) { 62 AVPlayerItem *objItemNow = object; 63 NSUInteger index = [self.avPlayerItems indexOfObject:objItemNow]; 64 if (index != NSNotFound) { 65 NSLog(@"Now Playing title: %@",self.playLists[index]); 66 } else { 67 NSLog(@"AVPlayerItem Not Found"); 68 } 69 } 70 } 71} 72@end

投稿2016/11/20 01:10

編集2016/11/21 15:00
TakeOne

総合スコア6299

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sirosiro

2016/11/20 02:03

Playlistには個々のAVPlayerItemに紐付くタイトルなどの曲情報が入っています。AVPlayerItemには曲情報などを入れる箇所がないので(自分がそのやり方を知らない)仕方なしにそうしています。AVPlayerItemはAppleが提供する再生キューに渡すものなので、継承などせずそのまま渡しています。どの曲でイベントが発行されたのかが別の方法で分かればこんなことをしなくても済むのですが…。
TakeOne

2016/11/20 02:30

> どの曲でイベントが発行されたのかが別の方法で分かればこんなことをしなくても済むのですが…。 私は新しいクラスを作れともAVPlayerItemを継承しろとも言っていません。もちろんそうするのが 理想ですが、あまりソースコードに手を入れられない状況なのだろうと思ってそこまで言ってません。 「監視対象のAVPlayerItemのプロパティと同じ場所にPlaylistを保持するプロパティを定義したら?」 と言っているだけです。 Playlistに曲情報が入ってるなら、AVPlayerItemで再生する曲をセットすると同時にplaylistに 今セットした曲の情報をセットしたらいいだけのように見えます。 もちろん全体の設計構造を知らないのでトンチンカンなことを言っているのかもしれませんが。
sirosiro

2016/11/20 04:04

> もちろん全体の設計構造を知らないのでトンチンカンなことを言っているのかもしれませんが。 抜本対応をするのに必要な書ける限りの情報を追加しました。 ところで、 > 私は新しいクラスを作れともAVPlayerItemを継承しろとも言っていません。 これはAVPlayerItemを継承したクラスを作ってAVQueuePlayerに渡して、 後でobserveValueForKeyPath:ofObject:change:contextの時にその新しいクラスにキャストして 使用できるということでしょうか?(あまりオブジェクト思考のキャスト周りで何ができて何が禁止なのか理解していません) できるのなら、その程度の労は可能です。それが一番シンプルだと思います。 「できるかどうかわからないから試してみて」、と言われると、そこまでする時間はありません。
TakeOne

2016/11/20 21:54 編集

> これはAVPlayerItemを継承したクラスを作ってAVQueuePlayerに渡して、 > 後でobserveValueForKeyPath:ofObject:change:contextの時にその新しいクラスにキャストして > 使用できるということでしょうか? そんなことは一言も言っていません。できるかどうかも試してみないとわからないし、試してみたら?というつもりもありません。 私が言いたいのは、ARC管理のオブジェクトを(__bridge void *)でキャストして渡して別のメソッドで参照するのはとても危険なのでやめるべきということだけです。 それをやめて、どうやって処理すればいいのかがわからないのだと思いますが、 > 1.再生を停止する > 2.removeObserverでaddObserverしたものを解除 > 3.PlaylistとAVPlayerItemを解放(ARCなのでnilセット) > 4.新しいPlaylistとAVPlayerItemを作成してセット > 5.再生開始 のように処理しているのであれば、4.でPlaylistとAVPlayerItemをどこかにセット しているのですから、observeValueForKeyPathの中でcontextを参照するのではなく 4.でセットしたオブジェクトをselfからたどって参照したらいいんじゃないでしょうか? もし参照できない構造になっているなら、selfからたどって参照できる構造にしたらいいんじゃないでしょうか? ということです。
sirosiro

2016/11/20 23:09

以下に、selfから辿って参照できない理由を書いているつもりです。 > このような形でAVQueuePlayerに渡すAVPlayerItemそれぞれに > addObserverでstatusプロパティが変更された時にイベントが発生するように > 細工をしています。 > これだけでは渡した複数のAVPlayerItemのどの曲でイベントが発生したのかわからないので > Playlistという曲情報が入ったオブジェクトも一緒に渡しています。 最初はTakeOneさんが言われてる通り、contentsは使っておらずselfを見ようとしていました。 ですが、私の技術力では恥ずかしながら、どの曲でイベントが発生したのかがわからないので、 selfから辿る案は頓挫しました。
TakeOne

2016/11/21 15:03

私がイメージするサンプルコードをつくってみました。回答欄に追記しています。 本物のAVPlayerItemを使うのは面倒だったので擬似的にAVPlayerItemクラスを 作って、再生曲変更のタイミングでPlayListのタイトルを表示するような処理をつくってみました。
sirosiro

2016/11/21 19:30

非常に参考になりました。 ありがとうございます。
guest

0

Zombie Objectsのチェックをしてみてはどうでしょうか?

最近は使っていませんし、今は確認できる環境がありませんので、なるべく新しめの記事を探してみました。
XCode 7 で EXC_BAD_ACCESS のデバッグ (Zombie Objects)

投稿2016/11/20 04:04

fuzzball

総合スコア16731

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問