Cocoapod как XCFramework с зависимостями

Cocoapod как XCFramework с зависимостями

18 февраля 2023 г.

Эта статья будет полезна тем, кто изо всех сил пытается создать iOS-фреймворк как услугу для клиентов. Я знаю, что в Интернете по-прежнему не хватает информации, и Apple не была так очевидна в отношении того, как создавать и использовать фреймворки iOS, особенно если этот фреймворк должен иметь зависимости. Возможно, потому что сама Apple не рекомендует использовать зависимости во фреймворках, а может быть, это просто довольно редкий случай, который мы здесь рассмотрим.

Если вы являетесь экспертом по рок-звездам и этот вопрос для вас тривиален, пожалуйста, добавьте несколько комментариев к этой статье, возможно, у вас есть дополнительная информация, которую нам следует знать — Очень ценю, по крайней мере, здесь мы могли бы собрать полезные руководства для всех, кто страдает (как и я некоторое время назад), чтобы построить и поддерживать структуру с зависимостями и отправить ее как кокоапод. Поехали.

XCFramework

Давайте определим нашу цель более конкретно. Представьте себе историю, в которой вы iOS-инженер и у вас есть клиент. Этот клиент пришел к вам и попросил создать iOS-фреймворк, который должен делать почти все (представьте себе что-то вроде сложного решения, например, онлайн-банка или чего-то еще). Исходный код, естественно, должен быть закрытым и наружу выставлены только некоторые публичные методы и все работает в симуляторах и реальных устройствах. В этот момент вы бы сказали, что наша структура будет XCFramework, и вы правы.

XCFramework bundle

Для тех, кто не знал, что это такое, взгляните на примечание из документации Apple:

<цитата>

Пакет XCFramework, или артефакт, представляет собой двоичный пакет, созданный Xcode, который включает в себя платформы и библиотеки, необходимые для сборки для нескольких платформ (iOS, macOS, tvOS и watchOS), включая сборки симулятора. . Фреймворки могут быть статическими или динамическими, а также включать заголовки.

Итак, нашим конечным продуктом будет XCFramework, и его нужно установить как кокоапод.

Но как всегда есть нюанс: этот продукт нужно сделать как можно скорее. Вы как разработчик, конечно, можете написать все сами, но как долго вы сможете это делать? У вас мало времени. Конечно, нужно найти готовые решения. Это означает, что наша структура будет иметь некоторые зависимости.

Ваш Cocopod как XCFramework

Прежде чем углубиться в процесс, давайте подготовим список того, как мы собираемся тестировать наш XCFramework Cocopod.

  1. С зависимостями с открытым исходным кодом
  2. Ваш XCFramework > 100 МБ
  3. XCFramework с зависимостями XCFrameworks
  4. С зависимостями, которые используют XCFrameworks внутри
  5. XCFramework с зависимостью как XCFrameworks от специального исходного пути

Давайте пройдемся по всем этим пунктам и протестируем все эти кейсы вместе со мной, подробно описывая прогресс.

1. С зависимостями с открытым исходным кодом

Давайте начнем с простого и добавим в наш проект несколько зависимостей с открытым исходным кодом. Если вы создали свой фреймворк и выполнили pod init, у вас будет этот Podfile. Откройте его и давайте добавим некоторые зависимости.

platform :ios, '14.0'

target 'YourFramework' do
  use_frameworks!

  pod 'CropViewController'
  pod 'Kingfisher'

end

Как видите, я не стал добавлять какие-то глупые абстрактные поддельные какаоподы, потому что этот эксперимент был бы просто бесполезен. Чтобы быть более реалистичным, я добавил существующие (выбранные случайным образом) кокоаподы, которые помогут вам отладить самостоятельно.

Кроме того, я дам вам несколько советов, как избежать некоторых проблем в будущем. Итак, ловите первого:

<цитата>

Совет. Не забудьте заблокировать версии всех зависимостей.

platform :ios, '14.0'

target 'YourFramework' do
  use_frameworks!

  pod 'CropViewController', '2.6.1'
  pod 'Kingfisher', '7.5.0'

end

