「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、コーディング規範などに関連する内容を扱いますので、お見逃しなく、次回お会いしましょう!