Я пытаюсь создать статическую библиотеку, содержащую код Swift и C и предоставляющую только интерфейс Swift. Таким образом, сторона кода C в идеале не должна подвергаться воздействию внешнего мира. Кажется, я не могу понять, как это сделать, не вызывая предупреждений или ошибок.
Для упрощения я попытался настроить минимальную статическую библиотеку Swift+C с этими файлами:
Test.swift
class Test {
func test() -> CInt {
return testFromC()
}
}
Тест.с
#include "Test.h"
int testFromC(void) {
return 0xdeadbeef;
}
Тест.ч
int testFromC(void);
Мне нужна статическая библиотека, которая предоставляет класс Swift Test, но не функцию C testFromC(). Я могу жить с тем, что это разоблачается, если нет альтернативы.
Что я пробовал:
Добавьте соединительный заголовок. Это почти работает, но а) многие люди говорят, что это не поддерживается или не рекомендуется, б) он предоставляет testFromC() импортеру модуля и в) при некоторых обстоятельствах он выдает предупреждения о неявном импорте связующего заголовка (что верно, но я хочу специально НЕ делать этого).
Добавьте карту модуля. Я не могу понять, как это сделать. Никакие онлайн-примеры, кажется, не работают для меня, и если кто-нибудь знает, как это сделать правильно, я хотел бы увидеть пример для этих трех файлов.
Добавить приватную карту модулей - я понимаю, что это то, что мне нужно, если я хочу избежать раскрытия функций C, но у меня с этим было еще меньше успеха.
Кто-нибудь может собрать точные шаги, чтобы сделать этот тривиальный пример сборки?
Вы действительно можете создать карту частного модуля, чтобы символы C не экспортировались, см. https://clang.llvm.org/docs/Modules.html#private-module-map-files.
Заголовок моста не будет работать, потому что вы хотите создать структуру, а не приложение.
Конечно, есть несколько способов настроить проект, одним из которых будет: создать проект с соответствующими настройками карты модулей в XCode.
.modulemap файл может выглядеть примерно так:
framework module SwiftOnlyAPI {
umbrella header "SwiftOnlyAPI.h"
export *
module * { export * }
}
framework module SwiftOnlyAPI_Private {
header "Test.h"
}
Ваш Test.swift скорее всего будет выглядеть примерно так:
import SwiftOnlyAPI_Private
public class Test {
public init() {
//perform some initializations
}
public func test() -> CInt {
return testFromC()
}
}
Обратите внимание на несколько небольших изменений: импорт SwiftOnlyAPI_Private, это необходимо для функций C, которые не экспортируются, но, конечно, вызываются локально внутри фреймворка. Классы и экспортированные методы должны быть общедоступными, чтобы быть доступными из-за пределов фреймворка.
Инструкции
Framework
, выберите язык Swift
, назовите его SwiftOnlyAPI
SwiftOnlyAPI
Test.h
в навигаторе проекта и переключите целевое членство в инспекторе с Project
на Private
SwiftOnlyAPI
в навигаторе проекта, выберите цель SwiftOnlyAPI
Build Settings
и для Mach-O Type
выберите: Static Library
Build Settings
: для Module Map File
введите: SwiftOnlyAPI/SwiftOnlyAPI.modulemap
SwiftOnlyAPI
в проекте SwiftOnlyAPI
, выберите New File...
в контекстном меню и выберите шаблон Empty
в разделе Other
и назовите его SwiftOnlyAPI.modulemap
(снимите флажок целевого членства)Тест дает желаемый результат, как показано ниже для альтернативного решения для нескольких целей.
Статическая библиотека
Фреймворки на самом деле представляют собой либо динамические, либо статические библиотеки (в зависимости от настройки) с дополнительными связанными файлами. Удобно для пользователей фреймворка.
Вы можете проверить это самостоятельно с помощью команды file:
Альтернатива
Если вы хотите, чтобы часть C была отдельной (например, для других потребителей), вы также можете разделить ее на два подпроекта в рабочей области Xcode, один для статической библиотеки C и один для статической платформы на основе Swift:
SwiftCWorkspace
Library
, выберите в разделе Framework None (Plain C/C++ Library)
, назовите его MyCLib
и выберите тип Static
Add to
и Group
: SwiftCWorkspace
MyCLib
в навигаторе проекта, выберите New File...
в контекстном меню и выберите шаблон файла C, назовите его TestFramework
, выберите язык Swift
, назовите его SwiftOnlyAPI
Add to
и Group
: SwiftCWorkspace
SwiftOnlyAPI
в проекте SwiftOnlyAPI
, выберите New File...
в контекстном меню и выберите шаблон Swift
и замените его своим контентомSwiftOnlyAPI
в навигаторе проекта, выберите цель SwiftOnlyAPI
Build Settings
и для Mach-O Type
выберите: Static Library
Build Settings
: для Module Map File
введите: SwiftOnlyAPI/SwiftOnlyAPI.modulemap
General
и в разделе Frameworks and Library
добавьте libMyCLib.a
SwiftOnlyAPI
в проекте SwiftOnlyAPI
, выберите New Group...
в контекстном меню и назовите ее MyCLib
Test.h
из MyCLib
проекта в папку MyCLib
в SwiftOnlyAPI
, снимите флажок Copy items if needed
Test.h
переключите целевое членство в инспекторе с Project
на Private
SwiftOnlyAPI
в проекте SwiftOnlyAPI
, выберите New File...
в контекстном меню и выберите шаблон Empty
в разделе Other
и назовите его SwiftOnlyAPI.modulemap
(снимите флажок целевого членства)Ноты
Если Xcode отображает сообщения об ошибках, даже если вы все настроили правильно, иногда необходимо выйти из Xcode после задач настройки проекта, удалить папку ~/Library/Developer/Xcode/DerivedData, а затем перезапустить сборку.
Контрольная работа
import SwiftOnlyAPI
...
let result = SwiftOnlyAPI.Test()
print(result.test())
Вы можете протестировать это в отдельном приложении с помощью фреймворка SwiftOnlyAPI: будет выведен правильный результат.
Также функция C testFromC является частной и недоступной извне, как хотелось бы, см. снимок экрана:
Важным моментом является структура файла .modulemap и настройка приватного заголовочного файла. Я добавил к ответу вариант, в котором все происходит в одном проекте Xcode. Фреймворки на самом деле представляют собой либо динамические, либо статические библиотеки с дополнительными связанными файлами. Удобно для потребителей фреймворка, см. обновленный ответ.
Вы уверены, что? Я вижу много ссылок, в которых говорится, что не существует такого понятия, как «статическая структура». Фреймворки являются динамическими, а статические библиотеки — это просто файлы .a. Помещение статического .a внутри фреймворка на самом деле не должно поддерживаться? Например, если вы поместите ресурс в эту «статическую структуру», будет ли он действительно включен в приложение или просто будет проигнорирован?
Да, я уверен, что есть динамические и статические фреймворки. Соответствующая настройка — Mach-O Type: либо Static Library, либо Dynamic Library. Статическая библиотека — это ar архив. Вы можете проверить это сами. Смотрите скриншот команды file в ответе, вместо Mach-O 64-bit dynamically linked shared library печатает current ar archive random library. В терминале вы также можете запустить команду ar -t SwiftOnlyAPI для статического фреймворка, чтобы вывести содержимое этого ar архива.
Я не уверен, что правильно понял вопрос о ресурсах. При статической компоновке код помещается в окончательный исполняемый файл. Фактическая структура не будет встроена в виде папки в пакет приложений. Вы можете включать ресурсы отдельно, например, в само приложение или в отдельный пакет. Но возможно я не правильно понял вопрос. Хотя в любом случае звучит как отдельная тема.
Вы сказали: «Фреймворки на самом деле представляют собой либо динамические, либо статические библиотеки с дополнительными связанными файлами», а я говорю, что не думаю, что у вас действительно могут быть статические фреймворки со связанными файлами. Таким образом, в целом кажется неправильным подход, чтобы попытаться создать "статическую структуру" здесь, это должна быть обычная статическая библиотека, а не какая-либо структура.
Ах хорошо. Например, статические фреймворки также имеют в комплекте info.plist, который содержит, например, информацию об используемом компиляторе. Произвольные отдельные файлы ресурсов (например, изображения) действительно нельзя использовать, как указано выше (ни при использовании статической структуры, ни при использовании статической библиотеки).
Я не думаю, что это «статическая структура». Это просто статическая библиотека, которая находится внутри папки, которая выглядит как фреймворк, но на самом деле таковой не является. Здесь правильно было бы собрать статическую библиотеку, а не фреймворк. Есть ли способ сделать это?
Хм, я бы даже сказал, что это было определение «статической структуры» в течение многих лет. Следует признать, что этот термин, вероятно, был придуман за пределами Apple, и первоначальное значение фреймворка относилось к объединению динамической библиотеки с информацией и ресурсами. Но он широко используется, например. если вы загрузите версии Firebase за последние годы, такие как github.com/firebase/firebase-ios-sdk/releases/download/6.19.0/… и примените file FirebaseCrashlytics.framework/FirebaseCrashlytics, вы получите current ar archive. Другим очень ярким примером является вариант статической привязки CocoaPods.
Ну, вот в чем дело: это слово для чего-то, что Apple не создавала и не поддерживала, и оно не имеет особого смысла. Пытаясь найти это, Apple кажется довольно непреклонной в том, что не существует такого понятия, как «статическая структура», и поэтому я бы предпочел не пытаться ее создать, а использовать то, что на самом деле поддерживается.
Похоже, что это может сработать, но кажется слишком сложным, поскольку включает в себя несколько целей. Кроме того, в идеале я хочу, чтобы в качестве продукта была одна статическая библиотека, а не фреймворк.