Firebase Cloud Function の関数を削除する

Function を消したいけど、 Firebase Cloud Function 上では削除メニューなどが見当たらない。 その場合は GCP の方から関数を削除する。

  1. https://console.cloud.google.com/projectselector/home/dashboard にアクセス
  2. 画面左上の「プロジェクトを選択」から該当のプロジェクトを選択
  3. 画面左メニューの Cloud Functions に遷移
  4. Function 一覧から削除したい関数を見つけて、右にある ・・・ から削除を選択

で削除できる。

監視している中で対象を update して無限ループが発生してしまった場合などに便利。

export const cartIsSubmitted = functions.database.ref('/v1/user/{userID}/name').onWrite(event => {
    // …
    event.data.adminRef.update({name:newName})
})

追記

Firebase Cloud Function をローカルで実行する

Cloud Function をローカルで実行する方法を用意してくれている: ローカルでの関数の実行  |  Firebase

手順はドキュメントに書いてある通りで簡単にできる。

firebase ライブラリの更新

$ npm install --save firebase-functions@latest
$ npm install -g firebase-tools

ローカルデプロイ

まず Function を作る

これは 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 がローカルで動くというのがイケてて良い。

fastlane deliver を使ってコマンド1発で Waiting For Review までもっていく

$ bundle exec fastlane release

このコマンドを叩くだけでリリースビルド、iTunesConnect へバイナリ提出、IDFA 情報など入力して審査待ちの状態まで持っていけるようにした。

Deliverfile

こんな感じに書いてる。

app_identifier "com.myapp"
username "appleid@hoge.com"

force true
skip_screenshots false
skip_metadata false
skip_binary_upload false
automatic_release true
submit_for_review true
overwrite_screenshots true
ignore_language_directory_validation ['fonts']

submission_information({
  export_compliance_encryption_updated: false,
  export_compliance_uses_encryption: false,
  add_id_info_uses_idfa: true,
  add_id_info_serves_ads: true,
  add_id_info_tracks_install: false,
  add_id_info_tracks_action: false,
  add_id_info_limits_tracking: true,
})

submit_for_review true にして、 submission_information を書けばいいっぽい。あと force true にしないと申請情報の確認画面が出てしまう。
encryptionadd_id_info は普段手動でぽちぽちしてる状態になるようにした。

他の option 一覧は fastlane/app_submission.rb にある。

Fastfile

deliver で has completed processing まで持っていくので、その後 dsyms を iTunesConnect から DL して Crashlytics に Upload できる。

  desc "Deploy a new version to the App Store"
  lane :release do
    match(type: "appstore")

    increment_build_number(build_number: "#{Time.now.strftime("%Y%m%d%H%M")}")

    gym(scheme: ENV["RELEASE_GYM_SCHEME"])

    deliver

    upload_symbols_to_crashlytics
    
    refresh_dsyms

    payload = {"Git Commit" => changelog}
    slack(
      channel: ENV["SLACK_CHANNEL"],
      message: ":itunesconnect: Successfully uploaded a new App Store build",
      payload: payload,
      default_payloads: default_payloads
    )
  end

Fastfile はソース公開しているのでこっちで全体像を見れる: fastlane-example/Fastfile

おわり

寝る前に bundle exec fastlane release 叩いて、寝ながら Youtube 見てたら Waiting For Review の通知きて最高。

人間だもの

読んだ: 自戒 - <y>

わかる。

自分の書くコードはクソでエンジニア向いてないからエンジニアやめたいって思うけど俺にはプログラミングしかないからコード書くことで生きていくしかないから勉強しないといけないけど勉強しても理解度が低いし人生なんのために生きているんだプログラムを書くために生きているのかいや違う人生楽しむために生きているのに俺は強迫観念や劣等感にかられ勉強や仕事をしているが果たしてそれは生きているというのか死んだほうがいいのではパン屋さんになりたい何が圧倒的成長だ成長している人間より人生楽しんでいる人間のほうが絶対にいいみんな仕事できるし人生楽しそうだしすごい人間死んだら終わりだし何をがんばっているんだなどということを1日3回くらい考えていてそしてまた今日も1日が終わる。

でもいいじゃない、つらいときはつらいし楽しいときは楽しい。
べつにコンビニのバイトでも生きていけるし、そんなに頑張らなくてもいいじゃない、人間死ぬときは死ぬ、人間だもの。

個人アプリを Xcode9 Swift4 対応した時にやったこと

個人アプリ2つを Xcode9 GM で動くようにした。
これは私のアプリの場合なので、違うプロジェクトでは不要なことや、他にもっとやらないといけないことがあると思う。

やったこと

1. Xcode9 DL

