Google Firebase で Crash Reporting を使う
まず初めに
後からこの issue で分かった事だけど
https://github.com/firebase/quickstart-ios/issues/13
Google Cloud Platform の利用規約に同意しておかないと下の手順の dSYM アップロード用のアカウントの作成やその権限付与でサーバエラーが起きて進めなくなってしまうので、Firebase にアプリを登録したらまずGCPのダッシュボードをチェックしてアクセスできるか確認しておいた方がいい。(これで結構時間を潰した)
GCPのダッシュボードを開くと以下のようなダイアログが出る場合があるので設定しておく
https://console.cloud.google.com/
セットアップ
Google Firebase の Crash Reporting を使うには dSYM アップロードのための準備を行う必要がある。
設定手順は以下
https://firebase.google.com/docs/crash/ios#upload_symbol_files
1 Download a service account key to authenticate your uploads
- プロジェクトの歯車アイコン「権限」から「サービスアカウント」を選択
- 「サービスアカウントを作成」を選択
- 任意のサービスアカウント名とサービスアカウントIDを入力して「新しい秘密鍵の提供」をチェック、「キーのタイプ」でJSONを選択して「作成」を選択
- アカウントを作成して鍵の作成に成功するとJSONファイルがダウンロードされる。また、作成したサービスアカウントに自動でプロジェクトの編集者権限も付与される。*1
2 Ensure that the service account has write permission
ここは権限の確認。
先の手順でサービスアカウント作成が正しく行われていれば、鍵が作成された事と必要な権限が付与された事がダイアログ表示されるのでこの確認作業はスキップしても良い。
3 Add an upload script for your symbol files
Xcode の Build Phase に Run Script を設定する。
# Replace this path with the path to the key you just downloaded JSON_FILE=Path/To/ServiceAccount.json # Replace this with the GOOGLE_APP_ID from your GoogleService-Info.plist file GOOGLE_APP_ID=1:my:app:id defaults write com.google.SymbolUpload version -integer 1 # creates file if it does not exist JSON=$(cat "${JSON_FILE}") /usr/bin/plutil -replace "app_${GOOGLE_APP_ID//:/_}" -json "${JSON}" "$HOME/Library/Preferences/com.google.SymbolUpload.plist" "${PODS_ROOT}"/FirebaseCrash/upload-sym
2行目のJSON ファイルのパス設定は、JSONファイルをプロジェクトディレクトリ内に置いて $SRCROOT や $PROJECT_DIR を使ってパスを指定すると分かりやすい。*2
以下のように指定する
JSON_FILE="$(PROJECT_DIR)"/<リソースフォルダ>/<ファイル名>.json
10行目の ${PODS_ROOT}
はCocoapodsを使っていないので
以下のように設定した
"${PROJECT_DIR}"/<外部ライブラリフォルダ>/FirebaseCrash/upload-sym
Cocoapods を使わない場合
Cocoapods を使わない場合、以下からリンク先のページから Firebase SDK を直接ダウンロードして必要な framework をXcodeプロジェクトに手動で入れていく。
Crash Report に必要な framework は FirebaseCrash.framework と Analytics 関連の framework 一式なので、これらを全てプロジェクトに取り込む。
どのサービスにどの framework が必要なのかは README に書いてある。
ダウンロードリンク
https://firebase.google.com/docs/ios/setup#frameworks
このダウンロード版の Firebase SDK には upload-sym など幾つかの必要なファイルが入っていないので、それらのファイルは適当なダミープロジェクトを用意して Cocoapods 経由で Firebase/Crash をインストールしてゲットするか、以下の FirebaseCrash の podspec に書かれている source のURLから直接ファイルを拾ってきてやる必要がある。
https://github.com/CocoaPods/Specs/tree/master/Specs/FirebaseCrash
ダウンロードした source のZIPファイルを解凍するとCLIやスクリプトファイルが同梱されているので、それらをまとめて Xcode の Run Script で指定したディレクトリへ移動orコピーする。
最終的にXcodeプロジェクトのディレクトリ内に用意したファイル一式は以下のようになった
試す
実際に試してみる。
ドキュメントでは assert を仕込んで Build&Runして、一旦 Xcode からアプリをStopさせてアプリをホーム画面から起動。
アプリがクラッシュしたのを確認したら assert を取り除いて再度 Xcode からBuild&Runしてログがレポートが送信されたのを確認したらウェブのダッシュボードを確認。
という流れになっている。
実際に試してみると
2016-06-07 21:58:57.160 AppName[76436:] <FIRAnalytics/INFO> Firebase Analytics v.3200000 started 2016-06-07 21:58:57.162 AppName[76436:11233546] Firebase Crash Reporting: Successfully enabled 2016-06-07 21:58:57.166: <FIRInstanceID/WARNING> FIRInstanceID AppDelegate proxy enabled, will swizzle app delegate remote notification handlers. To disable add "FirebaseAppDelegateProxyEnabled" to your Info.plist and set it to NO 2016-06-07 21:58:57.160 AppName[76436:] <FIRAnalytics/INFO> To enable debug logging set the following application argument: -FIRAnalyticsDebugEnabled (see http://goo.gl/Y0Yjwu) 2016-06-07 21:58:57.185 AppName[76436:] <FIRAnalytics/INFO> Successfully created Firebase Analytics App Delegate Proxy automatically. To disable the proxy, set the flag FirebaseAppDelegateProxyEnabled to NO in the Info.plist 2016-06-07 21:58:57.194 AppName[76436:] <FIRAnalytics/INFO> Firebase Analytics enabled 2016-06-07 21:58:57.911 AppName[76436:11233921] Firebase Crash Reporting: Crash successfully uploaded
という感じでログが出て、ドキュメントには15秒内にレポートが送信されると書いてあるところテストした環境ではアプリ起動後すぐにクラッシュレポートが送信されたのが確認できた。
ダッシュボードへの反映はドキュメントには20分かかると書いてあって、これはテスト時には15分弱でダッシュボードに反映された。
Known Issues
iOS の Crash Reporting は現在β版で現時点での Known Issues としてデバイスのモデル名が表示されないと書かれている。
この点はアップデートを待つしかない。
Device models may not be showing in error reports. This will be addressed in an upcoming Pod update.
Bitcode対応のアプリでクラッシュレポートを使う際の注意
アプリをBitcode対応してリリースしたら Crashlytics でクラッシュレポートが表示されなくなってしまった。
ダッシュボードには dSYMs が見当たらないとのメッセージが。
Found 1 unsymbolicated crash from missing dSYMs in 1 version in the last 24 hours.
Crashlytics のヘルプによればBitcode対応のアプリがApp StoreからリリースされるとAppleは新しく dSYMs を生成するらしく、この生成された dSYMs をアップロードする必要があるとの事。
手順は以下
https://docs.fabric.io/ios/crashlytics/missing-dsyms.html#bitcode-download
dSYMs は iTunes Connect からダウンロード、もしくは Xcode の Organizer で 「Download dSYMs...」 からダウンロードできる。
Xcode の場合は dSYMs をダウンロードした後ターミナルで
mdfind "com_apple_xcode_dsym_uuids == <UUID>"
と実行して dSYMs のある場所を探さなくてはならないのでちょっと面倒。
dSYMs のダウンロード先は以下のような場所になっていて、この xcarchive の中の dSYMs フォルダにいくつか dSYM ファイルが入っている。
/Users/<UseName>/Library/Developer/Xcode/Archives/yyyy-MM-dd/<AppName> yyyy-MM-dd HH.mm.xcarchive
iTunes Connect からダウンロードする場合はリリース済みアプリのアクティビティから該当のバージョンのビルドを選択して「dSYM をダウンロード」を選択。
dSYMs.zip という名称でZIP形式でダウンロードされる。
ZIPファイルの中身は Xcode からダウンロードした時と比べて増減があって中身が異なっているけど、どちらにしてもとりあえず Crashlytics で missing になっている dSYM と一致する UUID のものが入っていればOK
Crashlytics の設定ページから「Missing DYMs」のタブを選択して dSYM ファイルか dSYM ファイルが入ったZIPファイルをアップロードして、Missing DYMs のリストに表示されている UUID に打ち消し線が入れば成功。ダッシュボードでシンボルを確認できるようになる。
今回は Fabric Crashlytics での方法だったけど、Google Firebase でも同様にBitcode対応のアプリの dSYM をアップロードし直す必要があるはず。
追記
fastlane を使えば Bitcode 対応アプリの dSYM を iTunes Connect から自動で引っ張ってきてくれるとの事
Atom のデフォルト syntax を変更する
GitHub製のエディタAtomでplistを開くとデフォルトでは Property List (Old-Style)
として開かれるが、今時のplistファイルはOld-Style形式ではなくXML形式なので syntax error になってしまう。
メニューの Edit>Select Grammar かウィンドウ右下のフォーマット名をクリックして Property List (XML)
に変更すれば syntax が正しく解釈される。
この設定は新しくplistファイルを開く都度行わなくてはならない。
そこでplistファイルはデフォルトで Property List (XML)
で開くように設定する。
設定方法は以下の公式ドキュメントを参照
http://flight-manual.atom.io/using-atom/sections/basic-customization/#_customizing_language_recognition
メニューの Atom>Config... を選択して config.cson ファイルを開く
core: themes: [ "atom-light-ui" "one-light-syntax" ]
上記の config.cson に customFileTypes を追加する
core: themes: [ "atom-light-ui" "one-light-syntax" ] customFileTypes: "text.xml.plist": [ "plist" ]
これでAtomでplistファイルを開くときはデフォルトで Property List (XML)
で開くようになる。
Property List (XML)
の identifier はパッケージから Property List を検索して Property List (XML)
の Scope: を参照する。
もしくは以下のソースファイル末尾の scopeName
を参照する。
https://github.com/atom/language-property-list/tree/master/grammars
Property List (Old-Style)
の scopeName は source.plist
Property List (XML)
の scopeName は text.xml.plist
となっている。
watchOS 1からwatchOS 2への置き換え例
watchOSとiOSでデータのやり取りをしている場合のwatchOS 1からwatchOS 2への置き換え例
watchOS
watchOS 1
[WKInterfaceController openParentApplication:userInfo reply:^(NSDictionary *replyInfo, NSError *error) { if (error) { } else { } }];
watchOS 2
openParentApplication:reply:
は使えなくなったので WatchConnectivity.framework の WCSession
を使った実装に置き換える。
[WCSession isSupported]
はwatchOSでは常にYESを返すので isSupported
のチェックは無くても良い。
セットアップ
- (void)awakeWithContext:(id)context { [super awakeWithContext:context]; WCSession *session = [WCSession defaultSession]; session.delegate = self; [session activateSession]; }
データ取得リクエスト
[[WCSession defaultSession] sendMessage:userInfo
replyHandler:^(NSDictionary<NSString *,id> * _Nonnull replyMessage) {
}
errorHandler:^(NSError * _Nonnull error) {
}];
iOS
iOS 8 with watchOS 1
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply { NSDictionary *dict; // configure reply data reply(dict); }
iOS 9 with watchOS 2
WCSession
をセットアップして delegate でのメッセージの受け取りとデータの返却を行う。
WCSessionDelegate
は色々とメソッドのバリエーションがあるけど、watchOS 1の時のデータのやり取りをそのまま置き換える場合は didReceiveMessage:replyHandler:
を実装すればOK
セットアップ
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if ([WCSession isSupported]) { WCSession *session = [WCSession defaultSession]; session.delegate = self; [session activateSession]; } return YES; }
データ取得リクエストへの応答
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message replyHandler:(void(^)(NSDictionary<NSString *, id> *replyMessage))replyHandler { NSDictionary *dict; // configure reply data replyHandler(dict); }
Playground で delegate を使う
Playground で delegate メソッドを扱うにはどうする?
delegate が呼ばれるクラスの実装が必要、Playground でクラス定義できるの?
というあたりが疑問だったけど Playground でも普通にクラス定義が可能で delegate も試せるらしい。
下記の前回のコードに
クラス定義とクラス生成、delegate の設定のコードを追加してみる。
これで delegate メソッド内で出力している print の内容がコンソールに順次表示されていく。
クラスを定義する箇所はコードの先頭でなくても良くて、delegate を生成する直前にクラス定義のコードを挟むなんて事も出来る。
Playground でオーディオ再生(音声読みあげ)をする
Playground を使って簡単なオーディオ再生のテストを行う。
スニペットを実行確認するのにいちいちダミーのアプリを作ったりしなくていいのが Playground の便利なところ。
Playground なので言語は Swift
まずババッと以下のコードを書いてみる。
オーディオファイルを用意したりするのも面倒なので簡単に AVSpeechSynthesizer
を使ってテキスト読み上げを行う。
import UIKit import AVFoundation let string = "あのイーハトーヴォのすきとおった風、夏でも底に冷たさをもつ青いそら、うつくしい森で飾られたモーリオ市、郊外のぎらぎらひかる草の波。またそのなかでいっしょになったたくさんのひとたち、ファゼーロとロザーロ、羊飼のミーロや、顔の赤いこどもたち、地主のテーモ、山猫博士のボーガント・テストゥパーゴなど、いまこの暗い巨きな石の建物のなかで考えていると、みんなむかし風のなつかしい青い幻燈のように思われます。" let synthesizer = AVSpeechSynthesizer() let voice = AVSpeechSynthesisVoice(language: "ja-JP") let utterance = AVSpeechUtterance(string: string) utterance.voice = voice synthesizer.speakUtterance(utterance)
読み上げするのが日本語テキストのため AVSpeechSynthesisVoice
を使って日本語の声を設定してやる必要があるのに注意。
(Playground では Mac のシステム環境に設定に関わらず en_US の Locale で動作するようで、en_US の声で日本語テキストを読み上げさせてもできない。)
と、このコードを実際に Playground で実行してみると一番最後の行を実行した時にすぐ処理が終了してしまい、音声読み上げがされないという事態に直面する。
ここが今回の肝で、Playground でオーディオ再生をするには XCPlayground
module の XCPlaygroundPage
クラスを使う。
import XCPlayground XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
これでOK
以下ドキュメントより
Set needsIndefiniteExecution to true to continue execution after the end of top-level code.
最終的な実装コードは以下
これで Playground 上でオーディオ再生(音声読みあげ)を実行確認する事ができる *1
*1:うまく再生されない場合は Playground のウィンドウ下部にある再生停止ボタンを押して処理を再実行させてみると良い
中国語のローカライズ表記
iOS の言語設定の言語一覧画面と同じような画面を作ろうと思って実装してみたら中国語の場合に思い通りにいかなかったメモ
iOS の言語設定の言語一覧画面
cell.textLabel.title
には現地語でのローカライズ表記
cell.detailTextLabel.title
にはシステム言語でのローカライズ表記で言語が表示されている。
これらをとりあえず見えてる範囲で同じ表記になるよう実装してみる。
ロケールの identifier は以下のように場合によっては言語コード+国コードの形になっている。
コメント部分に記載した様におおよそ設定画面と同じように表示されている。*1
問題は中国語で、まず日本語表記では「簡体中文」「繁体中文」と表記されて欲しいところが「中文」のみが表示されている。
また現地語表記では「香港」が「中華人民共和国香港特別行政区」と表示されている。
試しにコードから取得できる中国語の全リストを洗ってみる。
香港とマカオは 中華人民共和国XXX特別行政区
と付与されるらしい。
これをシステム設定の表記に合わせるには、直接 displayNameForKey:
メソッドを呼ばずに1つクッションをかまして特定の NSLocaleIdentifier
の時には自前で用意したローカライズ文字列を返す様にしてやる事で対応できる。
また「中文」の表記の部分についても同様に、システム設定の表記に合わせるには特定の NSLocaleLanguageCode
の時には自前のローカライズ文字列を返す様にする事で対応できる。
*2