Блокировка версий защитит вас от неожиданных сбоев на стороне клиента, поскольку клиенты могут использовать одни и те же зависимости. Даже минимальные различия между версиями могут привести к сбоям.

Теперь пришло время построить наш XCFramework, и для этого мы будем использовать 3 скрипта:

  1. Архивация для симуляторов
  2. Архивация для iPhone
  3. Объединение двух артефактов в один пакет XCFramework

# 1
xcodebuild archive 
-workspace MyFramework.xcworkspace 
-scheme MyFramework 
-configuration Release 
-sdk iphoneos 
-archivePath archives/ios_devices.xcarchive 
BUILD_LIBRARY_FOR_DISTRIBUTION=YES 
SKIP_INSTALL=NO 

# 2
xcodebuild archive 
-workspace MyFramework.xcworkspace 
-scheme MyFramework 
-configuration Debug 
-sdk iphonesimulator 
-archivePath archives/ios_simulators.xcarchive 
BUILD_LIBRARY_FOR_DISTRIBUTION=YES 
SKIP_INSTALL=NO 

# 3
xcodebuild 
-create-xcframework 
-framework archives/ios_devices.xcarchive/Products/Library/Frameworks/MyFramework.framework 
-framework archives/ios_simulators.xcarchive/Products/Library/Frameworks/MyFramework.framework 
-output MyFramework.xcframework

В моем случае этот скрипт я назвал build.sh и сохранил его в проекте. Вы можете запустить этот сценарий, чтобы получить свежеиспеченный XCFramework, который мы собираемся распространять.

2. Ваш XCFramework > 100 МБ

Давайте немного усложним задачу (мы не из тех, кто выбирает легкий путь). Наша небольшая сложность заключается в его размере, который превышает 100 МБ. Это означает, что тяжелый фреймворк мы не можем просто загрузить на GitHub. Вместо того, чтобы использовать репозиторий GitHub, нам нужно куда-то загрузить наш фреймворк. Это может быть любое общественное хранилище. Вы можете использовать хранилище больших файлов Github или (как и я) Облачное хранилище Google.

Итак, прежде чем настраивать инструкции podspec, заархивируйте XCFramework и загрузите его в хранилище.

Preparing framework for cloud storage

<цитата>

Совет. Переименуйте ZIP-файл фреймворка, используя формат: <Framework_Name><Version>.zip

Чрезвычайно полезно иметь версию в zip-файле вашего фреймворка, потому что это позволит вашим клиентам установить определенную версию вашего фреймворка.

Чтобы распространять фреймворк какcocopod, нам нужно подготовить файл podspec:

Pod::Spec.new do |s|
  s.name          = 'MyFramework'
  s.version       = '1.0.0'
  s.summary       = 'A short description of MyFramework'
  s.homepage      = 'http://maxkalik.com'
  s.license       = { :type => 'MIT' }
  s.author        = { 'MyFramework' => 'maxkalik@gmail.com' }
  s.source        = { :http => 'https://maxkalik.com/fameworks/MyFramework-v1.0.0.zip' }
  s.swift_version = '5.0'
  s.ios.deployment_target = '14.0'

  s.dependency 'CropViewController', '~> 2.6.1'
  s.dependency 'Kingfisher', '~> 2.6.1'

  s.vendored_frameworks = 'MyFramework.xcframework'
end

Как видите, наш файл podspec довольно прост, но с некоторыми отличиями  — если вы попытаетесь сравнить его с другими файлами, которые не используют зависимости и не такие тяжелые, как наш.

* Источник. Нам нужно показать, где именно наш zip-файл использует :http => ‘path_to_public_storage’ * Зависимости. Сначала перечислите все свои зависимости и заблокируйте их версии. * Продаваемые фреймворки. Нам нужно указать здесь путь к нашему окончательному фреймворку. Он поддерживает пакеты .framework и .xcframework.

