ハイライト
Xcode 15 では、静的リンカーを介してビルド時にメタデータを生成するマージ可能ライブラリが導入されています。これにより、開発中に動的ライブラリのビルド速度の利点を維持しながら、動的ライブラリを静的ライブラリと同様に最終バイナリにマージしてサイズを削減し、起動を高速化できます。
主要内容
静的ライブラリと動的ライブラリの間のトレードオフ
開発者はライブラリの種類を選択する際に、長い間次のようなジレンマに直面してきました。
静的ライブラリ は、ビルド時にコードをアプリのバイナリにコピーします。利点は、起動が早く、dyld の読み込みが必要ないことです。欠点は、コードを変更するたびに再リンクする必要があるため、反復的な開発とデバッグが遅くなることです。
ダイナミック ライブラリ (dylib) はビルド時にインストール パスを記録するだけであり、コードはコピーされません。利点は、ビルドが速く、ライブラリの追加または更新のコストが低いことです。欠点は、dyld が起動時にすべての依存関係を検索してロードする必要があることです。フレームワークの数が増えると、メモリ使用量と起動時間も長くなります。
(00:55)
Apple プラットフォームはシステム ライブラリに対して多くの最適化を行っていますが、App バンドルに埋め込まれたサードパーティのフレームワークはこれらの最適化を享受できません。したがって、歴史的には、特定のアプリケーションに対してトレードオフを測定する必要がありました。
マージ可能ライブラリの仕組み
Mergeable Libraries の核心は、動的ライブラリを構築するときに追加のメタデータを生成することです。
(04:06)
静的リンカーが動的ライブラリを作成する場合は、次を使用します。-make_mergeableオプションはメタデータを記録します。このメタデータはバイナリ内に埋め込まれているため、リンカーは後続のリンク中にライブラリを静的ライブラリであるかのように扱うことができます。
結合するときに使用します-merge_libraryまたは-merge_frameworkオプションを使用すると、リンカーはメタデータを利用してライブラリの内容を最終出力に組み込みます。マージされたバイナリは元のファイル タイプを保持します (実行可能ファイルは実行可能ファイルのまま、フレームワークはフレームワークのままです)。
(05:10)
合併のメリット:
- サイズ削減: リンカーは、ライブラリ間で文字列、シンボル参照、Objective-C セレクター、および objc_msgSend スタブの重複を排除できます。
- 起動高速化: dyld とカーネルがロードする必要があるフレームワークの数が減り、メモリ使用量が削減されます。
- ビルドは高速のまま: デバッグ モードではマージは実行されず、動的リンクのビルド速度が維持されます。
デバッグ モードの特別な処理
デバッグ モードでは、リンカーは実際にはライブラリをマージしませんが、再エクスポート メカニズムを使用します。
(16:41)
再エクスポートにより、コード実装は動的ライブラリ内に常駐しますが、外部には別のライブラリ実装として表示されます。つまり、App Extension またはテスト ターゲットは、マージされたターゲットに依存するだけで、純粋な動的リンクと同等のビルド速度ですべてのライブラリ API にアクセスできます。
詳細
構成を自動的にマージする
Xcode で自動マージを有効にするには、次の 1 つの設定だけが必要です。
MERGED_BINARY_TYPE = automatic
(09:02)
Xcode インターフェイスでは次のようになります。
- アプリのターゲットを選択します
- ビルド設定を入力します 3.「マージされたバイナリの作成」を検索します
- 値を「自動」に設定します。
自動マージにより、直接依存するすべての組み込みフレームワーク ターゲットがマージ可能になります。システム フレームワーク (SwiftUI など) は影響を受けません。
エクスポート シンボルを減らしてサイズを最適化します。
マージ後、ライブラリのエクスポートされたシンボルはアプリ内に残ります。通常、アプリではシンボルをエクスポートする必要はありませんが、これはボリュームとビルド時間に悪影響を与える可能性があります。
(10:04)
他のリンカー フラグを追加します。
-Wl,-no_exported_symbols
アプリがアプリ拡張機能のエントリ ポイントを保持する必要がある場合は、シンボルのエクスポート ファイルを使用して、どのシンボルを保持するかを正確に制御します。
構成を手動でマージする
フレームワークの一部のみをマージする必要がある場合は、手動モードを使用します。
// 在合并目标上
MERGED_BINARY_TYPE = manual
// 在要合并的库目标上
MERGEABLE_LIBRARY = YES
// 在保留的库目标上(默认)
MERGEABLE_LIBRARY = NO
(11:10)
手動マージの一般的なシナリオ: 「グループ ライブラリ」 (ForestKit など) を作成し、複数の内部フレームワークをそれにマージし、アプリとテスト ターゲットの両方をこのグループ ライブラリにリンクします。
ランタイム ルックアップ API の制限事項
コードが使用している場合dlopenまたはNSBundle.bundleForClass実行時に API を検索するときは、次の点に注意する必要があります。
(19:26)
dlopenパスはマージされたフレームワーク ターゲットを指す必要があります -Bundle(for:)/bundleForClass:iOS 12 がフレームワーク バイナリに依存してバンドルを検出する前は、マージされたフレームワークには実行時にスタンドアロン バイナリがありませんでした。- 最小展開バージョンでは、バンドル検索サポートを使用するには iOS 12 以降が必要です
- これらの API に依存しない場合は、次の API を追加できます。
-no_merged_libraries_hook導入バージョンの制限を回避するオプション
Mergeable ライブラリを配布する
マージ可能なライブラリは、Swift Package Manager または Xcode を通じて XCFramework を作成することで配布できます。
(23:17)
- マージ可能なメタデータにより、dylib サイズが約 2 倍になります
- メタデータはアプリの構築後に破棄され、最終的なアプリのサイズには影響しません。
- ボリュームの拡張を防ぐために、アプリに埋め込むときにメタデータが自動的に削除されます
重要ポイント
-
静的ライブラリをマージ可能な動的ライブラリに移行します
- 対処方法: プロジェクト内で広く使用されている静的ライブラリを動的ライブラリに変更し、マージ可能を有効にします。
- 実行する価値がある理由: 静的ライブラリの起動パフォーマンスの利点を維持しながら、動的ライブラリのビルド速度の利点を得ることができます。
- 開始方法: ライブラリのターゲットを静的ライブラリからフレームワークに変更し、設定します
MERGEABLE_LIBRARY = YES
-
大規模なアプリの自動マージを有効にする
- 対処方法: アプリ ターゲットで自動結合をオンにする
- 実行する価値がある理由: 1 つのスイッチで組み込みフレームワークの数を減らし、パッケージ サイズと起動時間を削減できます。
- 開始方法: ビルド設定で「マージされたバイナリの作成」を検索し、自動に設定します。
-
グループ ライブラリを作成して依存関係グラフを簡素化
- 内容: 複数の内部フレームワークをグループ フレームワークにマージします。
- 実行する価値がある理由: アプリ拡張機能とテスト ターゲットは 1 つのフレームワークにリンクするだけで済み、dyld 読み込みのオーバーヘッドが軽減されます。
- 開始方法: 新しいフレームワーク ターゲットを作成し、手動マージを有効にして、サブフレームワークをマージ可能に設定します。
-
エクスポートされたシンボルを最適化してサイズをさらに削減します
- やるべきこと: を使用します
-no_exported_symbolsまたは正確なエクスポート シンボル ファイル - 実行する価値がある理由: 通常、アプリはシンボルをエクスポートする必要がなく、ポストリンカーを削除すると、無効なコードをより効果的に削除できます。
- 開始方法: 他のリンカー フラグを追加する
-Wl,-no_exported_symbols
- やるべきこと: を使用します
関連セッション
- リンクの高速化: ビルドと起動の時間を改善する — リンクの最適化とビルドと起動の速度の向上について詳しく説明します。
- Xcode 15 の新機能 — Xcode 15 の新機能の紹介
- Swift マクロの作成 — Swift マクロの作成と使用
- プラグインについて Swift Package Manager を紹介 — Swift Package Manager プラグイン
コメント
GitHub Issues · utterances