Xcode - Apple Developer から DL してインストール。
Xcode8もまだ使うので、8と9を同居させるため Xcode9GM.app にリネームした。

2. xcode-select で 9GM を使うようにする

$ sudo xcode-select -s /Applications/Xcode9GM.app/Contents/Developer/
$ xcode-select -p
/Applications/Xcode9GM.app/Contents/Developer

3. bundle update

念のため bundle update して cocoapods や fastlane を最新にした。

4. ライブラリ更新

$ pod update
$ carthage update --platform iOS --no-use-binaries

ここで Xcode 9 でプロジェクトをビルドすると動くようになった。 と言ってもこの時点ではまだ Swift3.2 のはなし。

5. Swift 4

メニュー > Edit > Convert > To Current Swift Syntax… を選ぶ、そうすると Xcode が自動でマイグレーションしてくれる。
マイグレーションしてくれたのはほとんどが dynamic@objc をつけるものだった。

あとはこんなところを手動で直した。

- let attributes = [NSFontAttributeName: UIFont.fontAwesome(ofSize: 20)] as [String: Any]
+ let attributes = [NSAttributedStringKey.font: UIFont.fontAwesome(ofSize: 20)]

- dismiss(animated: true, completion: { [weak self] _ in
+ dismiss(animated: true, completion: { [weak self] in

これだけで Swift4 のビルドは通った。 しかし起動したらエラー。

6. UIVisualEffectView を修正

let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .extraLight))
- effectView.addSubview(UILabel())
+ effectView.contentView.addSubview(UILabel())

UIVisualEffectView は contentView に subview を追加しないといけなくなった。
これは実行時エラーなのでコンパイルは通ってしまう。

これを直したら起動した。

7. warning 対応

Swift3 @objc InterfaceOff にした。
また、 substring が使えなくなったり CGFloat(truncating: String) を使うように修正した。

7. Runtime Error

これは Swift4 対応とは特に関係ないと思われる。

アプリは問題なく動いているのだが、 UIApplication あたりでなんか出てた。

runtime: UI API called from background thread: -[UIApplication delegate] must be used from main thread only
runtime: UI API called from background thread: -[UIApplication applicationState] must be used from main thread only
runtime: UI API called from background thread: -[UIApplication keyWindow] must be used from main thread only
runtime: UI API called from background thread: -[UIApplication supportedInterfaceOrientationsForWindow:] must be used from main thread only

画像で見るとこんな感じ。

f:id:star__hoshi:20170917232741p:plain

ググったら Firebase が古い時のエラーのようで、 Firebase を新しくすれば消える。

  pod 'Firebase' , '~> 4.0'
  pod 'Firebase/AdMob' , '~> 4.0'
  pod 'Firebase/RemoteConfig' , '~> 4.0'

これで pod update すれば OK。

8. 新しい Google Analytics, Firebase 修正。

Firebase を新しくしたら依存する Google Analytics の書き方も変わったみたいなので、 Bridging-Header.h を直した。

+ #import <Google/Analytics.h>
- #import <GoogleAnalytics/GAI.h>
- #import <GoogleAnalytics/GAIFields.h>
- #import <GoogleAnalytics/GAILogger.h>
- #import <GoogleAnalytics/GAITracker.h>
- #import <GoogleAnalytics/GAIDictionaryBuilder.h>

あと Firebaes の FIR のプレフィックスを消す必要があった。

9. おわり

これで Simulator で Run したらエラーも消えてめでたし ㊗️

UITabBar の背景をすりガラスのまま Dark にする

普通の UITabBar は明るいすりガラスだけど、これを UIBlurEffect(style: .dark) と同じように暗くしたい。

Light Dark
f:id:star__hoshi:20170829220502p:plain f:id:star__hoshi:20170829220551p:plain
override func viewDidLoad() {
    super.viewDidLoad()

    tabBar.backgroundImage = UIImage()
    let dummy = UIToolbar(frame: tabBar.bounds)
    dummy.barStyle = .black
    tabBar.insertSubview(dummy, at: 0)
}

UITabBarController の viewDidLoad にこんな感じでかくと、これで Dark な UITabBar を作れる。透明な画像を UITabBar の背景に設定して、その下に Dark な UIToolbar を追加するハックっぽいやり方。

参考: ios - UITabBar with blur - Storyboard - Stack Overflow

また転職することにした

10ヶ月ほど前に意気揚々と 退職エントリ を書いたくせに、1年経たず退職となってしまった。

最初に断っておくと、前職よりは100倍マシだった。
けど悩みや欲望というのは無限に湧いてくるわけで、色々考えて転職することにした。

結局なんでやめんの

何か大きな出来事があったとかではなく、色々あった。

開発を外注して納期厳守だから突貫工事でリリースまではやるけど後は内部の人がメンテしてねという開発スタイルで渋い気持ちになったり、渋い割に給料が良くねえなと思ったり、 iOS エンジニアが 1 人しかいないので雑務が多かったり外注管理おじさんになったり、一人でプルリクみて一人でマージして寂しい気持ちになったり、ジョイントベンチャーだと自分たちじゃどうしようもないことあって辛いな〜と思ったりした。

「アプリなんてなければいいのに」という発言もあってだいぶショックを受けた。これは Apple の制限でできないことが多いことに対する文句なのだが、会社の体質をよく表していると思う。
アプリは「A は快適に使えるけど B は出来ない、 B をしたいなら Web を使ってくれ」というように、何かの機能に特化するのがよくある考え方だと思うけど『お客さんに出来ないと言えない』ので、アプリにも全ての機能を乗せようとする。しかし工数は限られるので WebView でそのページを開くだけ、みたいなことになる。そしてアプリ上の WebView がうまく動かないと問い合わせが来る。もうアプリない方が良くね?ユーザも混乱してるよ?全部 Web でやれば?ってなるじゃないですか。

やっていくぞ!!!!!!!!みたいに勢いある人がいてもつらくて、今まで色々開発を経験してそのノウハウを還元したい人とか、言われたことを素直にやる人にとってはいい会社なのかなと感じた。

ちょっと悲しいのが、自分が辞めるときに退職理由とか聞かれなかったこと。以前辞めた人も同じく聞かれなかったらしい。
新しい人を採用するぞ!と採用活動をするのはいいけど、今いる人を出さないことが一番大事なんじゃないのかな、と思う。 最近 1 on 1 が始まったらしいので、それで内部環境が改善されていくことを願っている。

別に悪い話ばかりじゃなくて、 iOS エンジニア 1 人しかいないから開発から CI 環境整備までやったので圧倒的成長ができた。辛いこともあったけど色々なことを経験できたし、入社して良かったと思ってる。

前職で不満だった上長のご機嫌取りとかする必要もないし、クラウドサービスも自由に使えたし、開発する時間も増えた。

一緒に働く人はいい人が多くてワイワイやれて楽しかったし、自分の前に入った2人の先輩には特にお世話になりました、感謝しています。

教育という事業は素晴らしいと心から思うし、子供たち、学校の先生のために頑張っていってほしい。

次は

今まではひたすらサービス開発をしていたので、そうじゃなくて開発者がサービス開発に集中できる環境づくりの手助けをしたいなと思って iOS の技術基盤をやることになっている。といっても別に iOS じゃなくても良くて、 Android の人が足りないならやりたいしサーバ側が足りないならそっちをやってもいい。

技術基盤というとメモリやOSなども詳しくないとダメなのでは?という気がして戦々恐々としている。 Objective-C も全然出来ないので不安である。サービス開発をメインやってきた人という目線で技術基盤として貢献できればと思っている。

前回の事業はドッグフーディング出来なかったけど次のはできるので使って改善していきたい。

ほしい物リスト

www.amazon.co.jp

fastlane pem で Push 通知に必要な p12 ファイルを生成する

iOS 用の Push 通知 証明書を生成したい。 真心を込めて手動で生成することもできるが、ここでは fastlane pem でコマンドで生成する。

fastlane pem

fastlane/pem at master · fastlane/fastlane

詳細は上の README みればやり方書いてる。

gem install fastlane

$ gem install pem

pem

  1. コマンドラインpem と打つ
  2. Apple ID 聞かれるので入力する
  3. Apple ID パスワード聞かれるので入れる
    • fastlane を信用する、不安なら OSS なのでコードを読むべし
  4. bundle id を聞かれるので該当のアプリの bundle id を入れる
  5. 証明書の期限が 30 日以内の場合、p12 ファイルが吐かれる
    • –force オプション使うと 30 以上でもファイルが吐かれるっぽい

下記のようなログと流れになる。

$ pem
+------------------+-------+
|  Summary for PEM 1.4.0   |
+------------------+-------+
| development      | false |
| generate_p12     | true  |
| force            | false |
| save_private_key | true  |
| output_path      | .     |
+------------------+-------+

[14:56:45]: To not be asked about this value, you can specify it using 'username'
Your Apple ID Username: Your Apple ID Username
[14:56:53]: Starting login with user 'Your Apple ID Username'
-------------------------------------------------------------------------------------
The login information you enter will be stored in your Mac OS Keychain
You can also pass the password using the `FASTLANE_PASSWORD` environment variable
More information about it on GitHub: https://github.com/fastlane/fastlane/tree/master/credentials_manager
-------------------------------------------------------------------------------------
Password (for Your Apple ID Username): *************
[14:57:01]: Successfully logged in
[14:57:02]: To not be asked about this value, you can specify it using 'app_identifier'
The bundle identifier of your app: jp.starhoshi.hoge
[14:57:18]: Existing push notification profile 'jp.starhoshi.hoge' is valid for 135 more days.
[14:57:18]: You already have a push certificate, which is active for more than 30 more days. No need to create a new one
[14:57:18]: If you still want to create a new one, use the --force option when running PEM.

あとは p12 ファイルを Amazon SNS とかにあげれば OK !

xcconfig を使い本体アプリと Embedded Framework を同じ環境設定でビルドする

本体アプリを Build した時に、本体アプリの CONFIGURATION を Embedded Framework 側に渡したい。

  • 本体アプリを Debug Build
    • Embedded Framework も Debug Build
  • 本体アプリを Release Build
    • Embedded Framework も Release Build

というように、本体アプリのビルド環境によって自動で Embedded Framework のビルド環境も変えたい。

xcconfig を使う

  1. Embedded Framework 側で xcconfig を作成する
    • New File から Configuration Settings File を選択
    • Targets は Embedded Framework を選ぶ
  2. hoge.xcconfig に下記を追記
    • OTHER_SWIFT_FLAGS = "-D" $(CONFIGURATION)

これでビルドすると、本体でビルドした環境を Embedded Framework 側に渡せる。

参考

The Unofficial Guide to xcconfig files

ここにサンプルやユースケースなど色々書いてあって便利。 今回は CONFIGURATION を渡したが、他にも色々な設定ができる。

TodayViewController の viewDidLoad が呼ばれるタイミングについて

iOS の TodayExtension を実装していて、 TodayViewController の viewDidLoad が走るタイミングが最初はわからず苦労した。

viewDidLoad が呼ばれるタイミング

画面に表示されたタイミングで ほぼ毎回 viewDidLoad が呼ばれる。
TodayExtension を 10 こくらい並べて、スクロールして画面外から表示させても viewDidLoad が呼ばれる。

開発し始めた最初は

  1. 端末起動後/Widget 追加後に viewDidLoad が 1 度だけ呼ばれる
  2. 画面表示時に widgetPerformUpdate(completionHandler:) が呼ばる
    • 更新の必要があれば completionHandler(.newData) を実行する

だと思って開発を始めたため、何度も viewDidLoad が呼ばれて戸惑った。

TodayExtension の高さを変えるような時に問題が生じる

TodayExtension Widget 表示時、こう動く。(TableView で、 List の要素 1 つにつき 50px 使うようなコードだとする。)

  1. 一瞬だけ前回取得したデータが表示される
    • 要素が 4 つあったので、 height が 200px くらいある
  2. Extension の表示が初期表示になる
    • 初期は要素数が 0 なので、 height がデフォルト(110px)になる
  3. 取得されたデータが表示される
    • しかし、 Show More になっているのにデータが2つしか表示されない…

Show More の状態は変わらないが画面はリセットされてしまい、表示がおかしい…。
こんな感じ。

f:id:star__hoshi:20170720140917g:plain

表示を改善するためには?

初期表示時に前回取得したデータを表示すれば良い。

  1. 一瞬だけ前回取得したデータが表示される
    • 前回の height が 200px だったので、それがそのまま表示される
  2. Extension を初期化する
    • 初期化時にローカルに保存しておいたデータを表示する
      • 画面に変化がないので height 200px を保つ
  3. 取得された最新のデータを表示する
    • 素数が 4 つだったので、 height が 200px になる
    • 取得したデータをローカルに保存しておく

つまり、前回取得したデータを保存しておいて、 viewDidLoad 時にそのデータを描画するとユーザとしては画面がチラチラせずに違和感なく使える。

そうすると初期化時に高さが前回と同じに設定されるので、突然小さくなったりしないですむ。

↓のは画面に変化がないように見えるが、実際には画面が初期化されたりしている。

f:id:star__hoshi:20170720140853g:plain

ローカルにデータを保存

保存したいデータが String だけとかなら楽でいいけど、実際は JSON Mapping されたクラスだったりするので、そのクラスに NSCoding を継承して data 型として保存すると楽。

UserDefault などに class を保存するサンプル

ちなみに TodayExtension で UserDefaults を使うには AppGroups の設定が必要。

おわり

最初は viewDidLoad が呼ばれまくることに違和感があるが、 UserDefaults などを使って適切にデータを管理すればそんなに難しくなかった。

しかし Show More / Show Less の挙動とかうまくいかないところとかあってなかなか難しい…。