古い macOS リリースをサポートするには、新しい API に対応した SDK でビルドし、サポート対象の最小デプロイメントターゲットを設定して、新しい呼び出しを利用可能性チェックと動作するフォールバックで保護します。この手順では、macOS 14 のビューを採用しながら、小さなネイティブのメモウィンドウを macOS 12 でも使えるようにします。

どの互換性設定を一致させる必要がありますか?

Mac アプリ開発者が取り組む具体的な課題は、サポート対象の最も古いリリースで起動し、新しい機能が利用可能な場合はそれを使うアプリを作ることです。まず、3 つの入力を分けて考えます。コンパイラはソース言語を理解し、SDK は API の宣言を提供し、デプロイメントターゲットは対象とする最も古い実行環境を指定します。Apple のビルド設定リファレンスでは、SDKROOT を、ビルドに使用されるヘッダーとライブラリを含む SDK と定義しています。ビルド設定リファレンス

新しい SDK と古いデプロイメントターゲットは併用できます。Clang は、最新の SDK に対してビルドしつつ、-mmacos-version-min= で以前の macOS バージョンを最小要件に指定できることを明記しています。この組み合わせでも、最小要件より後に導入された API は利用可能性チェックで囲む必要があります。Clang 言語拡張

選択した Xcode が実際にサポートする最小バージョンを選びます。Apple の互換性表では、Xcode の実行に必要な macOS バージョンと、ビルド可能なデプロイメントターゲットが区別されています。希望する最小バージョンがツールチェーンのサポート範囲外なら、適切なサポート対象ツールチェーンを選ぶか、フォールバックを実装する前にアプリの最小バージョンを見直してください。SDK とシステム要件

この架空の例では、macOS 12 以降を対象とし、macOS 14 API を含む SDK を使い、空のコレクションとサンプルメモ 1 件の 2 つの状態を持つメモウィンドウを作ります。新しい空状態の表示は任意で、メモの表示は最低要件の環境でも利用できます。

両方のビルド構成で最小バージョンを設定する

アプリターゲットの Build Settings で、Debug と Release の両方について MACOSX_DEPLOYMENT_TARGET を 12.0 に設定します。設定ファイルで管理する場合、以下はプログラムではなく、完全な設定フラグメントです。

MACOSX_DEPLOYMENT_TARGET = 12.0

プロジェクトの Info タブで、この設定ファイルを対象の構成に割り当てます。その後、最終的なターゲット値を確認してください。Apple の説明によると、ターゲット設定は割り当てられた設定ファイルの値を上書きする場合があります。ファイルをプロジェクトに追加しただけでは、その設定は有効になりません。プロジェクトへのビルド構成ファイルの追加

アプリの最小バージョンを引き上げずに新しい API を追加するには?

Swift の if #available は実行時の利用可能性を確認し、その分岐内で使用できる API をコンパイラに伝えます。同じ判定の中にフォールバックも用意し、古いシステムでも完全に動作する実装にします。必須の * は明示的に列挙されていないプラットフォームを対象にしますが、指定した macOS API を以前のリリースで使えるようにするものではありません。制御フロー — API の利用可能性の確認

Apple の ContentUnavailableView は macOS 14 以降で利用可能で、コンテンツが空の場合の表示に対応しています。ここでは小さな VStack を使って、macOS 12 と 13 でも同様の表示を行います。どちらの分岐でも、次に行う操作を同じように伝えます。ContentUnavailableView

SwiftUI の架空アプリ全体

CompatibilityNotes という名前の macOS アプリプロジェクトを作成し、Swift と SwiftUI を選択して、デプロイメントターゲットを 12.0 に設定します。macOS 14 API を含む SDK を使用します。生成されたアプリとコンテンツビューの宣言を、次の 1 つのファイルに置き換え、ターゲット内の @main 宣言が 1 つだけになるようにしてください。アセット、パッケージ、入力ファイルは不要です。

import SwiftUI

@main
struct CompatibilityNotesApp: App {
    var body: some Scene {
        WindowGroup {
            NotesView()
        }
    }
}

struct NotesView: View {
    @State private var notes: [String] = []

