Xcode 11 e XCFrameworks: um novo formato de pacote de estrutura


Na vida de muitas empresas que possuem e desenvolvem sua própria pilha de bibliotecas e componentes, chega um momento em que o volume dessa pilha fica difícil de manter.


No caso de desenvolvimento para a plataforma iOS e, em geral, o ecossistema Apple, existem duas opções para conectar bibliotecas como dependências:


  1. Colete-os sempre que criar o aplicativo.
  2. Colete-os com antecedência usando as dependências já coletadas.

Ao escolher a segunda abordagem, torna-se lógico usar sistemas de CI / CD para montar bibliotecas em artefatos prontos para uso.


No entanto, a necessidade de criar bibliotecas para várias plataformas ou arquiteturas de processador no ecossistema da Apple, geralmente nem sempre exige operações triviais, tanto na construção da biblioteca quanto no produto final que a utiliza.


Nesse contexto, foi difícil não notar e extremamente interessante estudar, uma das inovações da Apple, apresentada na WWDC 2019 como parte da apresentação dos Frameworks Binários no Swift - o formato de empacotamento dos frameworks é o XCFramework.


O XCFramework tem várias vantagens sobre as abordagens estabelecidas:


  1. Empacotamento de dependência para todas as plataformas e arquiteturas de destino em um único pacote pronto para uso.
  2. Pacote de conexão no formato XCFramework, como uma dependência única para todas as plataformas e arquiteturas de destino.
  3. Não há necessidade de construir uma estrutura universal / gorda.
  4. Não há necessidade de se livrar da fatia x86_64 antes de carregar os aplicativos finais na AppStore.

Neste artigo, explicaremos por que esse novo formato foi introduzido, o que é e também o que fornece ao desenvolvedor.


Como o novo formato apareceu


A Apple lançou anteriormente o gerenciador de dependências do Swift Package Manager .
A conclusão é que o Swift PM permite entregar bibliotecas na forma de código-fonte aberto com uma descrição das dependências.


Da perspectiva do desenvolvedor que fornece a biblioteca, gostaria de destacar dois aspectos do Swift PM.


  • O menos óbvio é que, por um motivo ou outro, nem todos os fornecedores de bibliotecas gostariam de abrir seu código-fonte para os consumidores.
  • Uma vantagem óbvia - ao compilar dependências de fontes, nos livramos da necessidade de observar a compatibilidade binária das bibliotecas.

XCFramework A Apple oferece como um novo formato binário para bibliotecas de embalagens, considerando-o uma alternativa aos pacotes Swift.


Esse formato, bem como a capacidade de conectar a biblioteca montada no XCFramework, está disponível a partir do Xcode 11 e de suas versões beta.


O que é o XCFramework


Na sua essência, o XCFramework é uma nova maneira de empacotar e entregar bibliotecas, em suas várias versões.


Entre outras coisas, o novo formato também permite empacotar bibliotecas estáticas juntamente com seus arquivos de cabeçalho, incluindo aqueles escritos em C / Objective-C.


Considere o formato com mais detalhes.


  1. Empacotamento de dependência para todas as plataformas e arquiteturas de destino em um único pacote pronto para uso


    Todos os assemblies de biblioteca para cada plataforma e arquitetura de destino agora podem ser empacotados em um único pacote configurável com a extensão .xcframework.
    No entanto, para isso, neste momento, você precisa usar scripts para chamar o xcodebuild com a nova opção -create-xcframework do Xcode 11.


    O processo de montagem e embalagem será considerado mais adiante.


  2. Pacote de conexão no formato XCFramework, como uma dependência única para todas as plataformas e arquiteturas de destino


    Como o pacote configurável .xcframework contém todas as opções necessárias de montagem de dependências, não precisamos nos preocupar com sua arquitetura e plataforma de destino.


    No Xcode 11, uma biblioteca empacotada em .xcframework é conectada como um .framework comum.
    Mais especificamente, isso pode ser alcançado das seguintes maneiras nas configurações de destino:


    • adicionando .xcframework à seção "Frameworks And Libraries" da guia "General"
    • adicionando .xcframework a "Vincular binário com bibliotecas" na guia "Construir fases"

  3. Não há necessidade de construir estrutura universal / gorda


    Anteriormente, para suportar várias bibliotecas e várias arquiteturas em uma biblioteca de plug-in, era necessário preparar as chamadas estruturas gordas ou universais.
    Isso foi feito usando o lipo para costurar todas as opções da estrutura montada em um único binário espesso.


    Mais detalhes sobre isso podem ser encontrados, por exemplo, nos seguintes artigos:



  4. Não há necessidade de se livrar da fatia x86_64 antes de carregar os aplicativos finais na AppStore


    Normalmente, essa fatia é usada para fornecer bibliotecas no simulador do iOS.
    Ao tentar baixar um aplicativo com dependências que contêm fatia x86_64 na AppStore, você pode encontrar o erro conhecido ITMS-90087 .



Criando e empacotando XCFramework: teoria


