Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Предыстория и сразу к делу
В свое время мне понадобилось обернуть написанный модуль в библиотеку. Порядочно погуглив, я нашел кучу туториалов, суть которых - создается библиотека с одним-двумя .swift - файлами. У меня же был целый проект, да еще с подами (а ля Alamofire, Moya, EasyPeasy и др), и создание библиотеки именно этим и усложнялось, было непонятно как переносить поды, нужно ли их вообще переносить и как в целом правильно сбилдить такую библиотеку.
P.S. Данный туториал не претендует на полноту теории, скорее он из раздела "как сделать правильно и чтоб работало".
P.P.S. Статья будет написана как расширение обычных туториалов для случая использования в библиотеке Static Library - cocoapods, но ее можно использовать и как просто туториал для создания Static Library.
Тестировалось на xcode 12.4, swift 5
1. Билдим библиотеку
Итак, начинаем, cоздаем новый проект в xcode:
Выбираем Static Library, жмем Next,
библиотеку назовем StaticLibraryExample - рекомендую давать название без пробелов!
Получаем пустую библиотеку с одним автоматически созданным файлом StaticLibraryExample.swift:
Теперь мы можем использовать созданный файл(или удалить его), а также создать бесконечно своих файлов и добавить их в библиотеку.
Не забываем указывать модификаторы доступа public и open для классов свойств и функций!
Шрифты, локализацию Localizable.strings, картинки Assets не вносим в библиотеку, их добавим в клиентский проект отдельно!
Если же у нас имеется проект, который нужно сделать подмодулем(как было в моем случае), то берем все необходимые файлы и копируем их из этого проекта в нашу библиотеку:
Получаем что то наподобие(автоматический созданный файл StaticLibraryExample я удалил):
Теперь попробуем сбилдить нашу библиотеку (неважно на симуляторе или устройстве), нажимаем Ctrl+B. Если в вашей библиотеке нет cocoapods зависимостей то все компилируется успешно - билдим проект на устройстве и симуляторе и переходим к пункту 2 туториала.
Если же есть, то вы получите ошибку наподобие этой:
Окей, создаем новый Podfile с необходимыми подами, например мне нужны были следующие(какие поды дело несущественное):
platform :ios, '13.0'
target 'Static Library Example' do
pod 'Moya'
pod 'Alamofire'
pod 'Kingfisher'
pod 'EasyPeasy'
pod 'KeychainAccess'
pod 'SwiftPhoneNumberFormatter'
end
Устанавливаем поды - pod install. Открываем созданный workspace (на установке подов я не останавливаюсь).
Билдим библиотеку для симулятора и устройства, все должно быть успешно, если есть ошибки рекомендую в Targets -> StaticLibraryExample -> Build phases в разделе Library search paths удалить все, кроме $(inherited).
После того как все билдится успешно, файл библиотеки перестанет подсвечиваться красным, и, нажав на него правой кнопкой и выбрав Show In Finder, мы можем найти его на диске (один файл для iphoneos и второй для iphonesimulator):
В итоге на первом этапе для нас главное получить эти два файла библиотеки - один для айфона, второй - для симулятора.
2. Компилируем Universal Static Library
Под Universal Static Library имеется в виду библиотека (файл), который подходит и под устройство и под симулятор.
Сразу отмечу, что ее можно создать через терминал, такой способ есть во многих туториалах, однако создавать через агрегатор намного удобнее, к тому же если вы что что измените в библиотеке и вам необходимо будет ее пересобрать, то будет достаточно сбилдить агрегатор еще раз.
Добавляем новый таргет - Aggregator:
Жмем Next, назовем агрегатор UniversalLib_Aggregator, жмем Готово:
Далее добавим Run Script Phase - код, который будет выполняться при билде этого агрегатора:
Удаляем все что там написано:
И вставляем код, который я нашел на просторах интернета. Этот код универсален для любой вашей будущей библиотеки, только в переменную LIB_NAME необходимо вписывать имя проекта(библиотеки):
# 1: Declare variables
# Вписываем имя библиотеки:
LIB_NAME="StaticLibraryExample"
RESULT_DIR="libUniversal"
BUILD_DIR_SIMULATOR="Debug-iphonesimulator"
BUILD_DIR_DEVICE="Debug-iphoneos"
LIB_BINARY_NAME="lib$LIB_NAME.a"
LIB_BINARY_NAME_SIMULATOR="lib$LIB_NAME-simulator.a"
LIB_BINARY_NAME_DEVICE="lib$LIB_NAME-device.a"
SWIFTMODULE_DIR=$LIB_NAME".swiftmodule"
# 2: Билд
# Билдим для симулятора
xcodebuild -target $LIB_NAME -configuration ${CONFIGURATION} -sdk iphonesimulator -arch x86_64 BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"
# Билдим для устройства
xcodebuild -target $LIB_NAME ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"
# 3: Операции с бинарными файлами
# Переходим в билд директорию
cd $BUILD_DIR
# Удаляем результат предыдущей сборки
rm -rf $BUILD_DIR/$RESULT_DIR 2> /dev/null
# Создаем новую директорию для библиотеки
mkdir $RESULT_DIR
# Копируем двоичный файл симулятора
# в директорию библиотеки и переименовываем его
cp ./$BUILD_DIR_SIMULATOR/$LIB_BINARY_NAME ./$RESULT_DIR/$LIB_BINARY_NAME_SIMULATOR
# Копируем двоичный файл устройства
# в директорию библиотеки и переименовываем его
cp ./$BUILD_DIR_DEVICE/$LIB_BINARY_NAME ./$RESULT_DIR/$LIB_BINARY_NAME_DEVICE
# Создаем нашу universal библиотеку(второе название "fat")
lipo -create ./$RESULT_DIR/$LIB_BINARY_NAME_SIMULATOR ./$RESULT_DIR/$LIB_BINARY_NAME_DEVICE -output ./$RESULT_DIR/$LIB_BINARY_NAME
# Удаляем двоичные файлы симулятора и устройства:
rm ./$RESULT_DIR/$LIB_BINARY_NAME_SIMULATOR
rm ./$RESULT_DIR/$LIB_BINARY_NAME_DEVICE
# 4: Создаем .swiftmodule
#
# Создаем директорию
mkdir $RESULT_DIR/$SWIFTMODULE_DIR
# Копируем 'swiftmodule' симулятора в созданную директорию
cp -r $BUILD_DIR_SIMULATOR/$SWIFTMODULE_DIR $RESULT_DIR
# Копируем 'swiftmodule' устройства в созданную директорию
cp -r $BUILD_DIR_DEVICE/$SWIFTMODULE_DIR/* $RESULT_DIR/$SWIFTMODULE_DIR
# Удаляем билд директорию
rm -rf $PROJECT_DIR/build
Билдим наш агрегатор Ctrl+B:
После успешного билда открываем файл библиотеки:
Да, открывается наш старый файл библиотеки для устройства из папки Debug-iphoneos, нам нужно перейти на уровень вверх (например щелкнув два раза на Products):
Перейдя в Products, мы видим нашу Universal Library:
В итоге на этом этапе мы получили нашу Universal Library, все файлы которой находятся в папке libUniversal (Обратите внимание что имя универсальной библиотеки тоже задается в скрипте переменной RESULT_DIR).
3. Интегрируем Static Library в ClientApp
Под "ClientApp" имеется в виду любой проект
Итак, осталось самое простое - внедряем библиотеку в проект.
Создаем новый xcode проект, выбираем App, жмем Next, назовем проект ClientApp
Переносим нашу папку libUniversal в наш проект:
Далее добавляем файл библиотеки libStaticLibraryExample.a в проект:
Должно получиться так:
Далее нам нужно заполнить раздел Import Paths:
Переходим во ViewController.swift и импортируем нашу библиотеку:
Билдим проект Ctrl+B, если вы не используете cocoapods, то все должно сбилдиться и нашу библиотеку можно использовать! (переходите к пункту 4. Тестирование)
Если же используем поды то необходимо в ClientApp установить те же поды, что были у нас при компилировании библиотеки. Также я столкнулся с такой ошибкой:
Решается так: Чистим проект Product -> Clean Build Folder, затем открываем Terminal (необязательно по пути где наш проект), вставляем следующий код(чистим папку DerivedData):
rm -rf ~/Library/Developer/Xcode/DerivedData
Затем билдим проект снова, получаем закономерную ошибку, что наши поды не обнаружены:
Устанавливаем Pods - код для Podfile берем из Static Library (см. выше):
Открываем ClientApp.xcworkspace, и билдим проект - убеждаемся, что все успешно!
Не забудьте перенести шрифты(а также добавить данные о них в Info.plist), файлы локализации Localizable.strings, а также добавить картинки в Assets! Т.е. все те файлы которые вам не нужны в вашей библиотеке, но нужны для запуска ClientApp.
4. Тестируем
В моей библиотеке Static Library Example был такой класс:
public class TestViewController: UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
}
public func printLog() {
print("The Universal Library works!")
}
}
Как видите это простой класс для тестирования работоспособности библиотеки, который содержит в себе публичную функцию printLog().
Давайте вызовем эту функцию из нашего ClientApp:
Отлично! Библиотека работает, вы можете использовать классы, функции и свойства доступных файлов библиотеки!