    var body: some View {
        VStack(spacing: 16) {
            if notes.isEmpty {
                emptyState
            } else {
                List(notes, id: \.self) { note in
                    Text(note)
                }
            }

            Button(notes.isEmpty ? "Add sample note" : "Reset sample") {
                if notes.isEmpty {
                    notes = ["Sample note: arrange the blue shapes."]
                } else {
                    notes = []
                }
            }
        }
        .padding()
        .frame(minWidth: 360, minHeight: 240)
    }

    @ViewBuilder
    private var emptyState: some View {
        if #available(macOS 14.0, *) {
            ModernEmptyState()
        } else {
            VStack(spacing: 8) {
                Text("No notes")
                    .font(.headline)
                Text("Add a sample note to begin.")
            }
        }
    }
}

@available(macOS 14.0, *)
private struct ModernEmptyState: View {
    var body: some View {
        ContentUnavailableView {
            Label("No notes", systemImage: "note.text")
        } description: {
            Text("Add a sample note to begin.")
        }
    }
}

動作はシンプルです。macOS 12 と 13 ではテキストのフォールバックを表示し、macOS 14 以降では ContentUnavailableView を表示します。Add sample note を選ぶと架空のメモが表示されます。Reset sample を選ぶと空の状態に戻ります。このデモではデータをメモリ内に保持し、起動をまたいでメモを保存しません。

@available 注釈により、このヘルパーは macOS 14 以降でのみ使用できます。呼び出し元の #available 分岐がこの制約を満たしています。注釈は宣言の利用可能性を示すもので、古いシステム向けの実装を提供するものではありません。属性 — available

この例は取得したドキュメントと照合して確認しましたが、コンパイルも実行もしていません。利用可能な実行環境は Linux で、Swift も Clang もインストールされていなかったためです。macOS でのビルドと実行時の確認が別途必要です。

リンク時に利用可能性チェックで解決できることは?

実行時の分岐が役立つのは、実行ファイルを読み込める場合に限られます。Clang の説明によると、デプロイメントターゲットより後に導入された、利用可能性注釈付きの宣言は通常、弱リンクできます。弱参照により、実行時に宣言が存在しなくても読み込みは可能になりますが、存在しない場合にアプリがそれを使用しないようにする必要があります。したがって、分岐だけでなく、デプロイメントターゲットとコンパイル時に認識される宣言も重要です。Clang の属性 — availability

この例では、SwiftUI は macOS 12 の最低要件でもすでに存在するため、通常どおりリンクします。互換性の境界は新しいビューを囲みます。最低要件の環境に存在しない新しいフレームワークを使う場合は、フレームワーク依存そのものを評価してください。1 つの呼び出しをガードしても、すべての読み込み要件が解決するわけではありません。Apple の弱リンクガイドでは、シンボルの不在を許容することと、実際にそのシンボルを使うことの違いを説明しています。フレームワークと弱リンク

ソース言語に対応した利用可能性構文を使います。

ソース言語 実行時の利用可能性条件
Swift if #available(macOS 14.0, *)
Objective-C または Objective-C++ if (@available(macOS 14.0, *))
Clang の Apple プラットフォーム対応を使う C または C++ if (__builtin_available(macOS 14.0, *))

Clang は、Objective-C の構文と C/C++ の組み込み関数に加え、-Wunguarded-availability についても説明しています。C 系言語のコードでは利用可能性診断を有効にし、古いデプロイメントターゲットが使えると判断する前に警告を調査してください。Clang 言語拡張

ビルド SDK に API が含まれていない場合は?

実行時チェックで、SDK にない宣言を補うことはできません。コンパイラが ContentUnavailableView を解決できない場合は、それを含む SDK でビルドしてください。古い SDK でもプロジェクトをビルドする必要がある場合は、そのビルドで未対応の参照が除外されるよう、意図的な条件付きソース構成を維持します。

Swift の #if compiler(...) はコンパイラのバージョンを調べ、#if canImport(...) はモジュールをインポートできるかを調べます。どちらも、既存モジュール内の個々のメンバーが実行中の Mac で利用できることを示すものではありません。これらのコンパイル条件は説明されている用途に使い、必要に応じて実行時の利用可能性チェックを使います。文 — 条件付きコンパイルブロック

Swift ABI と C++ ライブラリの制限は、どのように判断に影響しますか?

