Предыстория и сразу к делу

В свое время мне понадобилось обернуть написанный модуль в библиотеку. Порядочно погуглив, я нашел кучу туториалов, суть которых - создается библиотека с одним-двумя .swift - файлами. У меня же был целый проект, да еще с подами (а ля Alamofire, Moya, EasyPeasy и др), и создание библиотеки именно этим и усложнялось, было непонятно как переносить поды, нужно ли их вообще переносить и как в целом правильно сбилдить такую библиотеку.

P.S. Данный туториал не претендует на полноту теории, скорее он из раздела "как сделать правильно и чтоб работало".

P.P.S. Статья будет написана как расширение обычных туториалов для случая использования в библиотеке Static Library - cocoapods, но ее можно использовать и как просто туториал для создания Static Library.

Как создать фреймворк Universal Framework с использованием Cocoapods я писал здесь

Тестировалось на 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:

Отлично! Библиотека работает, вы можете использовать классы, функции и свойства доступных файлов библиотеки!

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


  1. house2008
    01.11.2021 08:54

    Дак выбрали бы вместо static library просто framework, с ним легче работать и его проще интегрировать в другие проекты. Ну или совсем чтобы по фэншую сделать xcframework.


    1. joker_in_the_pack Автор
      01.11.2021 09:58

      Задача была сделать библиотеку, про фреймворк будет отдельная статья


      1. house2008
        01.11.2021 10:13

        Ок, хорошо) Просто на Swift никто не собирает статичные либы, это удел с/с++ кода.


        1. joker_in_the_pack Автор
          01.11.2021 10:29

          у нас заказчик попросил и либу и фреймворк почему то = )


          1. house2008
            01.11.2021 10:44

            ну да, у нас тоже с заказчиками не соскучишься ))


      1. pingwinator
        01.11.2021 13:56

        я надеюсь будет про xcframework, а не просто фремворк с жирной либой


        1. joker_in_the_pack Автор
          01.11.2021 17:46

          Планировал сначала framework+universal, а потом framework+xcframework


  1. kotleni
    01.11.2021 10:01

    Спасибо за статью, было очень интересно.