モデルオブジェクトのプロパティgetterメソッドと同名のメソッドを作り込んでいるとRealmの初期化時にアプリが固まる

ちょっと前まで動いていた開発中のアプリが突然スプラッシュで固まるようになってしまい、Realmの初期化でなにかトラブルが起きることは把握したけど理由がぜんぜんわからず、あせってPodのアップデートをしたり過去のリビジョンと比較したり大騒ぎしてしまったんだけど、原因はRealmのモデルオブジェクトクラスに作っていたメソッドの名前を何の気なしにリネームしたら、それがモデルのRealmで永続化されるプロパティのgetterと衝突しており、そのせいでRealmを起動できず固まるという状態になっていた。

ドキュメントには「モデルクラスのsetterおよびgetterメソッドはオーバーライドできません」とは書いてあるけどそんな致命的な状態になるのはなんか別の原因があったのかな。Objective-Cそのもののgetter/setterの挙動で同じようにハマったこともあったような…ともかくあせった。


symbolicatecrashの処理が終わらないクラッシュレポート

iOSアプリのクラッシュ時に吐き出されるクラッシュレポートのメモリイメージをシンボルテーブルと引き合わせてヒューマンリーダブルなスタックトレースを得るsynbolicateというのをいままでやったことなかったんだけど、今回再現しないクラッシュの原因究明のためにやってみた。んだけど、なんか処理が終わらないように見える。初めてだったのでこういうもんかなとずっと待ってみたんだけど、やっぱり終わらない。perlスクリプトだというsymbolicatecrash(ここのを落とした)をいじって動作状況をみてみたところ、クラッシュリポートのメモリイメージに循環参照(? じゃなくて、単に同じメモリイメージの項目が大量にあるので処理に時間んがかかるだけかな)があってそこでシンボル検索が無限ループになって終わらないようになっているっぽかった。

while ( length($nextIDKey) ) {
    last if ( !length($images{$nextIDKey}{nextID}) );
    $nextIDKey = $images{$nextIDKey}{nextID};
}

というとこを、

while ( length($nextIDKey) ) {
    last if ( !length($images{$nextIDKey}{nextID}) );
    $nextIDKey = $images{$nextIDKey}{nextID};
    last if ( $nextIDKey == $images{$nextIDKey}{nextID} );
}

とかしたらとりあえず出力された。

同様の症状についての記事。クラッシュレポートのライブラリアドレステーブルが変になるのはiOS9からの現象らしい。


RealmがiPhone実機でだけクラッシュする(ことがあるらしい)

iOS開発が佳境で、そろそろ結合テストというタイミングのころ、iPhoneのいくつかの実機でだけアプリが起動直後にクラッシュするという現象が起きた。当該機種でもシミュレータでは再現しないし、手持ちの端末実機でも再現しない。しょうがないのでその機種を借りにいってその場で調べたら確かにクラッシュしてて、なにかと思ったらjsonファイルでバンドルしているマスタデータを初回起動時にまとめてインサートしてるとこで

json.forEach { v in
let master = Master()
try relam.write {
master.import(v)
}
}

的なコードで個別にトランザクションを張ってデータを入れてたら機種によっては(手持ちのiPhone5だと大丈夫でiPhone6だとだめだったりしたので、パフォーマンスの問題なのかも)クラッシュしてしまうらしかった。トランザクションをループの外で張るようにしたら大丈夫になった。ただあとでバグなら報告しようと思って後で戻して再度実行したらクラッシュしなくなってたのでなにか別の要因もあるのかも。

Realm使い心地としてはかなり最高だし次のプロジェクトでも絶対導入したいけど、初期データを投入したDBファイルを作っておくみたいなことはしにくいのはなんとかならないかな。


nibnameなしでViewControllerをinitするとiOS8だけでクラッシュする

配信したアプリがログイン後に落ちたりスプラッシュで落ちたりする、という報告を受けて、自分のところでは起きていなかったのでなんだろうと思って調べたら、画面の初期化時に読み込んでいるカスタムViewControllerのinitが間違っていて、しかもそれでもiOS9は動くためだった。

対応するnibを持つSubViewControllerを、

override func viewDidLoad() {

self.subVC = SubViewController()
}

と引数なしで生成すると、iOS9ではnibを探してロードしてくれるようで(ちゃんと調べてないけどそうなんだと思う)問題ないんだけど、iOS8は読み込まれないのでOutletのオブジェクトに触ろうとするとクラッシュする。

override func viewDidLoad() {

self.subVC = SubViewController(nibname: “SubViewController”, bundle: nil)
}

と正しく初期化すると問題ない。

swift - addSubview from xib - iOS 8 vs iOS 9 - Stack Overflow

あと、

iOS8系をサポートするなら、UILabelやUITextViewのフォントにHiraginoSansは使わないほうが良い - Qiita

  • Xcode7でiOS8系をサポートしたアプリを作成するときに、Interface Builder上(xibやstoryboard)で UILabel やUITextView のフォントに HiraginoSans を利用するViewControllerを作成すると、その画面ヘの遷移が遅くなる
    • 体感では、1つの HiraginoSans フォントを利用したUIパーツを配置する毎に1秒弱ずつ遅くなる
    • 現象が発生するのはアプリ初回起動時のみ
  • これはフォントに依存する問題で、System FontやTimes New Romanなど他のフォントを利用すると再現しなかった