Swift の ABI 安定性があっても、新しい標準ライブラリ機能やランタイム機能すべてが古いシステムで使えるとは限りません。Swift の解説では、ソース言語モードとランタイムサポートを区別しています。言語モードの変更はコンパイル時の選択ですが、新しいランタイム機能を必要とする機能には OS の要件が付く場合があります。採用する機能ごとの利用可能性に従ってください。ABI 安定性後の Apple プラットフォームにおける Swift の進化

C++ でも、コンパイラのサポートとライブラリのサポートは別です。Apple は Xcode を通じて Apple Clang を提供し、SDK とオペレーティングシステムを通じて libc++ ランタイムを提供しています。その機能表では、OS が提供する共有ライブラリに依存する要件が示されています。-std=c++17 または -std=c++20 を選択しても、ユーザーの Mac にそのランタイム機能がインストールされるわけではありません。C++ 言語サポート

数値変換がよい例です。Apple によると、整数の std::to_chars と std::from_chars は macOS 10.15 以降が最低要件ですが、浮動小数点数用のオーバーロードには macOS 13.3 が必要です。そのため、macOS 12 アプリで浮動小数点数用のオーバーロードが必要になる場合は、互換性のある変換実装が必要です。ヘッダーや C++ 標準のバージョンだけでなく、使用する正確なオーバーロードを確認してください。C++ 言語サポート

すべての C++ ライブラリ呼び出しを __builtin_available で囲めば十分だと考えないでください。Clang の利用可能性属性には、それ以前のプラットフォームを対象にする場合に使用を拒否する strict 形式があり、libc++ はコンパイル時にランタイムサポートの不足を診断するための利用可能性注釈を使っています。選択したヘッダーがその操作を拒否する場合は、最低要件と互換性のある実装を使うか、最小バージョンを引き上げてください。Clang の属性 — availability、ベンダーのドキュメント — 利用可能性注釈

依存関係間でライブラリの ABI 構成を統一してください。LLVM は、具体的な ABI 保証とその限界の両方を説明しています。たとえば、新しい libc++ ヘッダーに対してコンパイルしたコードが古いライブラリで動作しない場合があります。ABI を変更する設定フラグはベンダー向けの制御です。アプリケーションコードでこれらを変更しても、互換性のあるランタイムの代わりにはなりません。libc++ の ABI 保証

完成したビルドをどう診断し、確認しますか?

失敗した段階に応じて、次に調べる対象を決めます。この判断ガイドは、ここまでのコンパイラ、利用可能性、ランタイムの境界に沿った診断手順であり、サンプルがテストに合格したという主張ではありません。

症状 まず調べること 次に行うこと
新しい API 名が認識されない SDK の宣言 その API を含む SDK を選択します。
API がデプロイメントターゲットより新しい ガードされていない使用 サポートされる利用可能性境界とフォールバックを追加します。
C++ ライブラリの操作が引き続き拒否される 正確なオーバーロードとライブラリ要件 互換性のある実装を使うか、最小バージョンを見直します。
ウィンドウが表示される前にアプリが失敗する 読み込み要件と依存関係 報告された不足ライブラリまたはシンボルを調べます。
新しい機能だけが失敗する ガードされた分岐の外で行われる処理 作成、初期化、ヘルパーの呼び出しを調べます。
1 つのプロセッサファミリーでしかビルドが動作しない 実行ファイルと依存関係のアーキテクチャ 必要なアーキテクチャスライスをビルドしてテストします。

アーキテクチャのサポートも別途必要です。Apple のユニバーサルバイナリガイドでは、コンパイル済みライブラリやフレームワークを含む arm64 と x86_64 を扱っています。アプリの実行ファイルだけでなく依存関係も確認してください。開発用 Mac でビルドに成功しても、そのビルドの互換性が確認できただけです。ユニバーサル macOS バイナリのビルド

このサンプルでは、リリース構成をビルドし、サポート対象のハードウェア上で macOS 12、macOS 13、macOS 14 以降において、生成したアプリをテストします。各システムで起動し、サンプルメモを追加してからリセットします。古いリリースでも、フォールバックを通じて操作をすべて利用できる必要があります。配布する予定の各アーキテクチャをテストしてください。

アプリが大きくなるにつれて、サポート範囲を明確に保ってください。サポート対象のデプロイメントターゲット、適切な SDK の宣言、ガードされた新機能、最低要件の環境で役立つ実装を揃えます。API や依存関係を追加するたびに、ビルドに含める前に同じ契約に適合することを確認してください。

出典