Вступление
Столкнувшись с задачей создать фреймворк, первым делом я, как и полагается, порядочно погуглил. Однако, во всех туториалах, которые я встречал, создавали фреймворк без подов, и конечно было непонятно как работать в данном случае с cocoapods. Как переносить поды, нужно ли их вообще переносить и как в целом правильно сбилдить фреймворк, а затем его внедрить в другой проект.
Тестировалось на xcode 12.4, swift 5
Как создать статическую библиотеку Static Library с Cocoapods я писал здесь
1. Билдим фреймворк
Что ж, перейдем сразу к делу, открываем xcode, создаем новый проект, выбираем Framework, жмем далее, назовем проект FrameworkExample, тесты включать/не включать - на ваше усмотрение.
Я включил юнит-тесты, в итоге получили пустой проект-фреймворк:
Теперь мы можем создавать .swift-файлы и добавлять их в наш пока еще пустой фреймворк.
Не забываем указывать модификаторы доступа public и open для классов свойств и функций!
Шрифты, локализацию Localizable.strings, картинки Assets не вносим в библиотеку, их добавим в клиентский проект отдельно!
Если же у нас имеются файлы(например мы хотим из другого проекта перенести в фреймворк), которые нужно включить в фреймворк, то просто переносим(drag and drop) их в FrameworkExample:
Щелкнув на таргет FrameworkExamples -> General не забудьте настроить с какой версии доступен фреймворк и для каких устройств:
После этого билдим наш фреймворк Ctrl+B (два раза - для устройства и симулятора), и, если у вас нет cocoapods, то все должно сбилдится успешно Build Succeeded - переходим к окончанию пункта 1.
Если же есть подсы, то получим ошибку что они не найдены:
Итак, создаем Podfile в папке проекта, вставляем нужные подсы, не забываем указывать use_frameworks! :
platform :ios, '13.0'
target 'FrameworkExample' do
use_frameworks!
pod 'Moya'
pod 'Alamofire'
pod 'Kingfisher'
pod 'EasyPeasy'
pod 'KeychainAccess'
pod 'SwiftPhoneNumberFormatter'
end
Устанавливаем поды, открываем workspace, билдим Ctrl+B - для симулятора и устройства - должны получить Build Succeeded:
Находим папку Products, щелкаем правой кнопкой по framework-файлу, затем Show In Finder:
Попадаем либо в папку Debug-iphoneos, либо Debug-iphonesimulator в зависимости на какой product нажали, видим папку нашего фреймворка FrameworkExample.framework, а также подгруженные подсы.
Если перейти на уровень выше в папку Products, то увидим такую же папку и для устройства:
Таким образом, на этом этапе мы сбилдили фреймворк, в котором будут все файлы, помещенные в проект, сам фреймворк состоит из следующих файлов:
2. Компилируем Universal Framework
Под Universal Framework имеется в виду фреймворк, который подходит и под устройство и под симулятор. То есть, мы объединим фреймворки из папок Debug-iphoneos и Debug-iphonesimulator.
Для начала добавим новый target -> Aggregator:
Назовем его UniversalFramework, должно получиться так:
Далее добавим Run Script Phase - код, который будет выполняться при билде UniversalFramework'a и создавать нам объединенный фреймворк:
Вставляем следующий скрипт:
echo "project"
# 1) Открываем "${PROJECT_DIR}"
if [ "true" == ${ALREADYINVOKED:-false} ]
then
echo "RECURSION: Detected, stopping"
else
export ALREADYINVOKED="true"
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-iosuniversal
# 2) Переходим (либо создаем) папку с выходными файлами
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
# 3) Билдим для устройства и симулятора
echo "iphone"
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build
echo "iphonesim"
xcodebuild -target "${TARGET_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build
# 4) Копируем из папки Debug-iphoneos в папку universal
echo "universal"
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"
# 5) Копируем swift modules
echo "iphone simulator path"
SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/."
if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"
fi
# 6) С помощью lipo создаем universal framework
echo "lipo create"
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"
echo "end"
fi
Билдим UniversalFramework Ctrl+B,
Теперь выбрав правой кнопкой FrameworkExample.framework и нажав Show In Finder, переходим в папку с фреймворком, и, поднявшись на уровень выше в папку Products, видим созданную папку с нашим универсальным фреймворком:
В этой папке находятся все файлы нашего UniversalFramework. Переходим к следующему этапу!
3. Внедряем фреймворк в ClientApp
Под "ClientApp" имеется в виду любой проект
Cоздаем новый проект xcode -> new -> file -> project -> App
Назовем его ClientAppWithFramework:
Перенесем папку Debug-iosuniversal, т.е. наш универсальный фреймворк в папку с нашим проектом, затем подключим его в Build Phases -> Link Binary With Libraries:
Далее скачиваем pods в наш клиентский проект - Podfile можно взять из FrameworkExample:
platform :ios, '13.0'
target 'ClientAppWithFramework' do
use_frameworks!
pod 'Moya'
pod 'Alamofire'
pod 'Kingfisher'
pod 'EasyPeasy'
pod 'KeychainAccess'
pod 'SwiftPhoneNumberFormatter'
end
Установив pods, открываем workspace. Импортируем FrameworkExample во ViewController.swift:
Отлично, переходим к тестировке и к возможным ошибкам!
4. Тестируем и правим ошибки
Среди моих файлов фреймворка был файл для тестирования, который содержит публичную функцию, которая печатает нам The Framework works!:
public class TestViewController: UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
}
public func printLog() {
print("The Framework works!")
}
}
Попробуем вызвать эту функцию из ClientApp, добавляем две строчки:
import FrameworkExample
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let vc = TestViewController()
vc.printLog()
}
}
Выше я писал, что шрифты и картинки мы подгрузим отдельно, так вот, если у вас имеются шрифты, картинки, локализация, то самое время перенести их в ClientApp
Запускаем приложение, если у вас падает с ошибкой:
То необходимо на вкладке General сменить Do Not Embed на Embed & Sign:
Пытаемся запустить вновь, и снова возможна ошибка:
Здесь решение на вкладке Build Phases -> Validate Workspace сменить с No на Yes, сбилдить проект, ошибка должна стать Warning'oм (Магия!), затем измените Validate Workspace снова на No - ошибка и ворнинг должны исчезнуть:
Запускаем приложение, убеждаемся, что все работает корректно:
Отлично, фреймворк внедрен в другой проект и им можно пользоваться!