こんな恐ろしい情報もあったので全部のUILabelのフォントを再設定した。


Apple World Wide Developer Relation証明書の期限切れ

Adhocビルドをdeploygateで配信しようとしたら、有効なiPhone Distribution  Certificateがないというエラーに。先週末は問題なかったのにと思いつつ調べてみると、Apple Developerライセンス自体が切れているわけではなく、iPhone Distributionで発行している証明書もまだ有効期限内。しかしKeyChainアプリで証明書を見ると「証明書の発行者が無効」というエラーになっていた。証明書のプロファイルを見ると、中間証明書であるApple World Wide Developer Relation Certificateが期限切れになってた。調べてみると2/14に切れてAppleDevで発行した証明書が一斉に無効になったらしい。こんなことがあるのか。

【注意】Apple Worldwide Developer Relationsの期限切れ - Qiita
Uploading archive error: “Missing iOS Distribut… | Apple Developer Forums

新しい中間証明書は2023-02-07までなので、前に切れたのも7年前だったならiOS開発者は初めて期限切れを経験したとかなのかな。


Realmを使ってみる

Realm is a mobile database: a replacement for SQLite & Core Data

新しいiOSアプリのデータストアはRealmを使ってみることにした。過去のiOS開発だとCoreDataを使ったりSQLite+FMDBを使ったりしていて、CoreDataは二度と使いたくない感じだったし、SQLiteは初期データを入れたDBファイルを使うプロジェクトでは便利だったけど今回はそういう要件もないので新し目の技術にした。

で触ってみているけどこれはだいぶ便利なのではないだろうか。なにがいいってモデルと別にスキーマを管理しなくていいとこがいい。永続化されるデータをほぼモデルそのものと考えることができる。APIも(RealmSwiftだと)rubyライクというか、まっとうなSwiftライブラリだということかもしれないけど、納得感が高い。

ひたすらありがたいなこれはーと思いながら実装している。


rackアプリでモックAPIをつくる

新規開発するiOSアプリの開発フェーズに入ったのでいろいろ準備したり新しいことを覚えたりしている。今回はSwiftで開発することにしたのでSwift向きのライブラリ環境を模索しているところ。

別チームが開発しているAPIが用意されるまでダミーの応答で初期開発をすすめるためのAPIモックもなんかやり方かえようかなと思ったけど、ここはどうせ作り捨てなのでいつものにした。いつものというのはリクエストに応じてjsonファイルを読んで返すだけのrackアプリをpowでローカルサーバにするやつ。

これが一番楽な気がしている。


deploygateのコマンドラインツールを使ってみた

DeployGate コマンドラインツール

去年コンテンツ違いで9つ作ったiOSアプリのバージョンアップの準備をしている。ファーストリリースのときは1本づつそれなりの時間をかけて出していたのでまあよかったけど、まとめて作業するとなると9本は流石にごつい数で、しかも実際にはそれぞれプロビジョニングとかAPIを切り替えた2バージョン(つまり全18バージョンとか)を扱わねばならないため、それぞれビルドしたりアドホック版の配布作業をするのがかなり大変。ということで、アドホック版のテスターへの配布に使っているdeploygateが提供しているデプロイツールを試してみた。

deploygateのデプロイ用cliツールは、ipaファイルをコマンドラインからアップロードしてくれるだけでなく、ちゃんとコマンドラインからの自動ビルド〜アーカイブ〜ipa書き出し(これもXCodeでやるとけっこう手間取る)や、あのレジェンド級にめんどくさいApple Dev CenterへのUDID登録からプロビジョニングダウンロード後のipa再作成も1コマンドでお願いできるというもの。ちょっとホントかいなと思っていたけど導入してみたらちゃんとやってくれた。iOSアプリのXCode外ビルドってなんとなくそんなにすんなり行かなそうな気がしていたけど、(最近は?)意外とすんなりいくものらしい。上記の通り異常にビルドターゲットがたくさんあるプロジェクトなんだけど、deployコマンドをたたくとビルドターゲットが列挙されて使うターゲットを選べるし問題なかった。とりあえずこれで配信作業はかなり楽になる。

ただこのcliツールはかなり使い方に制限を設けているのか、Build ConfigurationがRelease固定だったりとか(deployするんだからReleaseだろということか。とりあえずgemのソースを書き換えてAdhocコンフィグでビルドするようにした)、あとコマンドからのUDID登録もたしかにできるんだけど、コマンドから更新されたプロビジョニングには登録されたデバイスが全部追加されるっぽいのでけっこう困る。簡単コマンドは単純なユースケースを対象にして複雑なのは自前でビルドタスクをつくれということかな。


SimulatorRemoteNotificationsを使ってみた

acoomans/SimulatorRemoteNotifications - Objective-C

これ使ってみた。iOSシミュレータでプッシュ通知をシミュレートして受信時のテストを行うライブラリ。cocoapodsで導入できる。

didfinishLaunchingでライブラリを起動しておいて(デバッグターゲットのみで)、

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    ...

    if DEBUG
        [application listenForRemoteNotifications];
    endif

    return YES;
}

ターミナルで9930ポートにデータを送ると

echo -n '{"message":"message"}' | nc -4u -w1 localhost 9930

iOSシミュレータに起動中のアプリのdidReceiveRemoteNotificationが擬似的に呼ばれる仕組み。簡単に確認できてよかった。