「iOS 知識体系の全貌を体験する」シリーズは久しぶりですが、新しい章「アプリ開発編」を始めます。この章では、iOS 開発におけるGUI フレームワーク、リアクティブフレームワーク、アニメーション、A/B テスト、メッセージバスについて説明します。
前文のおすすめ:
最初の章「基礎編」を見逃した場合は、こちらからジャンプできます:
「iOS 知識体系の全貌を体験する」基礎編(最適な学習パス)
21 | Cocoa 以外に iOS で使用できる GUI フレームワークは?#
アプリの起動速度を最適化するためには、メインスレッドからアプローチするだけでなく、GUI(Graphical User Interface、グラフィカルユーザーインターフェース)での最適化も考慮する必要があります。
現在人気の GUI フレームワーク#
現在人気の GUI フレームワークは Cocoa Touch の他に、Texture(旧名 AsyncDisplayKit)、WebKit、React Native(Facebook)、Flutter(Google)があります。それらの比較は以下の通りです:
GUI フレームワーク | Cocoa Touch | Texture | WebKit | React Native | Flutter |
---|---|---|---|---|---|
プラットフォームサポート | iOS | iOS | iOS、Android | iOS、Android | iOS、Android |
プログラミング言語 | OC、Swift | OC、Swift | JavaScript | JavaScript | Dart |
レンダリングエンジン | CoreAnimation | CoreAnimation | WebCore | Native | Skia |
レイアウト方法 | Frame、Auto Layout | FlexBox | Frame、FlexBox | Frame、FlexBox | Frame、FlexBox、LayoutWidgets |
-
FlexBox レイアウトは、iOS 開発者がフロントエンドの先進的な W3C 標準のレスポンシブレイアウトを使用できるようにします。iOS が新たに導入した UIStackView レイアウト方式も、FlexBox レイアウトの考え方に基づいて設計されています。Cocoa Touch フレームワーク自体は FlexBox レイアウトをサポートしていませんが、Facebook のYoga ライブラリを使用することで FlexBox レイアウトを利用できます。
-
Texture フレームワークの基本単位は UIView の抽象ノードである ASDisplayNode です。UIView と比較して、ASDisplayNode はスレッドセーフであり、バックグラウンドスレッドで全体の階層構造を並行してインスタンス化および構成できます。Texture が非同期ノード計算を使用しているため、メインスレッドの応答速度を向上させることができます。
-
iOS 開発で最も一般的に使用される UIWebView および WKWebView コントロールは、WebKit フレームワークに基づいています。WebKit フレームワークについては、著者が「WebKit を深く分析する」というブログを書いており、その原理を詳細に分析しています。
-
React Native と Flutter フレームワークのレンダリングに関する詳細な紹介は、後の「ネイティブとフロントエンドの共演」編で詳しく説明します。
GUI フレームワークには何がある?#
コントロール、レンダリングツリー、レンダリングレイヤーツリー。
-
コントロールは主にインターフェース要素のデータの保存と更新を担当します;
-
レンダリングツリーは、コントロール間の関係を記録する抽象的なツリー構造です;
-
レンダリングレイヤーツリーは、レンダリングレイヤーオブジェクトで構成されており、レンダリングレイヤーオブジェクトは GUI フレームワークの最適化条件に基づいて作成され、どのコントロールが含まれているかを記録し、レンダリングツリーのレイアウトと組み合わせて Bitmap を生成し、最終的に GPU にレンダリングを提供します。
それらの関係図は以下の通りです:
その他:
-
WebKit を使用したウェブページの表示が遅いのは、他のフレームワークよりもレンダリング性能が劣っているからではなく、主に CSS や JavaScript リソースの読み込み方法によるものです。また、HTML、CSS、JavaScript を解析する際には古いバージョンとの互換性が必要であり、JavaScript の型推論が失敗すると再実行され、リストに再利用メカニズムが欠けているなどの理由から、WebKit フレームワークの全体的な性能は他のフレームワークよりも劣っています。
-
Flutter はもともと Chrome ブラウザエンジンに基づいていましたが、Flutter の性能を考慮して、Google は HTML、CSS、JavaScript のサポートを削除し、自社の Dart 言語を使用して歴史的な負担を取り除きました。具体的な詳細はFlutter の創始者 Eric へのインタビュー動画を参照してください —— 知乎。
レンダリングプロセス#
GUI フレームワークのレンダリング技術は常に安定しており、一般的にはレイアウト、レンダリング、合成の 3 つの段階を経ます。
-
レイアウト段階:主にレンダリングツリーに基づいてコントロールのサイズと位置を計算します。
-
レンダリング段階:主にグラフィック関数を使用してインターフェースの内容を計算します。一般的に、2D 平面のレンダリングは CPU 計算を使用し、3D 空間のレンダリングは GPU 計算を使用します。
-
合成段階:主にレイヤーを統合し、表示メモリを節約します。
Texture の Node の非同期描画:
ユーザーインタラクション体験を大幅に向上させたい iOS 開発者にとって、Texture は非常に小さな切り替えコストと大幅な性能向上を提供します。
その利点は:
-
スレッドセーフな ASDisplayNode を開発しました;
-
UIView と良好に共生できます。
非同期描画の原理:
ASDisplayLayer(CALayer のラッパー)は、全体の描画の起点であり、描画イベントは最初に displayBlock で設定され、その後 ASDisplayNode(CALayer 内の delegate の代わり)が displayBlock を呼び出して非同期描画を行います。displayBlock はスレッドセーフな Core Graphics を使用しているため、displayBlock をバックグラウンドスレッドで非同期実行できます。
22 | iOS リアクティブフレームワークの変遷を詳しく見る#
リアクティブフレームワークの紹介#
定義:リアクティブプログラミングパラダイムをサポートするフレームワーク。
特徴:リアクティブフレームワークを使用すると、プログラミング時にデータフローを使用してデータの変化を伝播でき、このデータフローに応じた計算モデルが自動的に新しい値を計算し、その新しい値をデータフローを通じて次の応答計算モデルに渡します。このプロセスは、応答者がいなくなるまで繰り返されます。
現状:iOS のリアクティブフレームワークには ReactiveCocoa(略称 RAC)や RxSwift がありますが、どちらも広まっていませんでした。フロントエンドが React.js を発表するまで、リアクティブな考え方は広がりませんでした。
では、なぜ ReactiveCocoa は iOS ネイティブ開発で広まらなかったのでしょうか?
問題を持って次に進む#
ReactiveCocoa フレームワークの考え方は、React.js の考え方と基本的に一致しています。それでは、React.js フレームワークが何をしたのか見てみましょう。鍵は仮想ドキュメントオブジェクトモデル(Virtual DOM)の追加です。
React.js フレームワークの底層には Virtual DOM があり、ページコンポーネントの状態とバインドされており、DOM(ドキュメントオブジェクトモデル)との間にマッピングと変換関係があります。そのレンダリング原理は以下の図の通りです:
見ることができるのは:
-
React.js フレームワークは最初に Virtual DOM を操作します。この時点では直接 DOM レンダリングは行わず、Diff 計算を完了してすべての実際の変化のノードを取得した後に DOM 操作を行い、全体をレンダリングします。Virtual DOM は JavaScript と DOM の間のキャッシュに相当します。
-
JavaScript が DOM を操作するたびにすべてを再レンダリングするのとは異なり、パフォーマンスの損失が大きいです。
問題に戻ります:なぜ ReactiveCocoa は iOS ネイティブ開発で広まらなかったのでしょうか?
フロントエンドにとって、DOM ツリーの構造は非常に複雑であり、完全な DOM ツリーの変更は深刻なパフォーマンス問題を引き起こします。Virtual DOM はこの問題をうまく解決できます。
一方、iOS ネイティブの Cocoa Touch フレームワークにとって:
-
このようなパフォーマンス問題は存在せず、そのインターフェースノードツリー構造は DOM ツリーよりもはるかに単純です;
-
さらに、そのレンダリングメカニズムはフロントエンドとは異なり、Cocoa Touch はビューを更新するたびに全体のビューノードツリーを即座に再レンダリングするのではなく、setNeedsLayout メソッドを使用してそのビューが再レイアウトされる必要があることをマークし、描画ループがそのビューノードに到達するまで layoutSubviews メソッドを呼び出して再レイアウトを開始し、最後にレンダリングします。
したがって、ReactiveCocoa フレームワークは iOS のアプリにより良いパフォーマンスをもたらしませんでした。フレームワークがあってもなくてもよく、明確な利益がない場合、一般的にチームは使用する理由がありません。
その他:ReactiveCocoa には学ぶべき多くの点があります。例えば:
-
上層インターフェース設計思想:関数型リアクティブプログラミング方式は、コールバックや KVO を通じて実現できます;
-
マクロの利用:参考にしてください Reactive Cocoa Tutorial: 神奇的 Macros——sunnyxx。
23 | クールなアニメーション効果を構築するには?#
業界の痛点#
-
アニメーションのコードを手動で記述するのは非常に複雑で、多くのアニメーションの詳細調整にはアニメーションデザイナーとの継続的なコミュニケーションが必要です;
-
iOS、Android、Web の各プラットフォームの開発者は、それぞれアニメーションコードを維持する必要があります。
質問:アニメーション制作とプログラミング開発を分離し、専門家が専門の仕事を行い、複数のプラットフォームでアニメーション効果を一貫させる方法はありますか?
Lottie#
それが Lottie フレームワークです。Airbnb がオープンソースで提供するアニメーションフレームワークです。
使用手順:
-
アニメーションデザイナーがAfter Effectsを使用してアニメーションを作成し、Bodymovin プラグインを通じてアニメーションを JSON ファイルにエクスポートします;
-
開発者が Lottie を使用してこの JSON ファイルを読み込み、レンダリングし、自動的に対応するアニメーションコードに変換します。
実現原理:
Lottie が iOS 内で行うことは:
1)JSON ファイル(After Effects で作成されたアニメーション生成の中間メディア)の内容を iOS の LayerModel、Keyframe、ShapeItem、DashElement、Marker、Mask、Transform などのクラスの属性に一つ一つマッピングし、保存します;
2)その後、CoreAnimation を使用してレンダリングします。
したがって、Lottie は実際にアニメーション設計ファイルを開発コードに変換するプロセスを自動化し、LayerModel などの属性の設定タスクを JSON ファイルと Lottie のマッピングルールに委ねています。
💡ヒント:
-
Lottie のような作業フローは、未来のトレンドかもしれません。iOS の現在の発展トレンドのように、ますます多くのビジネスロジックが Objective-C や Swift を完全に使用する必要がなくなり、JavaScript 言語や DSL、さらにはツールを使用してビジネスを記述し、そのビジネスを記述するコードを JSON のような中間コードに変換し、異なるプラットフォームが同じ中間コードを解析して処理し、中間コードで記述されたビジネスロジックを実行するようになります。
-
Lottie の詳細な説明と使用例コードは、Lottie 公式 iOS チュートリアルを参照してください。Lottie は物理効果だけでなく、ページ切替のトランジションアニメーションもサポートしています。
-
アニメーションデザイナーと連携できない開発者は、LottieFilesを見てみてください。これはアニメーションデザイナーが作品を共有するプラットフォームで、各アニメーション効果の JSON ファイルをダウンロードして使用できます。
24 | A/B テスト:意思決定効果を検証するツール#
A/B テストの定義#
A/B テスト、またはバケットテスト、分流テストとは、1 つの変数の 2 つのバージョン A と B を対象に、ユーザーの異なる反応をテストし、どのバージョンがより効果的であるかを判断することです。これは統計学で使用される二標本仮説テストに似ています。
簡単に言えば、A/B テストは異なるアプリユーザーが異なるバージョンの機能を使用する際に、どのバージョンのユーザーのフィードバックが最も良いかを確認することです。
アプリ開発における A/B テスト:
アプリのバージョンのイテレーションにおいて、旧バージョンを A/B テストの A バージョン、新バージョンを B バージョンと理解することができます。2 つのバージョンが同時に存在し、B バージョンは最初に少数のユーザーを B テストバケットに配置し、徐々にユーザー範囲を拡大し、A バージョンと B バージョンのデータを分析して、どのバージョンが期待する目標に近いかを見て、最終的にどのバージョンを使用するかを決定します。
全体として、A/B テストはデータ駆動の可逆的なグレースケールプランであり、客観的、安全、リスクが少なく、成熟した試行錯誤メカニズムです。
A/B テストの全景設計#
A/B テストフレームワークは主に 3 つの部分で構成されており、構造図は以下の通りです:
-
戦略サービスは、戦略立案者に戦略を提供し、意思決定プロセスと戦略の次元を含みます。
- 一般的にはサーバー側が提供し、ユーザー群の次元分布に基づいてテストバケットを随時割り当てることができます。
-
A/B テスト SDK はクライアント内に統合され、上層ビジネスが異なる戦略を実行するために使用されます。
-
おすすめ:SkyLab。著者の Mattt は、私たちがよく知っている AFNetworking ネットワークライブラリや Alamofire ネットワークライブラリの著者でもあり、このライブラリはインターフェース設計においてもブロックを使用して A/B バージョンの違いを処理しやすく、高い使いやすさを持っています。
-
発効メカニズム:ある戦略が 1 つの場所でのみ発効する場合は、ホットスタート発効メカニズムを使用できます。一方、複数の場所で発効する場合は、コールドスタート発効メカニズムを使用するのが望ましいです。
-
-
ログシステムは、戦略結果をフィードバックし、分析者が異なる戦略の実行結果を分析するのを担当します。
- 一般的にはサーバー側が提供します。
25 | どのようにして基盤となる発行と購読のイベントバスを構築するか?#
イベントバスの定義#
イベントバスは発行と購読のデザインパターンの実装であり、発行と購読を通じてコンポーネント間の 1 対 1 および 1 対多の結合関係を解消します。
このデザインパターンは、特にデータ層が非同期でデータを発行する方法で UI 層の購読者に通知するのに適しており、UI 層とデータ層が結合する必要がなく、データ層または UI 層をリファクタリングする際にビジネス層に影響を与えません。
iOS における関連技術#
-
ブロックとデリゲート。これは 1 対 1 のモードに適しており、次のデータ購読者に非同期で発行し続ける必要がある場合(メッセージ間に因果関係がある場合)、コールバックが他のコールバックにネストされる状況が発生します。
-
KVO と NSNotificationCenter。これらは 1 対多のモードをサポートしています。しかし、KVO を使用することはプロパティに強く依存しており、プロパティが更新されるとすべてのオブザーバーに発行され、対応関係が過度に柔軟で管理と保守が難しくなります。NSNotificationCenter を使用する場合も同様の問題があり、文字列を使用して発行者と購読者間の関係を維持するため、可読性が低く、KVO と同様に管理と保守が難しい状況に直面します。
Q:では、イベントバスを処理する良いサードパーティライブラリはありますか?
実際、前述のリアクティブサードパーティライブラリである ReactiveCocoa と RxSwift はイベントバスのサポートに問題はありませんが、これらのライブラリはリアクティブプログラミングに重点を置いており、イベントバスはその中の非常に小さな部分に過ぎません。したがって、それらを使用するのは少し大げさです。
現在、フロントエンド分野には Promise と呼ばれるパターンがあり、これは非同期データ操作のために特別に設計された一連の統一ルールのパターンです。
Promise#
本質的に、このパターンは Promise オブジェクトを通じて非同期データ操作を保存し、Promise オブジェクトは統一された非同期データ操作イベント処理のインターフェースを提供します。
Promise オブジェクトには3 つの状態があります:
-
pending:非同期イベントが処理を待っている状態;
-
fulfilled:非同期イベントが成功裏に完了した状態;
-
rejected:非同期イベントが成功裏に完了しなかった状態。
さらに2 つの重要なメソッドがあります:then と catch。
Promise オブジェクトは、then または catch メソッドを実行するたびに、前の Promise オブジェクトを返し、その Promise オブジェクトの状態は非同期操作の結果に応じて変わります。
-
then:then メソッドは対応する購読操作を実行し、Promise オブジェクトは then メソッドをトリガーして対応する発行操作を行います。then メソッドが実行されると Promise オブジェクトが返され、複数の then メソッドを同期的に実行し続けることができ、これにより 1 つの発行操作が複数の購読イベントに対応します。
-
catch:then メソッドを実行した後に返される Promise オブジェクトが rejected 状態の場合、プログラムは直接 catch メソッドを実行します。
Q:では、iOS で Promise パターンをどのように使用するのでしょうか?
PromiseKitを導入します(Homebrew の作者 Max Howell が開発しました)。
さらに、PromiseKit は Apple の API に対して拡張を提供しており、UIKit、Foundation、CoreLocation、QuartzCore、CloudKit などをサポートし、さらにはサードパーティのフレームワークである Alamofire もサポートしています。具体的にはPromiseKit Organizationを参照してください。
シンプルで明確で規範的な Promise インターフェースを通じて非同期データ取得、ビジネスロジック、インターフェースをつなげることで、将来的な保守やリファクタリングが容易になります。ぜひ試してみてください~
さて、今日のシェアはここまでです~次回は「iOS 知識体系の全貌を体験する」アプリ開発編(下)に入ります。JSON 処理、レイアウトフレームワーク、リッチテキスト、TDD/BDD、コーディング規範などに関連する内容が含まれていますので、お見逃しなく、次回お会いしましょう!