Чтобы поддерживать более старые версии macOS, собирайте приложение с SDK, содержащим новые API, задайте поддерживаемую минимальную версию целевой ОС и ограждайте новые вызовы проверками доступности с рабочими резервными вариантами. В этом руководстве небольшое нативное окно заметок продолжает работать в macOS 12, используя при этом представление из macOS 14.

Какие настройки совместимости должны согласовываться?

Для разработчика приложений Mac конкретная задача — создать одно приложение, которое запускается на самой старой поддерживаемой версии системы и использует новые возможности, когда они доступны. Для начала разделите три входных параметра: компилятор понимает исходный язык, SDK предоставляет объявления API, а целевая версия определяет самую старую предполагаемую среду выполнения. В справочнике Apple по настройкам сборки SDKROOT определён как SDK, заголовочные файлы и библиотеки которого участвуют в сборке. Справочник по настройкам сборки

Более новый SDK может использоваться вместе со старой целевой версией. Clang прямо документирует сборку с новейшим SDK при выборе более ранней минимальной версии macOS с помощью -mmacos-version-min=. При такой комбинации всё равно нужны проверки доступности для API, появившихся после минимальной версии. Расширения языка Clang

Выберите минимальную версию, которую поддерживает используемая версия Xcode. В таблице совместимости Apple отдельно указаны версии macOS, необходимые для запуска Xcode, и целевые версии, для которых он может собирать приложения. Если желаемая минимальная версия выходит за пределы поддерживаемого диапазона этой цепочки инструментов, выберите подходящую поддерживаемую цепочку или пересмотрите минимальную версию приложения до реализации резервных вариантов. SDK и системные требования

Для этого условного примера требования таковы: macOS 12 или новее, SDK с API macOS 14 и окно заметок с двумя состояниями: пустая коллекция и одна примерная заметка. Новое представление для пустого состояния необязательно; отображение заметки доступно и в базовой версии.

Задайте минимальную версию в обеих конфигурациях сборки

В Build Settings целевого объекта приложения задайте MACOSX_DEPLOYMENT_TARGET значение 12.0 для Debug и Release. Если вы управляете настройками с помощью файлов конфигурации, ниже приведён полный фрагмент конфигурации, а не программа:

MACOSX_DEPLOYMENT_TARGET = 12.0

На вкладке Info проекта назначьте файл конфигурации нужным конфигурациям. Затем проверьте итоговое значение для целевого объекта: Apple указывает, что настройки целевого объекта могут переопределять значения из назначенного файла конфигурации. Простого добавления файла в проект недостаточно, чтобы его настройки начали действовать. Добавление файла конфигурации сборки в проект

Как добавить новый API, не повышая минимальную версию приложения?

Проверка Swift if #available определяет доступность во время выполнения и сообщает компилятору, какие API разрешено использовать в этой ветви. Поместите резервную реализацию в ту же конструкцию, чтобы на старой системе тоже была полноценная реализация. Обязательный символ * охватывает платформы, не указанные явно; он не делает названный API macOS доступным в более ранних версиях. Поток управления — проверка доступности API

ContentUnavailableView от Apple доступен начиная с macOS 14 и подходит для отображения пустого содержимого. Здесь небольшая структура VStack обеспечивает аналогичное отображение в macOS 12 и 13. Обе ветви предлагают одно и то же следующее действие. ContentUnavailableView

Полноценное условное приложение SwiftUI

Создайте проект приложения macOS с именем CompatibilityNotes, выберите Swift и SwiftUI и задайте целевую версию 12.0. Используйте SDK с API macOS 14. Замените созданные шаблоном объявления приложения и представления содержимого одним файлом ниже, убедившись, что в целевом объекте осталось только одно объявление @main. Ресурсы, пакеты и входные файлы не требуются.

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. Граница совместимости охватывает новое представление. Если в базовой версии отсутствует целый новый фреймворк, отдельно оцените зависимость от него: защита одного вызова не решает все требования загрузки. В руководстве Apple по слабой связи объясняется разница между допущением отсутствующего символа и фактическим использованием этого символа. Фреймворки и слабая связь

Используйте синтаксис проверки доступности, соответствующий исходному языку:

Исходный язык Условие доступности во время выполнения
Swift if #available(macOS 14.0, *)
Objective-C или Objective-C++ if (@available(macOS 14.0, *))
C или C++ с поддержкой платформ Apple в Clang 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. Используйте эти условия компиляции в соответствии с их назначением, а при необходимости проверяйте доступность во время выполнения. Операторы — блок условной компиляции

