Xcode で環境別にコンパイルするために Staging の Configuration を作ったんだけど、 -D STAGING
を指定しても #if STAGING
が false になっていた。
なんでかな〜 って設定眺めていたら、 Active Compilation Conditions が Debug になっていたのが原因だった。
これを STAGING にしたら #if STAGING
がうまくいった。
CircleCI を使って、テストが通ったら Heroku に deploy するという流れを構築する。
サンプルリポジトリ: starhoshi/rails-circleci
ソースコードは starhoshi/rails-circleci。
$ gem install rails $ rails new . --api -d=postgresql $ rails g controller
これで Rails Project + users_controller が作成される。
また、コメントアウトされている test のコメントを外し、通るようにする。
test/controllers/users_controller_test.rb
require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest test "the truth" do assert true end end
GitHub に Repository を作って、 Push しておく
CircleCI に GitHub でログインしておく。
※ CircleCIでRails4アプリをHerokuへデプロイする - Qiita にあるように、 ssh key の登録が必要かもしれない。
Projects > Add Project > rails-circleci を Set Up する。
そうすると勝手に CircleCI が回り出して、テストが成功する。
こんな感じで作成する。
https://dashboard.heroku.com/account から API Key を取得
https://circleci.com/account/heroku に取得した API Key を設定する。
これで Heroku / CircleCI の設定が完了。
deployment: production: branch: master commands: - heroku maintenance:on --app rails-circleci - git push git@heroku.com:rails-circleci.git $CIRCLE_SHA1:refs/heads/master - heroku run 'rake db:migrate; rake db:seed_fu' --app rails-circleci - heroku maintenance:off --app rails-circleci
こんな感じで作成。 --app と git@heroku.com:rails-circleci.git
は自分の環境に合わせる。
git push すると自動で CircleCI が動いて、 test が実行されたあとに deploy される。
これでめでたく当初やりたかったことができた。
Not Deploying になって、 deploy がされないことがわかる。
Gemfile.lock を最新に保つため、bundle update を毎日自動でできるようにしたい。
Tachikoma.io というサービスもあるみたいだけど、 private repo は有料っぽいので自作した。
勝手に update されてアプリケーションがバグると困るので、
という手順で行う。
ここら辺を参考にした。
lib/tasks/bundle_update_pull_request.rake
を作成する。
# frozen_string_literal: true namespace :bundle_update_pull_request do desc 'pull request if bundle updated' task check: :environment do project = 'repo-team/repo-rails' branch = 'master' token = 'CircleCI Token' params = '{"build_parameters": {"BUNDLE_UPDATE": true}}' header = { 'Content-Type' => 'application/json' } client = Net::HTTP.new('circleci.com', 443) client.use_ssl = true response = client.post("/api/v1/project/#{project}/tree/#{branch}?circle-token=#{token}", params, header) end end
CircleCI の Nightly Builds を叩くだけの task。
params に '{"build_parameters": {"BUNDLE_UPDATE": true}}'
を渡していて、 CircleCI ではこのパラメータをみて bundle update するのか判断する。
この task は以下のコマンドで実行できる。
$ bundle exec rake bundle_update_pull_request:check
circle.yml をこんな感じで書く。
test: post: - > if [ -n "${BUNDLE_UPDATE}" -a "${CIRCLE_BRANCH}" = 'master' ] ; then bundle update fi deployment: master: branch: master commands: - > if [ -n "${BUNDLE_UPDATE}" ] ; then bash script/circleci/create_pull_request_if_needed.sh fi
2 で params に渡した BUNDLE_UPDATE
が true だったら bundle update 実行 + PR 作成するようになっている。
script/circleci/create_pull_request_if_needed.sh
を作成する。
#!/bin/bash export BRANCH=bundle-update-`date -u "+%Y%m%d"` if [[ -n `git status -sb 2> /dev/null | grep Gemfile.lock` ]] ; then git config --global user.email git@git.com git config --global user.name 'git' git add Gemfile.lock git commit -m 'Bundle update' git branch -M $BRANCH git push origin $BRANCH bundle exec ruby script/circleci/create_pull_request.rb fi
3 で実行された bundle update で、 Gemfile.lock に差分があれば PR を作成するだけ。
script/circleci/create_pull_request.rb
を作成。
# frozen_string_literal: true require 'octokit' client = Octokit::Client.new(access_token: 'GitHub Personal access tokens') client.create_pull_request( 'repo-team/repo-rails', 'master', ENV['BRANCH'], 'Bundle update', '' )
これで PR が作成される。
ここまでで bundle exec rake bundle_update_pull_request:check
で Bundle update の PR が自動で作成されるようになった。
ここでは Heroku を使っているけど、定期実行できればなんでも良い。
こんな感じで GitHub に毎日 PR がくるようになります。
これを人間が目視で確認して問題なければマージしましょう。
しかし、目視といっても version が変わったことしかわからないので、これを実際に運用するにはテストがしっかりと書かれていて、そのテストが壊れてないことを確認したらマージするようにしないとダメですね。
import MediaPlayer ... override func viewDidLoad() { super.viewDidLoad() let player = MPMusicPlayerController.systemMusicPlayer player.setQueue(with: MPMediaQuery.songs()) player.play() }
これで音楽が再生できたんだけど、再生開始した瞬間にアプリがクラッシュした。どうやら権限が足りていないようだ。
端末の音楽情報を参照するには Info.plist に privacy を書かねばならない。
<key>NSAppleMusicUsageDescription</key> <string>Reference music information to play music.</string>
こんな感じで NSAppleMusicUsageDescription
に対してアクセスしたい理由を書けば OK。
(PropertyList で見る場合は Privacy - Media Library Usage Description
になっている。)
参考: ios - App crashes when running on iPhone with violations as exception - Stack Overflow
TestFlight という、アプリのベータ版などを配布する Apple 純正のサービスがある。
これのいいところは「リリースするアプリと同じアプリをテストできる」というところ。
実際にユーザが使うアプリと同じもので動作確認ができるし、同じアプリファイルで Apple は審査を行うのでバグがあったら Self Reject もしやすい。
なので私はいつも
という流れでリリースをするようにしている。
(この流れは fastlane で自動化している fastlane-example/Fastfile, fastlane deliver を使ってコマンド1発で Waiting For Review までもっていく)
先日アプリがクラッシュっするバグを出してしまって、そのバグの原因はアプリの過去バージョンに起因するものだった。
クラッシュする原因は以下の2つ。
今回 TestFlight に助けられたのは 2 のパターンの原因特定 / デバッグのところ。
最初はクラッシュする原因が全然わからなかったんだけど、 TestFlight を見てたら過去バージョンがズラ〜っと並んでいて、なんとなく ver 1.X.X のをインストールした後に ver 3.0.1 をインストールしたらクラッシュして原因特定できた。
原因特定できた後も修正確認のために ver 1.X.X をインストールして、 ver 3.1.0 をその後インストールして動作が問題ないか、というテストができた。
もし TestFlight がなかったらデバッグが大変になってて、 ver 1.X.X をインストールするためには Xcode 8.X を持ってきて... というところから始めないといけなくなる。 AppStore からも基本的に過去 ver は DL できないし。
TestFlight で配布していたおかげで過去バージョンからのマイグレーションテストも手軽にできて助かった。
ここ最近は Apple の審査が早いけど、致命的なバグなどを出してしまったら急いでレビューして欲しい、そういう時のために apple expedited review
というのが用意されている。
これを使うと優先的にアプリをレビューしてくれる。
しかしこれには注意が必要で、使いすぎてはいけないのでマジでやばい時にだけ使うべき。
今回はマジでやばいバグを生み出したので特急審査を出した。
* マジでやばいバグの図
iTunes Connect にログインして右下の Contact Us をクリック
を選択すると特急審査へのリンクが表示されるので、 Request an Expedited App Review
をクリック。
なんか突然今っぽいデザインになる。
内容をいい感じに入力して、 Explanation に説明を書く。
ここはなぜ特急審査が必要なのか真面目に書いた方がいいと思う。
自分は Apple への連絡はいつも英語 + 日本語で投げるようにしている。(英語に自信がないので...)
最後に Send
を押すと「問い合わせ受け付けたよ!」みたいな画面が出ておわり。
なお、問い合わせ完了メールなどは来ないもよう。
あとは迅速に審査が終わることを祈るのみ、しかし日本時間15時に特急審査願いを出して12時間経っても音沙汰なし。
普通のレビューと大して変わらないのでは...?
WWDC2017 で発表された機能。 1%, 2%, 5% ... 100% と限られた人から段階的に自動アップデートされていく。
新機能のテストや、バグが起きてないかなどの確認で使えそう。
これは 自動アップデート が対象なので、新規インストール/手動アップデートされると最新版がインストールされる。
自動アップデートされるのは 5% だけ。
でも iTunes Connect の分析画面では 30% くらいいってそうだ...。
同じく Firebase を見ても 30% くらいいってる。
確かに100%リリースに比べると浸透率は穏やかなんだけど、自動アップデート以外の人は普通にアプデされるしあまり意味なさそう...。
ちゃんとやるなら Firebase の A/B Testing とか Remote Config でやった方が全然良い、これはないよりマシかな〜くらいの機能。
新機能リリースでバグがないか不安で段階的リリースを使ったんだけど、3日目になってクラッシュ報告が特にないので100%リリースに変更した。まあやっぱりないより便利だ。
とある View の上に透明な UIView を載せて、 Swipe だったら透明な View でイベントをハンドリングして Tap だったら後ろの View にスルーしようとしたけど出来なさそうだった。
こういう View があって、右にスワイプしたら数値を200にして、左にスワイプしたら数値を100にしたい。でもボタンタップや TextFeild の入力はできるようにしたい。
この View の上に透明な View を被せて Swipe イベントを取って、 Tap イベントだったら後ろの TextField やボタンをタップできるようにすれば良さそうじゃんって最初は思った。
でも出来なさそう。
isUserInteractionEnabled = false
にすると透明な View のタップイベントがスルーされ後ろにある View をタップできる。
しかしこれだと Swipe もスルーされてしまうのでだめ。
hitTest を override して、 return nil しても後ろの View にイベントを流せる。しかし Swipe も反応しなくなってしまうのでダメ。
if event?.type == .swipe { }
ができればよかったが、UIEventType は touches
, motion
, remoteControl
, presses
の4種類しかなく、 Tap もスワイプも touches
になってしまいダメだった。
hitTest と似てる point というものあって、これはタップの判定位置を広くしたり狭くしたりできるっぽい。
ここで false を返すと後ろの View にイベントを流せる。
(しかしそういう用途には hitTest を使うべきで point は純粋にタップ範囲の変更に使った方が良さそう。)
透明な UIView を上に貼って、 Tap だったら後ろに流すというのは難しそう。
なので、透明な UIView の中にボタンや TextView を addSubview した。
class SwipableView: UIView { let leftSwipeGesture = UISwipeGestureRecognizer() let rightSwipeGesture = UISwipeGestureRecognizer() override init(frame: CGRect) { super.init(frame: frame) leftSwipeGesture.direction = .left rightSwipeGesture.direction = .right addGestureRecognizer(leftSwipeGesture) addGestureRecognizer(rightSwipeGesture) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
let swipableView = SwipableView() contentView.addSubview(swipableView) swipableView.leftSwipeGesture.rx.event.subscribe { event in // left event handler ... } aIVSwipeView.rightSwipeGesture.rx.event.subscribe { event in // right event handler ... } let textField = UITextField() swipableView.addSubView(textField) let button = UIButton() swipableView.addSubView(button)
これで Swipe した時はそれぞれハンドリング出来て、 textField や button のイベントはそれぞれちゃんと扱える。
最初からこうしろよ、という話なのだがこれをすると既存の View 構造を変えないといけないので気が重かった、透明な View を上に貼り付けるだけなら既存の View 構造は変えなくて済むので...。
遠回りだったかもしれないけど hitTest や pointinside の知識が深まってよかった。
Function を消したいけど、 Firebase Cloud Function 上では削除メニューなどが見当たらない。 その場合は GCP の方から関数を削除する。
・・・
から削除を選択で削除できる。
監視している中で対象を update して無限ループが発生してしまった場合などに便利。
export const cartIsSubmitted = functions.database.ref('/v1/user/{userID}/name').onWrite(event => { // … event.data.adminRef.update({name:newName}) })
ソースコードから削除してデプロイしても消せますね。
— コキチーズ🔥肉食獣 (@k2wanko) 2017年11月9日
急いで消したいとか言う場合はコンソールからのが便利ですが
Cloud Function をローカルで実行する方法を用意してくれている: ローカルでの関数の実行 | Firebase
手順はドキュメントに書いてある通りで簡単にできる。
$ npm install --save firebase-functions@latest $ npm install -g firebase-tools
これは https://my-firebase-url/sample?text=text
にアクセスするとその text を返却するだけのもの。
index.js:
const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(functions.config().firebase); exports.sample = functions.https.onRequest((req, res) => { const text = req.query.text; console.log(text); response.status(200); });
$ firebase experimental:functions:shell i functions: Preparing to emulate functions. ✔ functions: sample firebase >
これでデプロイ成功で対話モードになっているので、コマンドを入力する。
request の記法で動く。
firebase > sample.get('?text=text'); Sent request to function. info: User function triggered, starting execution info: text RESPONSE RECEIVED FROM FUNCTION: 200, {"text":"text"} info: Execution took 8 ms, user function completed successfully
こんな感じでローカルデバッグできる。
Cloud にデプロイするのちょっと時間がかかるし、ログ見るのも Cloud Function のログを見ないといけないのでだるいけど、ローカルデバッグだと関数書き換えたらリアルタイムデプロイ & ログがその場で見えるので手探りで開発するときには便利。
あと Cloud Function で外部 API と通信しようとするとお金がかかるけど(プラン変更が必要)、ローカルデプロイだと外部 API と通信もできた。
Cloud Function がローカルで動くというのがイケてて良い。