Na apresentação mencionada anteriormente, são necessárias várias etapas necessárias para montar e empacotar a biblioteca no formato XCFramework:


  1. Preparação do projeto


    Para começar, em todas as áreas de destino do projeto responsáveis ​​pela criação da biblioteca para as plataformas de destino, é necessário ativar a nova configuração Compilar Bibliotecas para Distribuição para o Xcode 11.



  2. Montagem de projeto para plataformas e arquiteturas de destino


    Em seguida, precisamos coletar todos os destinos para as plataformas e arquiteturas de destino.
    Vamos considerar exemplos de comandos de chamada usando o exemplo de uma configuração específica do projeto.


    Digamos que no projeto tenhamos dois esquemas “XCFrameworkExample-iOS” e “XCFramework-macOS”.



    Também no projeto, existem dois destinos que coletam a biblioteca para iOS e macOS.



    Para criar todas as configurações de biblioteca necessárias, precisamos coletar os dois destinos usando os esquemas correspondentes.
    No entanto, para iOS, precisamos de dois conjuntos: um para dispositivos finais (ARM) e outro para o simulador (x86_64).


    No total, precisamos coletar 3 estruturas.


    Para fazer isso, você pode usar o comando xcodebuild :


     # iOS devices xcodebuild archive \ -scheme XCFrameworkExample-iOS \ -archivePath "./build/ios.xcarchive" \ -sdk iphoneos \ SKIP_INSTALL=NO # iOS simulator xcodebuild archive \ -scheme XCFrameworkExample-iOS \ -archivePath "./build/ios_sim.xcarchive" \ -sdk iphonesimulator \ SKIP_INSTALL=NO # macOS xcodebuild archive \ -scheme XCFrameworkExample-macOS \ -archivePath "./build/macos.xcarchive" \ SKIP_INSTALL=NO 

    Como resultado, obtivemos três estruturas montadas, as quais empacotaremos ainda mais no contêiner .xcframework.


  3. Embalagem .framework montado em .xcframework


    Você pode fazer isso com o seguinte comando:


     xcodebuild -create-xcframework \ -framework "./build/ios.xcarchive/Products/Library/Frameworks/XCFrameworkExample.framework" \ -framework "./build/ios_sim.xcarchive/Products/Library/Frameworks/XCFrameworkExample.framework" \ -framework "./build/macos.xcarchive/Products/Library/Frameworks/XCFrameworkExample.framework" \ -output "./build/XCFrameworkExample.xcframework" 

    Pode haver muitas -framework parâmetros -framework que apontam para todos os assemblies .framework que você deseja anexar ao contêiner .xcframework.



Preparando um Projeto de Biblioteca para Montagem e Empacotamento Futuros do XCFramework


TL; DR: o projeto finalizado pode ser baixado do repositório no Github .


Como exemplo, estamos implementando uma biblioteca que estará disponível para duas plataformas: iOS e macOS.
Usaremos a configuração do projeto mencionada na seção anterior do artigo: dois esquemas e dois destinos correspondentes do Framework para plataformas iOS e macOS.


A própria biblioteca nos fornecerá uma extensão simples para String? ( Optional where Wrapped == String ), com uma única propriedade.


Chamamos essa propriedade de isNilOrEmpty e, como o nome indica, ela nos informará quando estiver dentro de String? valor ausente ou a cadeia armazenada dentro está vazia.


O código pode ser implementado da seguinte maneira:


 public extension Optional where Wrapped == String { var isNilOrEmpty: Bool { if case let .some(string) = self { return string.isEmpty } return true } } 

Prosseguimos diretamente para a criação e configuração do projeto.


  1. Para começar, precisamos criar um projeto do tipo "Framework" para uma das duas plataformas de destino de sua escolha: iOS ou macOS.


    Você pode fazer isso no Xcode através do item de menu “Arquivo” => “Novo” => “Projeto” ou usando o atalho de teclado ⇧ + + N (por padrão).


    Em seguida, na parte superior da caixa de diálogo, selecione a plataforma desejada (iOS ou macOS), selecione o tipo do projeto do Framework e vá para o botão "Avançar".


    Na próxima tela, precisamos definir o nome do projeto no campo "Nome do produto".


    Como alternativa, você pode usar o nome "base" do projeto, na configuração mencionada anteriormente é "XCFrameworkExample".


    No futuro, ao configurar o projeto, adicionaremos sufixos que denotam plataformas ao nome base usado no nome do destino.


  2. Depois disso, você precisa criar outro Destino do tipo “Framework” no projeto para outra das plataformas listadas (exceto aquela para a qual o projeto foi originalmente criado).


    Para fazer isso, use o item de menu "Arquivo" => "Novo" => "Alvo".


    Em seguida, selecionamos no diálogo outro (relativo ao selecionado no parágrafo 1) das duas plataformas, após o qual novamente selecionamos o tipo de projeto “Framework”.


    Para o campo "Nome do produto", podemos usar imediatamente o nome com o sufixo da plataforma, ao qual adicionamos o destino neste parágrafo. Portanto, se a plataforma for macOS, o nome poderá ser "XCFrameworkExample-macOS" (% base_name% -% platform%).


  3. Definiremos metas e gráficos para facilitar a distinção.


    Primeiro, renomeie nossos esquemas e os destinos anexados a eles para que seus nomes reflitam as plataformas, por exemplo:


    • "XCFrameworkExample-iOS"
    • "XCFrameworkExample-macOS"

  4. Em seguida, adicione o arquivo com o código da nossa extensão para String? ao projeto .swift String?


    Adicione um novo arquivo .swift ao projeto com o nome "Optional.swift".
    E no próprio arquivo, colocamos a extensão mencionada anteriormente para Optional .


    É importante não esquecer de adicionar o arquivo de código aos dois destinos.