Теперь вы можете опубликовать свой podspec и протестировать его. Если вы хотите протестировать свой podspec в своем собственном репозитории github, у вас должен быть небольшой обходной путь:

  1. Сделайте репозиторий с вашим podspec частным.
  2. Откройте строковое представление вашего подспецификации непосредственно в GitHub и скопируйте ссылку из браузера. Почему? потому что при каждом обновлении токен в этой ссылке всегда будет уникальным, иначе вы не сможете протестировать последнюю версию своего подспецификации.
  3. Используйте скопированную ссылку в своем тестовом подфайле и пометьте этот тип источника как podspec:

platform :ios, '15.0'

target 'ExampleProjectPods' do
  use_frameworks!

  pod 'MyFramework', :podspec => 'https://raw.githubusercontent.com/maxkalik/myframework-podspec/master/MyFramework.podspec?token=<TOKEN>'

end

Это должно сработать, так что давайте двигаться дальше.

3. XCFramework с зависимостями XCFrameworks

На самом деле, этот случай более очевиден, чем вы думаете. Все, что нам нужно сделать, это добавить эту зависимость в подфайл вашего фреймворка и podspec. Я тестировал этот случай с помощью Intercom, потому что он сам XCFramework.

Framework Pods

Следовательно, ваш подфайл должен быть обновлен только добавлением зависимости Intercom:

Pod::Spec.new do |s|
  s.name          = 'MyFramework'
  s.version       = '1.0.1'
  s.summary       = 'A short description of MyFramework'
  s.homepage      = 'http://maxkalik.com'
  s.license       = { :type => 'MIT' }
  s.author        = { 'MyFramework' => 'maxkalik@gmail.com' }
  s.source        = { :http => 'https://maxkalik.com/fameworks/MyFramework-v1.0.1.zip' }
  s.swift_version = '5.0'
  s.ios.deployment_target = '14.0'

  s.dependency 'CropViewController', '~> 2.6.1'
  s.dependency 'Kingfisher', '~> 2.6.1'
  s.dependency 'Intercom', '~> 14.0.6'

  s.vendored_frameworks = 'MyFramework.xcframework'
end

Как видите, для включения Intercom XCFramework необязательно использовать часть сторонних фреймворков, потому что это просто зависимость.

4. С зависимостями, использующими XCFrameworks внутри

Смотрите раздел 3. Абсолютно тот же процесс.

5. XCFramework с зависимостью как XCFrameworks от специального исходного пути

Возможно, мне не нужно было писать статью выше, кроме этой части, потому что, когда я столкнулся с этим случаем, я не мог найти правильного решения, которое могло бы пошагово описать, как это сделать. В первую очередь эту статью я пишу для себя, а затем для вас, кто разрабатывает фреймворки, как и я.

Хорошо. Хватит жаловаться, давайте обновим наш подфайл фреймворка.

platform :ios, '14.0'

source 'https://cdn.cocoapods.org/'
source 'https://github.com/passbase/cocoapods-specs.git'
source 'https://github.com/passbase/microblink-cocoapods-specs.git'

target 'YourFramework' do
  use_frameworks!

  pod 'CropViewController', '2.6.1'
  pod 'Kingfisher', '7.5.0'
  pod 'Intercom', '14.0.6'
  pod 'Passbase', '~> 2.7.0'

end

Я добавил новый модуль под названием Passbase. Этот модуль любопытен, потому что помимо добавления зависимости нам нужно добавить исходные пути туда, где хранятся podspecs. Но как насчет подспека? Вы можете предложить просто добавить эту строку (как обычно) в наш список зависимостей:

s.dependency ‘Passbase’, ‘~> 2,7,0 дюйма

Когда ваш клиент попытается установить вам новую версию фреймворка, процесс завершится ошибкой:

Unable to find a specification error

Почему? Потому что, очевидно, у нас есть зависимость (Passbase), но исходный путь не распознан. Конечно, будет круто просто обновить наш подфайл скажем так:

s.dependency 'Passbase', '~> https://github.com/passbase/cocoapods-specs.git'

Но это невозможно. Инструкция Cocoapod этого не позволяет. Я имею в виду, что мы не можем просто указать конкретную зависимость на конкретный исходный путь.

Временное решение:

Да, нам нужно сделать некоторые странные манипуляции, чтобы заставить его работать. Итак, давайте посмотрим на папку pods вашего фреймворка и увидим, что на самом деле есть 2 XCFramewok: Passbase.xcframework и Microblink.xcframework

