4年間運用する中で表示速度が落ちた画面を改善する中で得た知見
古い端末のほうが標示が顕著に遅くなってしまう
iPhone 8などを使用しても一秒以上かかってしまう
init
からviewDidAppear
までの時間を測定した
画面の構成
上から,
- VideoPlayerViewController
- DetailViewController
- VideoFullScreenViewController
となっている
VideoFullScreenViewControllerがまず初期化され,その中でVideoPlayerViewContollerを初期化するなどの処理を行う
はじめはDetailだけを表示するだけだったが,プレイヤとかも追加された
DetailViewは
- サムネイル
- Expandする詳細
- アクションのアイコン
- 縦方向のTableView
- 一番下にレコメンド
という感じで,StackViewで作られている
これ以外に横画面に対応するためのStackViewとかもいる
サムネイルはすぐに表示されるが,動画が初期化されたらプレイヤを表示する
表示を遅くした原因
Nibは一回生成されるとキャッシュされる
→殆どの場合で二回目以降は少し早い
よくあるNibの初期化が遅くなる原因
- フォントの参照が切れる
- System Fontを使用していたため,ない
改善案
- Xibをやめてコードで作る
- Viewの初期化を遅延させる
- こっちを採用
レコメンド
- はじめはHide
- 取得が終わったらレイアウト処理
Expand
- はじめはHide
- タップするとレイアウト処理
改善方法
一個一個Viewが初期化された後別のViewを初期化するみたいな遅延初期化をするようにした
ViewModelとObservableをつなぐクラスを作った
ViewModelが初期化完了したかどうかを知るObservableを作った
Viewの初期化が終了したときのObservableを作り,それが終わったらレコメンドのレイアウトを開始する
…ということをして高速化した
結果,半分くらいの表示速度になった
- StakViewのdidSetでarrangedSubViewしていたViewをviewDidAppear後に遅延初期化してそのタイミングでarrangedSubviewするように修正
- viewDidLoadでbindしていたObserver・ObservableをLazyRelaysのPublishRelayを経由するようにして遅延初期化に対応
- レコメンドの取得をViewのち円初期化後に変更(レコメンドの描画その後になる
画面の回転について
画面の回転時も時間がかかる
collectionViewの高さのConstraintに合わせてView自体の高さを決めるため,Cellは再利用されずに,すべて再描画され
縦横回転をするとStackViewのスタック方向が変わる→遅延初期化後に方向をチェックする
改善
StackViewだったところをCollectionViewに変更
CollectionViewで表示するときにLazyViewを再利用できるようにした →差分更新を使って描画する
RxDataSourceを使ってサムネイルとかレコメンドセルとかをいい感じに流すようにした(?)
副次的な効果として,A/Bテストが簡単にできた
- UIScrollView + UIStackViewの構成からUICollectionViewに変更
- 各ViewからUIStackView + arrangedSubviewsの実装を別Viewに分離
- UIStackViewのarrangedSubViewのisHiddenによるレイアウト更新を差分更新ライブラリを用いたUICollectionViewのアニメーションに更新
まとめ
- Xibは初回ロードに大幅に時間がかかる
- 画面の表示速度が遅くなった場合にViewの生成を遅延させると効果が出やすい.(bindした直後に処理がは閣下し,レイアウト処理が実行された事によって表示が遅延してしまっていた可能性もある)
- UICollectionView(またはUITableView)でデータソースの受け渡しを遅延させれば,Viewの生成を遅延させることもできる
- データソースを宣言的にすることで,モジュールの入れ替えや追加がかんたんになる
Ask-the-speaker
最終的に表示するタイミングは変わらないが,ちゃんとプログレスバーが表示されて,ユーザがちゃんとロードしていることが理解できるようにしたというお話だった
Nibを使わないという方法があったと思うが,あれはどうなのか?
- 着手できなかったのでわからない
ユーザに対する改善はあったのか
- もともと数値が伸びていなかった→このスピードが問題なんじゃないかということで,改善
- 結果,あまり関係はしていなかったが,事業として進められたのはとても良かった
そろそろ始めるCombine
非同期処理の難しさ - いつ値が帰ってくるかわからない!
ライブラリもたくさんあるが,処理の方法が場合によって異なるため,難しい
→そこでCombineの登場!
Combine
すべての処理をPublisherで行うため,一通りの書き方で様々な場合に対応できる
最終的な値の更新も一連の流れでできる
複数箇所からのデータの同期も,データとViewの更新を一箇所に集約できる!
→多種類の非同期処理などを単一の方法で行うもの!
タイマー,通知,ユーザアクアションなど,繰り返し行うものについても記述可能
「宣言的」とは?
対義語は「指示的」.指示的だと手順を頑張って書く必要がある.
宣言的に書くと,目的のみが簡素に簡素に書かれることになる
主要なクラス
- Publisher
- 特定の型の値を時間とともに連続的に流す
Publisher<Output, Failuer: Error>
- Value: Output
- Completion: Finished or Failuer
- Completionが起きると値は二度と出力されない
- Subscriber
- Publisherからほしい文だけ値を受け取る
Subscriber<Output, Failuer: Error>
- OutputとFailuerの型はPublisherと一致させる必要がある
- 複数のSubscriberを一つのPublisherをwatchすることも可能
- Subscriberが要求しなければPublisherは値を放出しない(BackPressure)
- Subscriberがほしい量も要求可能(
maxPublishers
とかを使う模様)
- Subscription
- PublisherとSubscriberの仲介役
- Publisherが生成,Subscriberにわたす→そうすると,SubscriberはSubscriptionを介してPublisherから値をもらう
cancel
というメソッドが生えており,これを呼ぶとPublisherから値が送られなくなる.- AnyCancellableというものがあり,こいつを使うとインスタンスの開放時に自動的に
cancel
されるので最高.(これがDisposable) - ただし,AnyCancellableは値を保持しないとちゃんとcancelしない(ワーニング出るので良き)
処理の流れ
- 始まりはSubscriberからPublisherに「値が欲しい」というメッセージを飛ばす
- PublisherがSubscriptionを生成し,Subscriberにわたす
- SubscriberがDemandを渡す
- 値を繰り返しやり取りする
- Completionが呼ばれて終了
使えるメソッド
- sink
- assign
その他のクラス
- Subject
- Publisherの一種.手動で値を流したりできる
- CurrentValueSubjectは現在の値をとって置くこともできる
- 通常の変数に
@Published
をつけるとPublisherのように振る舞うようにすることが可能.$
を頭につけるとPublisherのとして関数に渡せる@Published
はClassのメンバにしかなれない
- Classのインスタンスに
@Published
を使用すると内部の値の更新がフックされないので値が流れない
- Operator
- Publisherの更新時に新しいPublisherを生成して流すことが可能
- SwiftのCollectionと使い方が似ていることが多い
- これまでのPublisherを包んで新しい型を作るため,呼び出しまくることで値が複雑になる
eraseToAnyPublisher
することで内部の複雑な型を削除してシンプルにすることはできる
他のフレームワークとの連携
Appleは様々なものをCombineとくっつけて全部Combineの世界にしている
- URLSession
dataTaskPublisher(fron: url)
を使用することでPublisherを生成可能
- NotificationCenter
NotificationCenter.default.publisher()
が生えてる
- SwiftUI
- UIKitを使っていてもViewModelを使うことで値の共有が可能
- ViewModelをObservableObjectに適合させると双方向データバインディングが可能
- UIKitの場合,UIそのものにも状態があるため,状態の二重管理になってしまい,副作用が怖いが,SwiftUIではViewが状態を持たないので二重管理にならない(Single Source of Truth)
既存の実装に取り込む
- UIKit + Combine
- Delegateからの値をCombine出遅れる
- handleEventsでタイミングを調整できる
- Result -> Publisher
- Featureを使うと非同期で帰ってきたResultをPublisherにできる
- 値の出力は一度だけ
- Featureはインスタンスを生成したタイミングですぐに処理されてしまう.
- そこで,Defferedを使用することで遅延評価が可能
- KVOの活用
- イベントに合わせてUIの大きさなどを変えるなどが可能
- Custom Publisher
- 自分でPublisher作れる
- 実装方法はいくつか存在する.Appleのレポジトリなどを眺めると良い
困ったときのデバッグ方法
処理を一つにつなげることができる反面,一箇所の問題がすべての破綻を招く可能性がある
ユニットテストでもいいが,アプリを動かしながら試したいことがある
コンソールで見られる
- print
- すべての出力値を表示
- handleEvent
- 種類を選べる
ブレークポイント系
- breakpointOnError
- エラー時に止まる
- もう一個あったけどメモしきれなかった
アプリ系
- Timelane
- 状態をタイムリーに見られるアプリ
もっとCombine!
- WWDCセッション
- Appleのドキュメント
- YouTubeなど
- 書籍など
- 実際に使ってみる
- Playground
- チュートリアル
- 公式リファレンスを都度検証
- 個人アプリを作る
- 困ったときは
- デバッグツールを使う
- 困っている人がいないか探す
- combinecommunity.slack.com
- スピーカーの方のレポジトリもある
まとめ
使い方わからずに見切り発車してしまうと後々困ったりしてしまう
この機会に学ぼう!
Ask-the-speaker
- テスト時に時間の流れを見ながらテストできる?(RxのTestScheduler的なもの)
- 公式にはない.
- 頑張って自力で実装すればできるが,できれば公式でやってほしいw
@Publisher
はiOS14じゃないと使えないの?- 使える.
assign
の引数の一部がiOS14から導入された
- 使える.
- Rxから乗り換える必要はある?
- 近々ではRxがサポートされ続けるはずなので問題はない
- ただし,App Clipなど,アプリサイズが制限される場合では使わざるを得ないはず
- 現在のところCombineを使うとRxより良いところは?
- CombineのメリットはAppleが出しているところ
- OSのバージョンアップでもすぐにサポートされる
- 新しいアプリではCombineを使うべきなのでは
- ただし,Backpressureが破綻しているらしい・・・?
- Rxからの乗り換えでハマるところはある?
- エラーの型がしっかりしているのがRxと違う上にXcodeが不親切なのでコンパイルエラーでハマるかも
- RxはRealm(モバイルDB)とかと連携できるが,そのへんはどうか
- 今の所自力で変換するコードを書かないと行けない
- Combine Realmというライブラリもある!すごい!
- CoreDataならちゃんと連携されている…はず
- 今の所自力で変換するコードを書かないと行けない
- 学習コストはRxに比べてどう?
- オペレータなどがSwiftと同じなので,そのへんのコストは低くなる
- ただし,はじめの導入はRxと同じくらいコスト高い
- オペレーターを自作するのは難しそう?
- Appleが公式で出しているコードが有る!
- こういうのを見て学習していくと良さそう
Flutter移行の苦労と,乗り越えた先に得られたもの
じゃらんでは,Flutter移行にチャレンジしているとのこと
じゃらんはiOS/Androidともにリリースから10年を迎えている
- プロジェクトの大規模化によるビルド時間の増加
- 全体的にコードが古い
という問題があり,フルルニューアルを考えた
→クロスプラットフォーム技術の採用 →Flutterがもっとも生産性が高いという技術検証からの結論があったとのこと
移行の手法
段階的にリプレースする.
Add-to-appというものを使用し,既存のネイティブプロジェクトにFlutterプロジェクトを部分的に組み込む.
→部分的に組み込んだものをFlutterモジュールと呼ぶことにする
レイアウト構築
- Widget
- UIの構成情報を保持するクラス
- デザインもWidgetを用いて表現する
直面した課題
- タブ切り替えのパフォーマンス
- Aにテキスト,Bに1000このリストを入れておき,Bのリストを一番下までスクロールしてから再度Aに戻り,その後Bに戻ると重くなる
- タブを切り替えた際,表示されていないWidgetはツリーから除外される.
- タブが保持するリストのアイテムの高さが可変の場合,1つ目のアイテムから順番に大きさを計算しなくてはならず,結果としてスクロール位置までのすべてのアイテムの大きさを計算し直すため重くなる
- Flutterの画面が初期化されない
- Nativeの画面からFlutterの画面を閉じて再度開くと,前回開いていた状態が復元されてしまう
- 画面を破棄して再生成したのに状態が残るのはおかしい:thinking_face:
- Flutterの画面を閉じるとFlutterViewContollerは破棄されるが,FlutterEngineは破棄されない(Flutter Engineの初期化はコストが大きいため,AppDelegate等で予め行う)ため,Dart内部の状態は全部残る
- InitialPageという空のページを生成しておき,ここで前回表示していた画面をすべて破棄し,新しい画面を開くようにした
- ネットワーク通信がproxyサーバを経由しない
- パケットモニタリングを使用したいことがあったが,通信がProxyサーバを経由せずに,使えなかった
- HttpClientクラスにプロキシ自動設定(PAC)を手動で設定する必要がある
- system_proxyというパッケージがあり,システムのProxy設定を取ってきて指定することが可能
- Google Mapsがクラッシュする
- google_maps_flutterパッケージを使用
- Developers Previewだった
- 何度も開くとクラッシュする→メモリが開放されなず,メモリリークがあった.
- PlatformViewに循環参照があり,メモリリークしてたw
- 1.15.17にアップデートしたら治った
- Developer Previewでは既知の問題にはIssueタグが付与されており,一覧できる
プラットフォームとして未成熟かも? →だけど,一応使えるので,プロダクション採用を見送るまでには行かなかった.
メリット
- 開発効率の向上
- 従来の2〜3割り増し?
- 既成部品が充実していた
- hot reeload / restart
- ビルド時間が大幅に短縮した
- IDE(Android Studio)がXcodeに比べてつよい
- Widget上でoption+Enterを押すと
- stless stfulで自動的にリソースを作成
- 工数の削減
- じゃらんはメディアであり,プラットフォーム依存のコードは少ない
- iOS/Androidの使用差分をなるべく減らし,デザインはマテリアルデザインにといういつされているため,やりやすかった
- 段階以降を行っているため,各プラットフォーム固有のコードを書く必要はあるが,大部分が共通化されているので良い
- 宣言的UI
- OSSなのでコードを読める
- パフォーマンスモニタリングが充実
- すべてDartで書くので,コンフリクトが少ない,コードレビューがかんたん
デバッグメニューのメンテナンスが大変だったので,専用アプリを作りました
Atomさんのセッション
デバッグメニューとは,
- 接続するAPIを切り替える
- 新機能のON/OFF
- アカウントの課金状態などを切り替える
- キャッシュ・DBを空にする
要するにHyperionみたいなやつ
→デバッグ・開発の効率化のために使う
pixivでのデバッグメニューの状態
あったりなかったり,UIが違ったり,実装方法も様々
- 社内テストのためにマニュアルを作らなくてはならない
- 学習コストが高い
- 新規導入コストが高い
- 開発優先度が低く,メンテナンスがされにくい
- 散財する
#if DEBUG
による自己リスク
→すべてのアプリのデバッグメニューを一つのアプリにすればよいのでは?
このアプリのことを「Chusya」と読んでいる
アプリの外側から他のアプリの状態を変更してた
仕組み
App Groups + UserDefaults
App Grpupsとは,同一のデベロッパーのアプリ間でデータ共有ができるようになる仕組み
アプリ側とChusya側でそれぞれUserDefaultsにアクセスできるようにし,それをApp Groupsを使って共有させる
UserDefaultsはKeyValue型のDBっぽい
App Groupsの設定
AppleのWeb SiteでCertificationのところでCapabilityからApp Groupsを選択し,追加する.
Xcode側でも同様にCapabilityからApp Groupsを選択し,追加する. このとき,上のサイトで作ったものが表示されるので,チェックする.
コードで使う場合は,UserDefaultsで使用する際にsuteName
として,App Grpupsの名前を入れるだけで使えるようになる
デバッグメニューアプリ作成のコツ
アプリの一覧とデバッグの状態を変化させる画面の2画面で構成されている.
→デバッグメニューの値を増やすごとにUI作るの面倒くさい
そこで,宣言的にUIを自動生成するようにした!
アプリ一覧画面では,アプリの情報を
(
App : "アプリ名",
name: "identification id"
id)
みたいにして追加するだけ.
デバッグメニューでは
(
StringSelector: Keys.apiEndpoint.rawValue, // UserDefaultsのKey
key: "接続先API", // タイトル
title: [ // 値
values"https://...",
...
]
)
https://github.com/fromatom/iosdc-2020-example
YamlやTomlを使わない理由
- 正しいデータ形式か確認する機構の実装が必須
- TAML,TOMLとしてのただしさ
- Chushaが扱う形としての正しさ
- Swiftで書けばコンパイラが正しいか見てくれる
- [Atomさんの感想]読み書きするのきつくない?
CI時のコツ
CIで自動生成される(!?)
実機用の.ipa
とシミュレータ用の.app
を自動生成している.
.app
だけ節目詠する.
Fastlaneでxcodebuildでiphonesimulatorを使うか,Bitriseにはxcodebuild for simulatorというそのままのタスクが存在する.
あとはこれをGitHub Releeasesにアップロードするだけ.かんたん.
出てきた.app
はドラッグ・アンド・ドロップするだけでインストールできる.
メリット
- 一つのアプリで複数のアプリにデバッグ情報を注入できる
- 起動した時点でデバッグ情報が注入できる
- App Storeに公開されているアプリでも使える
#if DEBUG
がなくなる
デメリット
- Chushaインストールが必要になる
- 別アプリに移動しないといけない
- 即時反映系の機能が作りにくい
- DBクリア,キャッシュクリアなどがフラグ起因で起こるので,すぐには実行されない
Ask-the-speaker
- Hyperionは?
- 複数のアプリの共通化をしたかったので今回は採用見送り
- アプリが一つ,2つくらいであればHyperionで良いと思う
- アプリが多すぎてChushaのリリースタイミングがぐちゃぐちゃになるのでは?
- 今の所起こっていない
- ビルド時にテストが通ってれば他のアプリには鑑賞しないということが保証されている
- Chushaのアップデートはどのくらいの頻度?
- 全然更新されえないw
- Chushaは注入だけではなくて抽出もできる!
- App Store版がおかしくなったらUserDefaultsを抽出して確認することも可能
エラーアーキテクチャ設計について考える
Androidもやってる人っぽい
Androidの話もするっぽいw
モチベーション
エラーをいい感じに扱いたい!
iOSアプリでエラーハンドリングを改善した話
ユースケースのエラーをもっと表現力豊かにしたいなぁ
- Rxを使用
- クリーンアーキテクチャ
- Singleを使っていることにする
onErrorで帰ってきた値をSwitchで分岐して実装
→これだとどんなラーが起こるのかget()メソッドに帰って見る必要があるのと,Switchだとdefaultを設定しないといけない
→そこで,Enumでエラーを定義する
UseCaseでUseCaseErrorに変換する
UseCaseErrorはapplyだけがあるProtocolで,実際にはそれぞれのUseCaseでそれぞれ必要なエラー型を定義し,applyで変換する
早速使ってみた
UseCaseResultにくるむのはかんたん.しかもEnumでSwitchすれば良いので,考慮漏れも起きづらい
Androidアプリの場合
機能の個別エラーを追加しづらい
- クリーンアーキテクチャ
- Eitherに変換するのがむずい
- 抜け漏れなくやりたい
ある特定の固有エラーが有っても共通機構に則って処理しないと行けなくなっていた
いろんな機能の知識が一箇所にまとまるため,そこがどんどん太っていく.「神エラー」ができてしまう.
- 機能個別のエラーは個別定義
- それぞれは機能だけに閉じているべき
- もともとあった共通処理は残したい
FeatureSpecificというExceptionを定義しておき,機能ごとに個別に定義する
変換処理は「共通 + 個別」
個別エラーのみ,外から差し込まれたメソッドに変換処理を移譲する
固有のエラーの変換機構は利用側がにない,共通の仕組みはその詳細を知らない状態を作った
エラーアーキテクチャについて
- 型で成約をかける
- 列挙型を使用する
- 個別のエラーは個別に定義・処理する
The Clean Architeecture「優れたアーキテクチャはユースケースを強調し,周辺の関心事からユースケースを切り離すのだ」
強調・分離すべきユースケストは?
- 基本コース: 晴れの日
- いつもどおりのシナリオ
- 代替コース: 雨の日
- なにか悪いことが起こったときにシナリオ
エラーアーキテクチャは雨の日のシナリオを強調する
Error型を使うと,雨の日のシナリオは実装依存になっています
AppUseCaseErrorを使うと,雨の日のシナリオを強調し,分離している
「基本コースの中で,何が起こるのか問いかけることで,雨の日で起こることを探し出すことができる」
「エラーアーキテクチャとは,雨の日にも道を照らす街灯」
Ask-the-speaker
- スライドで使っていたかわいい画像はどこから?
マルチモジュール,ビルド速度快適化
Googler.なんか強そう
Androidチームの人
Google入社の前の内容の発表
実践マルチモジュール編
モジュールを分ける基準
- ドメインロジックがグループに分けられる
- スコープが well-definedである
- 複数のアプリで適用可能なアプリ
- 既存モジュールと明確に違う依存性を要するモジュール
- 長期間担当するチームが存在している
→Single Responsibility Principle
中規模以上じゃないとやりすぎでは?と思っていたが,2人以上ならやらない理由はない.あとから分離するのは難しいので,できれば初期段階で分けたほうが良い
マルチモジュール設計
- コードベース全般に破壊的な影響を与える.
- モジュール化のためのPRをマージすると大量のcnflictが必然的に起こる
- QAさんとのすり合わせ(フルリグレッションテストの必要性がある)
モジュールをどう分けるのか?
- ドメインごとにモジュールを分ける
- 画面ごとに分けるのもあり
- すべての場所から呼び出せるものを切り出すのも大事
- コンポーネントとか,apiとか.ただ,基本的にdata層は各モジュールごとに定義されるべき
Common Module分離
- Project生成の仕組み作成
- XcodeGenとかを使う
- 既存のxcodeprojはgitから削除
- 意外と大量のコミュニケーションを要する
- 共通モジュールだけ分離する
各domain module分離
- 必要なら,各モジュールのエンジニアとペアプロしながらやる
- この段階で作られたものがその後の基準になるため,なるべく正しくやるべき
- 最も時間がかかる作業
- 複数のモジュールが同じクラスを参照してしまっている場合
- 複数の責務を持っている可能性があるので,分ける.
- それ以外の場合,上位のモジュールを作る
- ファイルが分離された場合
- 2つのファイルの一つはgitの履歴を失ってしまう.
- もとのファイルのblameのGitHubのリンクを新規作成したファイルに貼ると良い
Domainごとの分離
- 各ドメインを3-4のレイヤに分離
- binding: モジュールをせえっとアップするための浅いレイヤ
- presentation: UIとビジネスロジックのレイヤ
- そこまでたくさん切らなくても良い: モバイルには複雑なロジックが入らないため.
- data: databaseとnetworkコード等
- 共通パーツ
- 依存性逆転の原則
- 上位レベルのモジュールは下位レベルのモジュールに依存すべきではない
- 上位モジュールにProtocolをつくって下位モジュールはそのProtocolの実装を行う
- この作業は面倒くさい
- スクリプトを作るとかすると良い
- struct…
- struct のdefault initメソッドはintrenal
- public initを別途書かないといけない
- [Refactor] - [Generate Memberwise Initializer]
- フィールドもいちいちpublicにしないと行けない
- struct のdefault initメソッドはintrenal
一旦完了
- これだけで,small incremental buildの速度は2〜50倍まdえ上昇
- でもlarg incrementalはそんなに変わらない
- モジュールごとの作業だけならかなりはやくなる
- チームとしても良い
ビルド編 google/bazel
bazelとは?
- Blaze基盤のビルドシステム→非常に巨大で複雑なビルドシステム
- クロスプラットフォーム(host/target)
- マルチ言語
- 拡張性:Skylarkを見るとわかるが,非常に柔軟
- 密閉(Hermetic):依存性グラフ
- 正しさ(Correct):一つ一つのアクションが相互に影響しない
- パフォーマンスにもつながる.
- 暗黙的な依存性がない
- Bazelは必要なときだけ再ビルドする
- スケーラブ
ビルドフェーズは3つ
- Loading Phase
- ビルドファイルを解析し,グラフを作る
- ルールの過不足チェック,循環参照チェック
- Analytics Phase
- ルールが効率的に設定されていないと時間かかることがある
- Execution Phase
- 実際にビルドされる
- Actionごとにキャッシュが利用できる場合は利用する
- ターミナルのログの殆どはここで見える
→嘘みたいなパフォーマンスが得られる →なぜかClean buildも早い
嘘みたいなビルド環境
- Linuxでビルド可能
- 分析段階とかだけならLinuxでできるので,ここだけサーバに送って高速化可能
- CIの負荷も軽減
→使わない理由なくない?
- 何故かClean buildも早い件
- サンプルが去年の例で,Legacy buildとの比較だった
- ビルド自体がリモートで実行できる件
- Remote Executionの設定の難易度が高い
- Setting bazel is extremely painful
- リモート環境で使っているlibcのバージョンが違ったりすると動かない
- Xcodeとの相性が悪い💩
欠点の対策
- まずはCIサーバへの対応を目標にする
- BazelのBuild→.xcodeproj生成
- Cocoapods,Carthage,SPM→BUILD
- podsだとPodToBUILDがある
- ほかは手動
- 分析が遅いので,最適化が必要
- ソースコードをNFSに格納しない.
- profileしまくって改善する
- 最も依存せえいチェインが長いものから修正
Xcodeとの基本連携
- .app + .xctestなどのコピーせ彫ってい
- デバッグ設定を生成
その他
Indexingができない
Debugにも問題がある
バックグラウンドでのIndexingは永遠にできない
Googleが色々やってくれるのは朗報
- ルールとかもメンテしてくれる
- バグレポートとかもやってくる
他の会社も積極的にサポート
テスト
Googleはこれだけ大量のコードベースを管理する方法として,テストを中心とした開発体系をとっている.
良いテストの条件
実際のアプリの期待している行動と一致している
テストケースはドキュメントにもなりうるべき
目的または行動の変更がない限り変更しない
基本的にはAPIはfakeを使う.どうしてもというときだけモックを使う
Ask-the-speaker
- サンプルはあるか?
- 公式サイト にチュートリアルとかはあるが,あまり良くない.今後Googleが色々やるようなので出てくることに期待
- Xcodeのビルドボタンを押したら何が起こるのか?
- Xcodeでボタンを押したときに実行されるスクリプトを切り替えて一応ビルドすることは可能
- デバッグができないと言っていたがどういうことか?
- 一応できる.設定を頑張れば結構なんとかなる
- Incremental Buildはマルチモジュールじゃないとできないのか?
- できる.だが,もっと効率よくIncremental Buildできるようにするためにマルチモジュール化する
- ただし,普通はClean buildは遅くなるので,それを改善するためにBazelを使用する
- Remote Executionはラスボス
- Googleにはそのための組織すらあるw
- ドメインと言っていたのは,ドメイン層の話ではなく,機能の塊の話をしていた.
無料トライアル施策のしくじりから学ぶサブスクリプションベストプラクティス
自動更新サブスクリプションでしくじった.
自社から◯十万円のお返しが発生した・・・
- 無料トライアルとかってサクッとできる?
- 3日だけとかミニマルでできる?
- 重複課金とか怖いなー
と言われてしまった
検証
- Appleが無料トライアルの仕組みを提供してる?
- お試しオファーとか言うものがある
- 期間の指定は?
- 期間とかも設定できる
- 審査無しでできる
- プランの構成は?
- アップグレード
- ダウングレード
- クロスグレード
- ガイドラインによる規制は?
- 同じ期間・同じサービスのプランを作ってもよいのか?
- ベストプラクティス,クソ長い
- 意図的にアプリ側がプランの対象ユーザを制限してよいのか?
- 記載なし
- 無料トライアルプランのテストはできる?
- サンドボックスで試験できる
- この環境では時間の流れが歪んでいる
- 1アカウント一回しかオファーは購入できない
- アカウントの作成にはAPIがある
- fastlaneで10数行でアカウントは作成可能
- アカウントは量産しよう
プランの申請
アプリとともに審査されるため,早めに審査する必要がある 審査は通過!
→買えないはずのプランがユーザに買われてしまったwww
アプリ外からプランを購入する動線がある!:AppStore,端末の設定から変えてしまう
別グループであれば購入は防げるのではないか →審査してみた→リジェクトされた!
不必要にグループを分けることがベストプラクティスではない.→アプリ外からの購入を防ぐのは諦めるしかない.
プラン名を若干変更してもう一度リリースした
しかし,本当の悲劇はこれからだった
キャンペーン検証の最終日
トライアルを購入したのに決済が走った
何故かトライアルが有効なレシートが突然全部消えた
お試しオファーの終了日という項目は,その日からオファーを無効化するという意味だった!(初見殺し)
補足対象を限定した無料トライアルはできない?
- リジェクトされえないためには既存グループに追加する必要がある
- アプリ外からの動線は隠せない
- オファーコードと言うものがAppleから出た
- 一度しか利用できないコードを配ることが可能
- ただし,非営利目的でしか使えない
- あとiOS 14以上
学び
- アプリ外の購入動線があるため,既存グループでの新しいプランはストアに反映した瞬間から購入され得うる
- 別のグループに同じ内容のプランを作るとリジェクトされる
- App Storeの終了日は期間終了日の翌日に設定する必要がある
- オファーコードの登場により,対象を性外縁した無料トライアル,割引の提供ができるかも
Ask-the-speaker
- 終了日は時間まで指定できる?
- ちょっと聞き取れなかったが,できなさそう
- タイムゾーンはどうなっているか?
- 地域を選べる
- →今回は日本だけにしたので,日本時間ですべてが起こった
- おそらく,提供する地域のタイムゾーンに合わせてイベントが起こるようになっている
- Appleに支払った30%は帰ってこない?
- 今回はこちらのミスなので,Appleからの返金は絶望的
- 課金周りは最近改善されているが,まだ改善してほしいことはある?
- サンドボックスの改善をしてほしい
- 最終日に買ったということだが,ユーザ側のTouchIDとかはどうなっていた?
- 注意深く見れば,期間と課金開始日時のズレは気づいたかもしれない
- オファーコードは今後スタンダードになっていくと思うので.キャッチアップしていってほしい