Agora temos um projeto que podemos montar no XCFramework usando os comandos do estágio anterior.


O processo de montagem e empacotamento da biblioteca no formato .xcframework


Nesse estágio, você pode usar o script bash em um arquivo separado para criar a biblioteca e empacotá-la no formato .xcframework. Além disso, isso permitirá que, no futuro, esses desenvolvimentos sejam integrados à solução no sistema de CI / CD.


O script parece feio simples e, de fato, reúne os comandos mencionados anteriormente para montagem:


 #!/bin/sh # ---------------------------------- # BUILD PLATFORM SPECIFIC FRAMEWORKS # ---------------------------------- # iOS devices xcodebuild archive \ -scheme XCFrameworkExample-iOS \ -archivePath "./build/ios.xcarchive" \ -sdk iphoneos \ SKIP_INSTALL=NO # iOS simulator xcodebuild archive \ -scheme XCFrameworkExample-iOS \ -archivePath "./build/ios_sim.xcarchive" \ -sdk iphonesimulator \ SKIP_INSTALL=NO # macOS xcodebuild archive \ -scheme XCFrameworkExample-macOS \ -archivePath "./build/macos.xcarchive" \ SKIP_INSTALL=NO # ------------------- # PACKAGE XCFRAMEWORK # ------------------- xcodebuild -create-xcframework \ -framework "./build/ios.xcarchive/Products/Library/Frameworks/XCFrameworkExample.framework" \ -framework "./build/ios_sim.xcarchive/Products/Library/Frameworks/XCFrameworkExample.framework" \ -framework "./build/macos.xcarchive/Products/Library/Frameworks/XCFrameworkExample.framework" \ -output "./build/XCFrameworkExample.xcframework" 

.Xcframework content


Como resultado do script de montagem do parágrafo anterior do artigo, obtemos o cobiçado pacote .xcframework, que pode ser adicionado ao projeto.


Se olharmos dentro deste pacote, que, como .framework, é essencialmente uma pasta simples, veremos a seguinte estrutura:



Aqui vemos que dentro de .xcframework existem assemblies no formato .framework, divididos por plataforma e arquitetura. Também para descrever o conteúdo do pacote .xcframework, existe um arquivo Info.plist.


O arquivo Info.plist possui o seguinte conteúdo
 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>AvailableLibraries</key> <array> <dict> <key>LibraryIdentifier</key> <string>ios-arm64</string> <key>LibraryPath</key> <string>XCFrameworkExample.framework</string> <key>SupportedArchitectures</key> <array> <string>arm64</string> </array> <key>SupportedPlatform</key> <string>ios</string> </dict> <dict> <key>LibraryIdentifier</key> <string>ios-x86_64-simulator</string> <key>LibraryPath</key> <string>XCFrameworkExample.framework</string> <key>SupportedArchitectures</key> <array> <string>x86_64</string> </array> <key>SupportedPlatform</key> <string>ios</string> <key>SupportedPlatformVariant</key> <string>simulator</string> </dict> <dict> <key>LibraryIdentifier</key> <string>macos-x86_64</string> <key>LibraryPath</key> <string>XCFrameworkExample.framework</string> <key>SupportedArchitectures</key> <array> <string>x86_64</string> </array> <key>SupportedPlatform</key> <string>macos</string> </dict> </array> <key>CFBundlePackageType</key> <string>XFWK</string> <key>XCFrameworkFormatVersion</key> <string>1.0</string> </dict> </plist> 

Você pode notar que, para a tecla “CFBundlePackageType”, ao contrário do formato .framework, o novo valor “XFWK” é usado, não “FMWK”.


Sumário


Portanto, o formato de empacotamento da biblioteca no XCFramework nada mais é do que um contêiner comum para bibliotecas compiladas no formato .framework.


No entanto, esse formato permite armazenar e usar separadamente cada uma das arquiteturas e plataformas apresentadas dentro. Isso elimina uma série de problemas inerentes à abordagem generalizada da construção de estruturas gordas / universais.


Seja como for, no momento, há uma nuance importante sobre a questão do uso do XCFramework em projetos reais - gerenciamento de dependências, que a Apple não implementou no formato XCFramework.


Para esses fins, são utilizados habitualmente Swift PM, Carthage, CocoaPods e outros sistemas de gerenciamento de dependências e seus conjuntos. Portanto, não surpreende que o suporte ao novo formato já esteja em andamento precisamente nos projetos CocoaPods e Carthage .

Source: https://habr.com/ru/post/pt475816/


All Articles