2回目のアップデートをリリースしました。
当初はスレッドまわりの見直しに取り掛かるつもりだったのですが、Ver.1.1のアップデートをリリースした直後にメモリーリークのバグが見付かってため、予定変更してバグ修正とトランジション追加のアップデートです。
バグが発覚したきっかけは、amazon prime videoで映画を観終わった後に、アプリの動作が重くなっていたので、状況を確認してみたら使用メモリが4GBにもなっていたことでした。盛大にメモリーリークが起きていたのです。検証してみたところ、他のアプリがフルスクリーン動作をしているときにメモリリークが起こることがわかりました。
これには二つの要素が絡みます。まず当アプリでは、他のアプリがフルスクリーン動作をしている時でも壁紙切替の進行を止めていないこと。システムの壁紙表示機能は、フルスクリーン動作のアプリがある場合には進行を止めています。背後で壁紙切替をしてもリソースの無駄にしかならないので本来はこれが正解だと思われますが、リソースの消費は微々たるものなので、それを無視すれば止めても止めなくてもどちらでも構わないというのが実情です。なので当初迷ったのですが、将来的に環境設定で選べるようにするとして、当面は続行させる仕様としたのです。止めても止めなくてもどちらでもいいのなら、手順の少ない方にしたわけです。これがまず第一点。
次に、当アプリで大容量のメモリリークを引き起こす要因は、表示の終わった画像のViewの削除漏れしかないのは、制作の段階からわかっていました。当アプリでは、現在表示中のViewと、次の画像を表示させるViewの二つしかViewを作らないので、表示切替が終わったら前の画像のViewを決め打ちで削除するコードを書いていました。一抹の不安はありましたが、コードの進行上三つ目のViewが作られることなどありえないはずと考えていましたが、そこに落とし穴があったようです。
なのでメモリリークを引き起こしている箇所はわかっていたので、コードの修正はすぐにできたのですが、どういう過程を経てViewの削除漏れが発生するのか、そのプロセスがわかりませんでした。
当アプリの表示切替の進行の流れは大雑把に次のようになっていました。
- (void)キュー実行メソッド { ⬅️ タイミング 1
表示切替メソッド呼び出し
//次の表示の遅延実行
[self performSelector:@selector(キュー実行メソッド)
withObject:添付オブジェクト
afterDelay:インターバル];
} //このメソッドを抜けた後に遷移エフェクトが始まる
- (result)表示切替メソッド: {
現在表示中(前の)のView = ・・・確保
次の画像を表させるView = 画像を読み込んで生成
次の画像を表させるView を デスクトップに追加
//遷移エフェクトの開始
[CATransaction begin];
[CATransaction setCompletionBlock:^{ ⬅️ タイミング 2
//遷移エフェクトの後処理
前のViewの削除
}]:
switch (トランジションのタイプ)
{
case タイプ1:
{
アニケーション設定
[前もしくは次のView.layer addAnimation:設定したアニメーション];
break;
}
case タイプ2:
・
・
}
[CATransaction commit];
}
キュー実行メソッドの中で、表示切替メソッドと次のキュー実行メソッドの予約を同時に行っていますので、切替間隔の時間の中に遷移エフェクトにかかる時間も含まれます。そのため、アプリ側の予約設定として最短の表示時間を5秒、遷移エフェクトの最長時間を4秒にしていました。
コードのあちらこちらを色々検証してみた結果、他のアプリがフルスクリーン動作中は、ポイントとなる二箇所の実行タイミングが、システムによってルーズに呼び出されているのが原因だとわかりました。
その様子を視覚的に示しているのが、XCodeがアプリの使用メモリの時間経過を表示したグラフです。

システムがなぜこのような計らいをするのかはとりあえず置いといて、結果遷移エフェクトが終わって後処理のブロックが呼び出される前に、次のキュー実行メソッドが呼び出される場合があるのが、ロジックが狂う原因でした。
呼び出しのタイミングが遅延するのはどうでもいいことですが、処理の順序が前後するのは困るので、Viewを確実に削除する修正だけでなく、次のキュー実行メソッドの予約を遷移エフェクトの終了通知の呼び出しの中で行うよう変更しました。これによって壁紙の表示時間の中に遷移エフェクトにかかる時間が含まれなくなり、最短の表示時間と遷移エフェクトの最長時間の絡みもなくなりました。遅延実行のメソッドも利便性を考慮してタイマーオブジェクトに変更し、結果進行を管理しているコードにかなり手を加えることとなりました。
今回の予定外のアップデートは、いい意味で怪我の功名でした。Ver.1.0のリリース時はシステムのスリープとモニタ構成の変化に対応させるのに手一杯で、フルスクリーン動作への対応を後回しにしたせいで、今回の事態に遭遇し、おかげでアプリの動作への理解が深まり、コードも練り込むことができました。又、バグ修正だけのアップデートではつまらないので、もう少し先のことになる予定だったCIFilterを使ったアニメーションにも挑戦しました。ver.1.1で実装したアニメーションはレイヤーのプロパティ変更によるものに留まっていましたが、やっぱり遷移エフェクトといったらCIFilterを使ったものが本命なので、それを前倒しで実装できたのは大きかったです。
時間ができたらCIFilterを使ったアニメーションについても記事にしたいと思います。
コメントする
※ 匿名希望の場合は空欄で構いません。
コメントを追加する キャンセル