Можно ли использовать Objective-C код через Swift Package Manager (SPM)?

Ответ

Да, но с определенными условиями. Swift Package Manager изначально ориентирован на Swift, но поддерживает Objective-C через модули (modulemaps).

Основной способ — использование файла module.modulemap:

  1. Создайте структуру папок include/ в директории с исходным кодом (Sources/ИмяЦели).
  2. Поместите заголовочные файлы (.h) в include/.
  3. Создайте файл include/module.modulemap, который описывает модуль.

Пример структуры и кода:

MyObjCPackage/
├── Package.swift
└── Sources/
    └── MyObjCLib/
        ├── MyObjCLib.c
        └── include/
            ├── MyObjCLib.h
            └── module.modulemap

Содержимое module.modulemap:

module MyObjCLib {
    header "MyObjCLib.h"
    export *
}

Настройка в Package.swift:

let package = Package(
    name: "MyObjCPackage",
    targets: [
        .target(
            name: "MyObjCLib",
            publicHeadersPath: "include" // Указывает путь к заголовкам
        )
    ]
)

Ограничения и рекомендации:

  • SPM не поддерживает смешанные цели (файлы .m и .swift в одной цели). Цель должна быть либо чисто Swift, либо чисто C/Objective-C.
  • Для удобного использования Objective-C кода в Swift-проектах часто создают чистую Objective-C цель (как библиотеку) и затем зависимую от неё Swift-цель.
  • Альтернатива для сложных библиотек — дистрибуция в виде .xcframework, который SPM может использовать.

Ответ 18+ 🔞

Ну, слушай, вот сидишь ты такой, пишешь на Swift, всё красиво, пакетный менеджер под боком. И тут тебе вдруг понадобилось впихнуть в проект какой-нибудь старый, добрый, вонючий Objective-C код. Ну, библиотеку какую-нибудь, которую ещё твой тимлид на коленке писал, когда Swift в проекте Apple только в виде грязной мысли был.

И думаешь: "Блядь, SPM же для Swift, он с Objective-C не дружит!" А вот и нихуя! Дружит, сука! Но не просто так, а через эти ваши модули (modulemaps). Это как взять этого упрямого осла Objective-C, надеть на него намордник в виде module.modulemap и только тогда он в Swift-табун впишется.

Как это, блядь, работает? Через этот самый module.modulemap:

  1. Ты в папке с исходниками (Sources/ТвояЦель) создаёшь структуру, как у параноика. Папка include/ — святая святых.
  2. Туда кидаешь все свои .h заголовки, как драгоценности в сейф.
  3. А потом создаёшь в той же include/ файлик module.modulemap. Это, сука, инструкция для SPM, как этот обшарпанный Objective-C код превратить в приличный модуль.

Вот смотри, как это выглядит, ёпта:

MyObjCPackage/
├── Package.swift
└── Sources/
    └── MyObjCLib/          <-- Цель чисто под Objective-C
        ├── MyObjCLib.m     <-- Сам код, который пахнет стариной
        └── include/        <-- Волшебная папка
            ├── MyObjCLib.h
            └── module.modulemap <-- Вот он, ключик!

А в этом самом module.modulemap пишешь простыню:

module MyObjCLib {
    header "MyObjCLib.h"  // Вот этот хедер — лицо твоего модуля
    export *              // И всё, что в нём есть, тащи на выход
}

Ну и в Package.swift не забудь ткнуть пальцем, где эти хедеры искать:

let package = Package(
    name: "MyObjCPackage",
    targets: [
        .target(
            name: "MyObjCLib",
            publicHeadersPath: "include" // Смотри сюда, блядь, заголовки тут!
        )
    ]
)

Но есть, конечно, подводные ебеня, куда ж без них:

  • SPM — максималист, блядь. Он не терпит смешанного дерьма в одной цели. Либо всё Swift, либо всё C/Objective-C. Файлы .m и .swift вместе — это пиздец и ошибка компиляции.
  • Поэтому мудрый путь — сделать отдельную, чистую Objective-C цель (библиотеку), а потом уже твой Swift-код будет на неё с придыханием ссылаться как на зависимость.
  • А если твоя библиотека — это такой монстр, с кучей настроек и файлов, то проще, наверное, запаковать её в .xcframework и скормить уже SPM'у в таком, готовом виде. Меньше мороки, хотя и не так изящно.

Вот так вот, не так страшен чёрт, как его малюют. Главное — правильно надеть на него намордник в виде modulemap.