Вступление

Столкнувшись с задачей создать фреймворк, первым делом я, как и полагается, порядочно погуглил. Однако, во всех туториалах, которые я встречал, создавали фреймворк без подов, и конечно было непонятно как работать в данном случае с 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 - ошибка и ворнинг должны исчезнуть:

Запускаем приложение, убеждаемся, что все работает корректно:

Отлично, фреймворк внедрен в другой проект и им можно пользоваться!

Комментарии (0)