コネヒト開発者ブログ

コネヒト開発者ブログ

iOS14からはaddTargetじゃなくてaddAction

こんにちは、コネヒトでiOSエンジニアをやっていますyanamuraです。

これは iOS Advent Calendar 2020 の 4日目の記事です。

TL;DR

UIKitのUIControl系のView(UIButtonなど)ではタップ時のアクションをコードで実装するときは、標準のAPIだとaddTargetを用いる必要がありました。addTargetだとclosureが使えずいちいち関数を定義しなければならなかったり、@objcをつける必要があったりと面倒でした。

// addTargetでやるパターン
@IBOutlet weak var button: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()

    button.addTarget(self, action: #selector(doSomething), for: .touchUpInside)
}

@objc func doSomething() {
    print("do something")
}

iOS14でついにUIControlにaddActionという新しいAPIが追加されこの面倒くささが解決しました!(SwiftUIがでているこの状況では今更感がありますが・・)

このようにシュッとかけるようになっています。

button.addAction(.init { _ in print("do something") }, for: .touchUpInside)

(ちなみにコネヒトでは現在RxSwift(RxCocoa)を使っているので正直addActionの恩恵にあずかることはなさそうです

詳細

UIControlの新しいAPIのaddActionではactionをselectorではなくUIActionを受け取るようになっています。

// UIControl
func addAction(_ action: UIAction, for controlEvents: UIControl.Event)

UIActionはinitializerで多くの引数を指定できますが、必須なのはhandlerだけです。

// UIAction
convenience init(
    title: String = "",
    image: UIImage? = nil,
    identifier: UIAction.Identifier? = nil,
    discoverabilityTitle: String? = nil,
    attributes: UIMenuElement.Attributes = [],
    state: UIMenuElement.State = .off,
    handler: @escaping UIActionHandler)

UIActionHandlerは単なるaliasです。

// UIActionHandler
typealias UIActionHandler = (UIAction) -> Void

UIControlにはaddAction以外にもinitializerでactionを設定することもできるようになっています。https://developer.apple.com/documentation/uikit/uicontrol/3600494-init

// UIControl
convenience init(frame: CGRect, primaryAction: UIAction?)

使い方

わかりやすく書くとこうなります。

let action = UIAction(handler: { _ in print("do something")})
button.addAction(action, for: .touchUpInside)

省略して書くとこのようになります。

button.addAction(.init { _ in print("do something") }, for: .touchUpInside)

addActionが使えるView

addActionはUIControlのAPIなのでUIControlとUIControlを継承しているクラスでは使えます。

ですので、UIButtonをはじめ、UIDatePicker, UIPageControl, UISegmentedControl, UISwitch, UIStepper, UISlider, そして新たに追加されたUIColorWellでaddActionが使えるはずです。

参考

WWDC2020: Build with iOS pickers, menus and actions https://developer.apple.com/videos/play/wwdc2020/10052/

Lambdaのコンテナサポートに関する考察

こんにちは。インフラエンジニアの永井(shnagai)です。

AWS re:Invent今年も大豊作ですごいですね。まだ全部は追えてないんですが、良さそうなものがあればサービスに取り入れていこうと思いわくわくしています。

この記事はコネヒト Advent Calendar 2020 - Qiita 3日目の記事です。

今回は、試してみてる方は結構いそうなので、ざっとLambdaのコンテナサポートを触ってみた感じの所感を中心に書いていきます。

うれしいポイント

  • 今想像してる一番うれしいポイントは、lambdaがサポートしてる数多のAWSインテグレーションをトリガに好きな処理が動かせるところ(lambdaRuntimeAPIの存在を知りそう甘くないことを理解した)
  • ローカルの開発がやりやすくなるなー
    • SAMとか使って出来るけど。個人的にはlambdaの管理は煩雑
    • dockerで検証出来た方が楽
  • lambdaの設定とアプリケーションコード(コンテナ)を分離して管理出来るようになるので、コード化しやすいなとか

vs 既存のLambda

  • Lambdaの一番かゆいところは、チーム開発しようとするとデプロイフローがやや面倒という点があると思っている(AWS SAMとかをうまく使っていけば出来なくはないが、チーム全員で回すにはやや複雑と感じている)

pros

  • 今回、アプリケーションコードをDockerコンテナ内に閉じ込めることで、既存の開発フローに乗せてLambdaを開発出来るようになる未来が見えた
  • ハンドラがDockerのCMD句に置き換わるので、共通のイメージで開発して、Function毎にCMD句だけ上書きすることで管理コストが大幅に下げれそうな気がしている

cons

  • しいて言うなら、Lambdaのメリットにマネコン上からコード閲覧出来たりサクッと変えてお試しみたいなのが出来るのがあると思うが、それは出来ない(手軽さは少し落ちるかなというところ)

vs Fargate

  • サーバレスでコンテナを動かせると考えると、Fargateの代替になりえるなと考えるのは当然の流れだと思う

pros:

  • AWSのインテグレーションを簡単に使えるというLambdaの強みを享受
    • 例えば、下のサンプルでやったSQSトリガのワーカーの仕組みなんかは、Fargateでやる場合は、ポーリングの処理を書かないといけないがそれが不要になるのはうれしい
  • Fargateでコンテナ動かすには、ECSなりEKSのオーケストレーターも必要なわけなので、スポットで単発の処理動かしたいみたいな時はLambdaはより簡単で良い

cons:

  • LambdaRuntimeAPIをコンテナ側で実装しなければいけないので、どんなイメージでも動くわけではない。
    • AWSである程度の言語については、すでに用意されてはいる
    • インターフェースを統一しなきゃいけないのはそれはそうだな
    • 既存のアプリケーションの一部をLambda+コンテナで動かすのはちょっとハードルあるなというのが正直な印象ではある

ちょっとイメージと違ったところ

これは触ってみて感じたことなので、実は違う可能性もある。

  • コンテナイメージは都度pullではなく、イメージ設定時に固定されそう
    • 同一タグでECR更新して、起動時に最新のイメージ取得みたいな構成は出来なさそう(何回か試したけど、lambdaを更新した時にイメージがずっと使われてそうだった)
    • 「新しいイメージをデプロイ」する時にpullしてるように見える
    • 起動速度維持するために、キャッシュしてるのかなと想像
    • コンテナビルドとLambdaの更新をセットでやらないとダメそう

で試してみる

今回は、SQSをイベントソースとして、SQSキューが入ったら中身を出力するという簡単な内容(疑似的なワーカー)

こちらのチュートリアルをベースに実行

docs.aws.amazon.com

コードはこちら

github.com

コンテナの準備

$ docker build -t [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/tst-shnagai:test .

## 手元で動作確認
$ docker run --rm [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/tst-shnagai:test

hogehoge

## ECRへのpush(ログイン込)
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com && docker push [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/tst-shnagai:test


Lambdaの設定

作成(コンテナイメージを選ぶ)

f:id:nagais:20201203100953p:plain

  • 手元にあった適当なpython動くコンテナで最初試したけど、それをただ実行させるとかは出来ないみたい(lambdaRuntimeAPIが必要で、呼び出しI/FもLambdaにそる)

f:id:nagais:20201203100356p:plain

  • IAM Roleとして AWSLambdaSQSQueueExecutionRole を追加するのを忘れずに。

SQSの準備

aws-cliでサッと作る

$ aws sqs create-queue --queue-name lambda-test
{
    "QueueUrl": "https://ap-northeast-1.queue.amazonaws.com/[アカウントID]/lambda-test"
}

先程作ったLambdaのイベントソースに上記のSQSを登録

テスト

$ aws sqs send-message --queue-url https://ap-northeast-1.queue.amazonaws.com/[アカウントID]/lambda-test --message-body "lambda-containerのテスト"

無事に動いた。

f:id:nagais:20201203100305p:plain

クラメソさんの記事が概要理解するのにめちゃ役立ちました!!

dev.classmethod.jp

TravisCIの料金体系の変更によるmacOSでのビルドへの影響(有料ユーザー向け)

2020/11/02からTravis CIの料金体系が変わりました。 blog.travis-ci.com

影響があるのは以下の4つ場合です。

  • Building on the macOS environment
  • Building with more than 10 concurrent builds
  • Building on a Trial Plan
  • Building on a public repositories only

コネヒトでは月額$249(5 concurrent job)のplanでweb, iOS, Androidで使っています。

このエントリーでは、コネヒトで影響のあった Building on the macOS environment についてのみふれていきます。

(注:TravisCIを無料でpublic repositoryでのみmacOSでビルドするのに使われている方には参考にならないです🙇🏻‍♂️)

macOS環境でのビルドへの影響

Travis CIではこれまではOSに関係なく同じプラン内で使えていましたが、今後はmacOSの場合はビルド時間に応じて追加料金が必要となります。 料金は以下の通りです。

  • $15で25000 creditを購入
  • macOSだと1分で50 credit

CircleCIとそろえているようでした。

いつから?

現在のプランの有効期限が切れると次回から新しいプランに移行するようでした。

Beginning on Monday the 2nd of November 2020, customers impacted by pricing changes listed above will be gradually upgraded to the new pricing scheme upon their plan expiration date.

他のCIとのコストの比較

追加料金必要になるなら他のCIに乗り換えることも検討するかと思いますのでコストを比較してみました。

月にどれくらい使うか、チームの規模、iOS以外の開発での利用などによって最適なものは異なってくるのでどれが一番良いとは一概に言えません。

ビルド時間 Travis CI Circle CI bitrise GitHub Actions
2000min $60 + $69/$129/$249 ※1 $60+$15 ※2 $100/$300 ※3 $136
2500min $75 + $69/$129/$249 ※1 $75+$15 ※2 $100/$300 ※3 $176
3000min $90 + $69/$129/$249 ※1 $90+$15 ※2 $100/$300 ※3 $216
3500min $105 + $69/$129/$249 ※1 $105+$15 ※2 $100/$300 ※3 $256

※1 Travis CIはconcurrent planによってベースの料金が異なります

※2 Circle CIはユーザー数によって料金が異なります

※3 bitriseはプランによって料金が異なります

2020/11/11時点の情報です。

コネヒトでの今後の方針

コネヒトのiOS開発でのCIの利用状況は月に3000分くらいで、コスト的には他のCIと遜色なさそうでした。

ただTravis CIを使っている上で ログが最大4MBまでしか保存されない というところが結構不便に感じていたので、同じくらいのコストで定額で使えるbitriseに乗り換えようかと検討しています。

potatotips #71 で Mixpanel について LT してきたのでその補足や失敗談の共有

こんにちは!コネヒトでアプリケーションエンジニアをしているaboです。

先日開催された potatotips #71 iOS/Android 開発 Tips 共有会で、コネヒトでも活用しているプロダクト分析ツールの Mixpanel について話してきました。コネヒトでは、エンジニアもプロダクトをより良くする開発チームの一員として仮説検証や施策を考えるために Mixpanel や Redash などの分析ツールを触ります。Mixpanel は GUI で比較的かんたんに分析できたり、レポートのビジュアライズが強力で、コネヒトでも分析ツールのひとつとして役立っています。

potatotips.connpass.com

今回は、発表内容の補足や失敗談の共有をします。当日の発表資料はこちらです。

GUI でかんたんに分析できる

発表で出さなかった例として、「Compare to previous year」を選択すると、前年の値と比べられたり(点線が前年、実線が今年)

f:id:aboy_perry:20201028160221p:plain
前年との比較

Annotation という、レポートの日付に注釈をつけられる機能があったりします。下の画像の例だと、機械学習のモデルを更新したんだな、ということがレポート上でわかりやすくなります。

f:id:aboy_perry:20201028184839p:plain
Annotation をつけてやった施策をわかりやすくする

ユーザーの Activity Feed をエラー調査に活用する

ユーザーの行動を時系列で確認できる Activity Feed も便利です。実際にアプリケーションのエラーを調査するときに、Activity Feed で該当のエラーが発生した時刻の前後の行動をみたりします。そして「通常のユースケースで起きてそう」「いや何か変だぞ」みたいなコミュニケーションをしたりしています。

f:id:aboy_perry:20201028181426p:plain
ユーザーの行動を時系列で確認できる Activity Feed

Cohorts も便利

発表では触れられませんでしたが、Cohorts というユーザーをグループ化できる機能があります。つくった Cohorts は分析の条件に入れたり、ユーザーリストを CSV としてエクスポートできたりして便利です。例えば「過去7日間に10回以上起動した Android ユーザー」などです。

f:id:aboy_perry:20201028170603p:plain
Cohorts の作成画面

エンタープライズプランであれば Cohorts はファネル分析やリテンション分析のレポートからも作成できます。ファネルであれば「施策で追加したバナーから購読画面を開いたが購読せず離脱したユーザー」や「ディープリンクから投稿画面を開いてそのまま投稿してくれたユーザー」など。リテンションであれば「投稿した次の日に起動しなかったユーザー」のようなユーザーのリストを、レポートからポチポチするだけで作成できます。

f:id:aboy_perry:20201028164022p:plain
ファネル分析のレポート上で選択するだけで Cohorts がつくれる

コネヒトでは使っていませんが、Cohorts に対してメッセージを送ることもできます。

help.mixpanel.com

MTU が想定以上に増えた

最後に失敗談です。Mixpanel は MTU (Monthly Tracked User) という方法で費用が計算されます。 MTU は対象の月にイベントを実行したユーザー数のことで、Mixpanel では distinct_id という識別子でユーザーが一意に決定されます。

コネヒトではアプリ (Native と WebView) からのみ Mixpanel にイベントを送信しており、distinct_id にはママリのユーザー ID が設定されるようになっていました。なので MTU はアプリ版ママリの月間ユニークユーザー数と近しい値になっていました。

しかし新たに Mixpanel へのトラッキングを追加した際、ママリにログインしていなくても見ることができるブラウザも対象にしました。すると distinct_id には Mixpanel で生成されたユニークな ID が設定されたことによってそれぞれが別のユーザーとしてカウントされてしまい MTU が想定以上に増えてしまいました。

f:id:aboy_perry:20201027231719p:plain
MTU が爆増した2週間

このときは、(1)コスト増のインパクトが大きいこと(2)トラッキングの目的であったデータ分析が終わっていたため消しても問題ない、という理由から該当のコミットをリバートする対応をしました。

公式ドキュメントには、集計対象外のイベントや MTU の計算方法の例外、MTU に大きな影響を与えているイベントの調べ方なども書かれてあります。

help.mixpanel.com

おわりに

LT で話せなかったことや、失敗談を共有しました。プロダクト分析ツール選びの際に参考のひとつになれば幸いです!


コネヒトではプロダクトを一緒に成長させられるエンジニアを絶賛募集中ですので、まずはお話だけでも聞きにきてください!

hrmos.co

コネヒトマルシェオンライン「事業を支えるWeb開発」vol.2 を開催しました!

こんにちは! @otukutun です。

先日 2020/09/25(金)に コネヒトマルシェオンライン「事業を支えるWeb開発」vol.2 をランサーズさんと共同開催で開催しました!コネヒトマルシェはみんなの「知りたい」「知ってる」をおすそ分け!をコンセプトにした参加者が主役の勉強会です。今回はオンライン開催になってから第二回目になります。前回はBASEさんとこんな内容でやっておりますのでそちらもよかったらご覧ください。

発表内容

今回はオンラインになってから第二回の開催となりますが、テーマは第一回に引き続き「事業を支えるWeb開発」というテーマでお送りしました。今回の発表内容は以下のようになっています。ではさっそく、これからLTの内容など簡単にご紹介できればと思います。

  • @奥津(コネヒト株式会社所属) Fabricate導入した話
  • @金澤さん(ランサーズ株式会社所属)ランサーズのCakePHP4移行について
  • @富田さん(コネヒト株式会社所属)コネヒトの健全性を支える GitHub Actions の事例紹介
  • @安達さん(ランサーズ株式会社所属)ECS/Fargateの活用事例 ( CakePHP編)
  • @山田さん(コネヒト株式会社所属)ページネーションから考えるSQLパフォーマンス

Fabricate導入した話 / @奥津(コネヒト株式会社所属)

こちら私が発表した内容になります。CakePHPにおけるテストデータ生成からFabricateというテストデータ生成ツールを導入した内容の発表でした。年数が経っているプロジェクトで既存のFixtureとFabricateを共存させて使っていく話をしました。

ランサーズのCakePHP4移行について / @金澤さん(ランサーズ株式会社所属)

既存のプロジェクトを分割しながらCakePHP4に移行していく発表でした。CakePHP4移行に当たってのレポジトリ戦略から始まり、アップデートしてくなかで多数のOSSにコントリビュータされていていて、とても刺激を受けた発表でした。弊社でメンテナンスしているcakephp-master-replicaにもコントリビュートしていただいてきました。

コネヒトの健全性を支える GitHub Actions の事例紹介 / @富田さん(コネヒト株式会社所属)

開発における健全性の定義から始まり、それをGitHub Actionsを使って観測・改善していこうという発表でした。ツールの導入して日が浅いようなので今後の経過も気になりますね!

ECS/Fargateの活用事例 ( CakePHP編) / @安達さん(ランサーズ株式会社所属)

こちらはCakePHPの開発環境を作るお話からはじまり、stg・本番環境をECS Fargateに移行する話を中心に発表していただきました。ログまわりの集約方法の話や作業用コンテナを設置している話とても共感しながら聞いておりました。Fargateはデバッグしづらいというのはその通りでうなづいちゃいました。

ページネーションから考えるSQLパフォーマンス / @山田さん(コネヒト株式会社所属)

先日業務で実装されたページネーションから、SQLパフォーマンスを考えてどんなindexを貼るべきかという発表内容でした。個人的にindexまわりの話は好物なので楽しく聞くことができました。MySQLのサンプルデータは気軽に試してみるのによさそうだな〜と思いました。

最後に

ランサーズさん、コネヒト社共にメインで使っているCakePHPの発表だけでなく、GitHub ActionsやSQLなどいろんなテーマの発表がありとても興味深い勉強会になったと思っています。

またまた第三回も開催したいと思いますので参加される方ははたまた共同開催していただける会社さんも募集しておりますのでぜひよろしくお願いします!

ということで、また会いましょー!

コピペでできるGitHub Actionsでreleaseブランチのマージ、リリースノートの作成の自動化【git-flow用】

こんにちは、コネヒトでiOSエンジニアをやっていますyanamuraです。

iOSやAndroidアプリの開発だとgit-flowを使って開発することも割と多いのではないかと思います。 git-flowの場合releaseブランチをマージする場合はdevelopとmain/masterにマージする必要があるのでちょっと手間がかかります。 また、main/masterにマージ後にタグをつけてpushし、さらにリリースノートを書いたりすると結構面倒です。

これをGitHub Actionsで自動化しました。

想定しているフロー

以下の手順は手作業で行います

  1. release-x.x.x という命名ルールでreleaseブランチをつくる
  2. 1のreleaseブランチでPull Requestを作成する。
  3. 2のPull Requestのbodyにリリースノートに書く内容を記載する
  4. releaseブランチをマージしたいときは、Pull Requestにshipit ラベルをつける

こうすると自動でマージが行われ、vx.x.x というタグがつけられ、Pull Requestのbodyに書いた内容がリリースノートに反映されます。

GitHub Actionsの作成

.github/workflows というフォルダを作成し、その中にymlファイルを作成し、以下の内容をコピペするだけです。

name: automerge
on:
  pull_request:
    types: [labeled]
jobs:
  automerge:
    if: github.event.label.name == 'shipit'
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Extract branch name
      uses: mdecoleman/pr-branch-name@1.0.0
      id: extract_branch
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
    - name: Extract tag name
      shell: bash
      run: |
        branch=${{ steps.extract_branch.outputs.branch }}
        echo "##[set-output name=tag;]$(echo ${branch#release-})"
      id: extract_tag
    - name: Merge
      if: startsWith(steps.extract_branch.outputs.branch, 'release')
      uses: yanamura/git-flow-merge-action@v1
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        branch: ${{ steps.extract_branch.outputs.branch }}
        tag: v${{ steps.extract_tag.outputs.tag }}
    - name: Get body from PullRequest
      id: pr-body
      uses: actions/github-script@v2
      with:
        github-token: ${{secrets.GITHUB_TOKEN}}
        result-encoding: string
        script: |
          const result = await github.pulls.get({
            owner: context.repo.owner,
            repo: context.repo.repo,
            pull_number: context.issue.number
          })
          return result.data.body
    - name: Create ReleaseNote
      if: startsWith(steps.extract_branch.outputs.branch, 'release')
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: v${{ steps.extract_tag.outputs.tag }}
        release_name: Release ${{ steps.extract_tag.outputs.tag }}
        body: |
          ${{ steps.pr-body.outputs.result }}
        draft: false
        prerelease: false

マージする際のトリガーとなるラベル名やリリースブランチの命名、タグの命名など変えたい場合はカスタマイズしてみてください。(masterではなくmainブランチの場合は、こちらを見てmainを指定してください)

かんたんにできると思いますので、ぜひお試しください!

今回ご紹介したところ以外にもGitHub Actionsを活用して自動化を行っているのでまた別の機会にご紹介できたらなと思います。

最後に

コネヒトではエンジニアを募集しています。少しでもご興味のあるかたはぜひ一度話を聞きに来てください! hrmos.co

hrmos.co

iOSDC Japan 2020 に協賛、参加しました!

こんばんは!コネヒトでアプリケーションエンジニアをしているaboです。

先日開催された iOSDC Japan 2020 は、前夜祭含め3日間におよび、大盛況のうちに幕を閉じました。今回はオンライン開催ということもあり、例年とは違った準備も必要で大変だったと思いますが、今年も素敵なコミュニティだったと思います。ニコ生での開催だったので、リアルタイムに視聴者のコメントが流れるのが楽しかったですし、事前録画の発表に対して発表者による補足コメントが流れるのも新鮮でした。また、Discord をつかった Ask the Speaker や雑談部屋などのチャレンジも印象的でした。

iOSDC Japan 2020 に関わった皆様に感謝致します! 🤝

f:id:aboy_perry:20200921183938j:plain

コネヒトは iOSDC Japan 2020 に協賛させていただきました

今年もシルバースポンサー、Tシャツスポンサーの2つで協賛させていただきました!

iOSDC は昨年も協賛させていただきましたが、根底にあるのはコネヒトが掲げる「人の生活になくてはならないものをつくる」というミッションです。エンジニア文脈においても、コネヒトが技術コミュニティになくてはならないような会社になるべく、様々なアウトプットを支援・促進するような制度*1も存在します。自分たちが普段お世話になっている技術コミュニティに、こうした形でサポートさせていただくことで、一緒に盛り上げていくことができたらと思っています。

f:id:aboy_perry:20200921173643j:plainf:id:aboy_perry:20200921173716j:plain

機械学習まわりの発表もいくつかありました

ぼくがちょうど機械学習をサービスに組み込んでいく動きをするチームに所属している*2のもあり、機械学習に関連する発表にも興味がありました。

とくに『機械学習のブルーオーシャン、CoreML』という発表に関しては、個人的にも Core ML を少し調べていたのもあり、実案件レベルをこなしている方の目線から見る Core ML を知る貴重な機会でした。

発表を聞いてみると、実務レベルでは Core ML モデルをつくる部分も含めて全体を触る必要があり、そのなかで機械学習やグラフ可視化ツール、Core ML Tools、Swift、Python などの知識が求められるため、ブルーオーシャンといえど幅広い知識が必要になりそうだなーと感じました。このあたりは全て独学だと骨が折れそうな感じですが、コネヒトには機械学習エンジニアがいるので、チームで取り組めればラクそう...!

Core ML はコネヒトでも可能性がなくはない技術なので、今回の発表が聞けたのはとても有意義でした!

最後まで大盛り上がりで終了!

2日目の LT タイムには視聴数が2000人を突破し、その後のクロージングでは2500人ほどになっていました!LT は事前録画ではなくリアルタイムで行われたので、オフラインで開催された昨年までと同様ライブ感のある盛り上がりでした。

f:id:aboy_perry:20200921182021p:plain
クロージングの様子

というわけで簡単ではありますが参加ブログとさせていただきます!今年も最高のコミュニティでした!あらためて、iOSDC Japan 2020 に関わった皆様ありがとうございました!