Как стабильность ABI Swift и ограничения библиотеки C++ меняют решение?

Стабильность ABI Swift не означает, что каждая новая возможность стандартной библиотеки или среды выполнения доступна в старых системах. В объяснении Swift различаются режим исходного языка и поддержка во время выполнения: изменение режима языка — это выбор времени компиляции, тогда как возможности, требующие новых функций среды выполнения, могут иметь требования к версии ОС. Учитывайте доступность конкретной используемой возможности. Развитие Swift на платформах Apple после стабилизации ABI

В C++ тоже следует различать поддержку компилятора и библиотеки. Apple поставляет Apple Clang в составе Xcode, а среду выполнения libc++ — вместе с SDK и операционными системами. В таблице возможностей указаны требования, которые могут зависеть от общей библиотеки, поставляемой с ОС. Выбор -std=c++17 или -std=c++20 не устанавливает функции этой среды выполнения на Mac пользователя. Поддержка языка C++

Полезный пример — преобразование чисел: Apple указывает macOS 10.15 как минимальную версию для целочисленных std::to_chars и std::from_chars, но macOS 13.3 — для их перегрузок с числами с плавающей точкой. Поэтому приложению для macOS 12 нужна совместимая реализация преобразования, если иначе ему потребуются эти перегрузки для чисел с плавающей точкой. Проверяйте конкретную перегрузку, а не только заголовочный файл или версию стандарта C++. Поддержка языка C++

Не считайте, что достаточно обернуть каждый вызов библиотеки C++ в __builtin_available. Атрибуты доступности Clang включают форму strict, запрещающую использование при сборке для более ранней платформы, а libc++ использует разметку доступности, чтобы на этапе компиляции диагностировать отсутствие поддержки в среде выполнения. Если выбранные заголовки запрещают операцию, используйте совместимую с базовой версией реализацию или повысьте минимальную версию. Атрибуты в Clang — availability, Документация поставщика — разметка доступности

Сохраняйте согласованность конфигурации ABI библиотеки во всех зависимостях. LLVM описывает конкретные гарантии ABI и их ограничения, включая то, что код, скомпилированный с более новыми заголовками libc++, может не работать со старой библиотекой. Флаги конфигурации, меняющие ABI, — это средства управления поставщика. Их изменение в коде приложения не заменяет совместимую среду выполнения. Гарантии ABI libc++

Как диагностировать и проверить готовую сборку?

Выбирайте направление дальнейшей проверки по этапу, на котором произошёл сбой. Эта памятка следует описанным выше границам компилятора, доступности и среды выполнения; это последовательность диагностики, а не утверждение, что пример прошёл тестирование.

Симптом Сначала проверьте Практический следующий шаг
Имя нового API неизвестно Объявления в SDK Выберите SDK, содержащий API.
API новее целевой версии Неограждённое использование Добавьте поддерживаемую проверку доступности и резервную реализацию.
Операция библиотеки C++ по-прежнему отклоняется Требования конкретной перегрузки и библиотеки Используйте совместимую реализацию или пересмотрите минимальную версию.
Приложение завершается до появления окна Требования загрузки и зависимости Изучите сообщение об отсутствующей библиотеке или символе.
Не работает только новая возможность Работа за пределами защищённой ветви Проверьте создание, инициализацию и вызовы вспомогательных элементов.
Сборка работает только на одном семействе процессоров Архитектуры исполняемого файла и зависимостей Соберите и проверьте необходимые архитектурные срезы.

Поддержка архитектур — дополнительное требование. В руководстве Apple по универсальным бинарным файлам рассматриваются arm64 и x86_64, включая скомпилированные библиотеки и фреймворки. Проверяйте зависимости наряду с исполняемым файлом приложения: успешная сборка для вашего Mac подтверждает совместимость только этой сборки. Сборка универсального бинарного файла macOS

Для этого примера соберите конфигурацию Release и проверьте полученное приложение на macOS 12, macOS 13 и macOS 14 или новее на поддерживаемом оборудовании. В каждой системе запустите приложение с пустой коллекцией, добавьте примерную заметку и сбросьте состояние. В старых версиях все действия должны работать через резервную реализацию. Проверьте каждую архитектуру, для которой планируете распространять приложение.

По мере развития приложения сохраняйте чёткую границу поддержки: поддерживаемая целевая версия, объявления из подходящего SDK, защита новой функциональности и полезная базовая реализация. Каждый новый API или зависимость должны соответствовать этим требованиям до включения в сборку.

Источники