LoginSignup
11
12

More than 5 years have passed since last update.

この記事は Appcelrator Titanium Advent Calendar 2015 の 21 日目の記事です。

今年の夏、私は Titanium のある挙動に悩まされていました。よく Titanium でハマるのは Android であると言われ、私もイベントで LT / 登壇することがあるとそのように言ってきたのですが、今回は iOS で発生した問題と、その解決法についての共有です。

JSON をどう扱いますか?

言わずもがな、 Titanium は JavaScript を使ってネイティブ UI を持つモバイルアプリケーション開発を行う環境です。そして JSON は JavaScript Object Notation が正式名称であるように、 JavaScript 生まれのメッセージフォーマット。つまりは Titanium と JSON の相性は抜群で、

var json = JSON.parse(json_text);
var text = JSON.stringify(json);

たったこれだけでシリアライズ・デシリアライズが行えます。 JSON をレスポンスフォーマットとした Web API が非常に多く公開されている今、クライアントアプリケーションを作るために Titanium を選ぶのは、理にかなった選択肢と言えます。

64bit

iPhone 5s 以降 64bit CPU が搭載された iOS デバイスが登場し、今では完全に 64bit に移行済みとなった iOS プラットフォームですが、今年の 2 月以降、 Apple からお達しで、 64bit バイナリ (ARM64 バイナリ) が同梱されていないアプリの提出ができなくなりました。

Titanium では Titanium SDK の他、使用しているネイティブモジュールも 64bit 対応を行う必要があり、 SDK は 3.5.0.GA から 64bit に対応しました。 64bit になったからと API に変更があるわけではなく、前述の JSON API もそのまま動作する はず でした。

メモリーリーク

memory_leak.png

とある案件で事件は起きました。 64bit 環境の iOS デバイスで大きめの JSON (1MB) を JSON.parse を使ってデシリアライズすると、途端にメモリリークを引き起こすのです。急激にメモリを食いつぶしてしまい、頻繁にクラッシュを引き起こしていました。 Instruments を使って確認してみると上のような状態でした。

Titanium SDK を 64bit 非対応の世代のものに戻してみると、この問題は発生しませんでした。 JSON.parse のタイミングを変えてみたり、 JSON オブジェクトを使い終わったら null を入れてみたりするなど試みましたが、この問題は解決しませんでした。ちなみにこの案件では Titanium SDK 4.0.0.GA を使用しており、タイミング的に 4.1.0.GA や 5.0.0.GA が登場していたのですが、これらに切り替えても問題は解決しませんでした。

Titanium 向けの JavaScript Core は GitHub で公開されていますが、 Titanium SDK のバージョンが違っても使われる JavaScript Core のバージョンが同じであるため、 Titanium SDK を変えても挙動の改善が見られなかったと推測しています。

モジュールの力

Titanium を本格的に仕事で使用してモバイルアプリケーションを作り始めると、様々な場面で "モジュール" を活用し始めます。ほとんどの場合は著名な iOS / Android 向けのライブラリを Titanium からも使えるようにする、 "ブリッジ API" を作ったり、ちょっとした処理をネイティブに委譲するためのものを作り、使うことになるでしょう。 Titanium 自体が備えている機能を代替することは滅多にありません。

滅多にないはずでしたが、今回の問題に対応するためには、 JSON を処理するという Titanium 自体の機能を代替する必要がありました。 JSON のシリアライズ・デシリアライズを行うモジュールの構築です。

実は 2 年前に実験と LT のネタにするために JSON モジュールを作っていました。このモジュールでは iOS 5 以降に標準搭載されるようになった NSJSONSerialization とサードパーティライブラリである JSONKit / SBJson を使っていましたが、今回は安定した動作が期待できる NSJSONSerialization ベースで新しく開発することにしました。

さらに、 Titanium SDK の iOS 向けコードを grep してみると、 TiUtils.m に興味深い記述が見つかります。 Titanium SDK 4.0.0.GA のコードを見てみましょう。なんと、 Titanium SDK の内部で NSJSONSerialization を使ってシリアライズ・デシリアライズを行うためのコードが実装されています。これを活用しない手はありません。

そんなわけで、モジュールを作りました。今回は案件対応のための複合的なモジュールから JSON 処理だけを行う部分を分離させ、 GitHub に配置しました

コンパイル済みのモジュールのバイナリは GitHub Releases に登録してあります

var module = require('com.r384ta.ti.module.tinativejson');
var json = module.parse('{"key": "value"}');
var text = module.stringify(json);

このモジュールを使った効果は絶大で、 Instruments 上で 600MB 程度のメモリ使用量まで膨れあがっていたものが、 1/10 の 60MB 程度のメモリ使用量に抑えられることになりました。案件では 1MB 程度の JSON 以外でも効果はあり、全体的に省メモリで JSON の処理を行える結果が得られました。

最後に

今回の例は、大きな JSON を使った場合に発生する特殊な例とは思いますが、原因が特定できるまでかなりの時間を費やした問題でした。最初はまさか JSON を処理するという基本的な API に問題があるとは考えられず、全く見当違いの場所を探ってしまっていたのですが、 Instruments を使うことで問題を目の当たりにして、こういうこともあるのだな……と。

もしも大きな JSON を取り扱うような Titanium iOS プロジェクトを実践されている方がいて、同じような問題に直面したらモジュールをご活用ください。メモリ使用量に関しても、これまた別途モジュールを組み立てるか Instruments で監視をしないと発覚しづらいため、一度確認されることをお勧めいたします。実は……なんてこともあるかもしれません。

11
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
12