Framework's pod folder in Xcode

Нам нужно скопировать эти два XCFramework и соединить их вместе с вашим окончательным XCFramework. Когда вы соберете их все вместе, мы сможем их изготовить и застегнуть молнию.

Zip all vendored xcframeworks

Кроме того, нам нужно обновить часть podspec vendored_frameworks:

s.vendored_frameworks = 'MyFramework.xcframework',
                          'Microblink.xcframework',
                          'Passbase.xcframework'

На самом деле, мы собираемся отправить zip-файл с тремя фреймворками внутри. Да, это не идеальное решение, но оно работает. Я лично хотел бы видеть возможность включения каких-то конкретных источников в файл podspec, но это то, что есть.

Возможные проблемы (вместо заключения)

Вместо скучного вывода, который никому не интересен, я решил написать список потенциальных проблем и способы их решения, собирая xcframework какcoapod. Я думаю, что это будет более полезно.

Проблема 1: Отказано в доступе к build.sh

MyFramework ./build.sh
zsh: permission denied: ./build.sh

Решение. Нам нужно изменить права доступа с помощью chmod со специальным флагом +x, чтобы сделать файл исполняемым.

Проблема 2: Xcode 14 требуется выбранная команда разработчиков для пакетов Pod

MyFramework/Pods/Pods.xcodeproj: error: Signing for "ThirdPartyPod-ThirdPartyPodBundle" requires a development team. Select a development team in the Signing & Capabilities editor. (in target 'ThirdPartyPod-ThirdPartyPodBundle' from project 'Pods')

Из Xcode 14 вы можете увидеть эту ошибку, и вам нужно выбрать свою команду разработчиков. Это не такая уж большая проблема, пока у вас не будет много пакетов.

Решение:

Этот небольшой скрипт поможет нам решить эту проблему:

post_install do |installer|
  installer.generated_projects.each do |project|
    project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings["DEVELOPMENT_TEAM"] = "Your Team ID"
         end
    end
  end
end

Чтобы узнать идентификатор команды участника, вы можете получить его в своей учетной записи разработчика Apple в разделе «Сведения о членстве» (строка идентификатора команды).

Или в той же строке вы можете использовать эту конфигурацию:

config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'

Проблема 3. Свойство не является членом класса MyFramework.MyFramework

.../MyFramework.swiftinterface:6:34: error: 'SomeProperty' is not a member type of class 'MyFramework.MyFramework'
   public var direction: MyFramework.SomeProperty { get set }

Решение:

Лучший способ избежать этого — если имена модулей в фреймворке разные и не совпадают с названием самого фреймворка. Так что, если вы только что запустили свой фреймворк с нуля  —  сохраняйте имя общедоступного модуля другим. Например, наша платформа называется MyFramework.xcframework, поэтому имя вашего общего модуля может быть MyFrameworkMethods или около того.

Проблема 4: Не найден путь к вашему продукту (папка продукта в архиве пуста)

Решение:

Отметьте флаг: SKIP_INSTALL=NO в вашем скрипте сборки  — он установит ваш фреймворк в архив, другими словами, папка вашего продукта не будет пустой.

Проблема 5: Скомпилированный модуль был создан более новой версией компилятора

Решение:

Отметьте флажок:BUILD_LIBRARY_FOR_DISTRIBUTION в скрипте сборки.

Проблема 6. Ошибка сборки. Клиент использует те же зависимости, что и ваш фреймворк.

Кратко о решении. Версии зависимостей в фреймворке и в приложении должны совпадать.

n Подробно. Предположим, вы создаете фреймворк с помощью Coapod Alamofire 5.5.0, а клиентское приложение будет использовать ваш фреймворк и Alamofire 6.0.0, так что эта комбинация вызовет проблему. Другими словами, вы не сможете создать свое приложение и не поймете, что происходит, потому что ошибка будет связана с символами или чем-то еще.

pod 'Intercom', '2.0.0'
pod 'Alamofire', '5.5.0'

Ссылки


Оригинал