Xcode 11 et XCFrameworks: un nouveau format de packaging de framework


Dans la vie de nombreuses entreprises qui ont et développent leur propre pile de bibliothèques et de composants, il arrive un moment où le volume de cette pile devient difficile à maintenir.


Dans le cas du développement pour la plate-forme iOS, et en général, l'écosystème Apple, il existe deux options pour connecter des bibliothèques en tant que dépendances:


  1. Collectez-les chaque fois que vous créez l'application.
  2. Collectez-les à l'avance en utilisant les dépendances déjà collectées.

Lors du choix de la deuxième approche, il devient logique d'utiliser des systèmes CI / CD pour assembler des bibliothèques en artefacts prêts à l'emploi.


Cependant, la nécessité de créer des bibliothèques pour plusieurs plates-formes ou architectures de processeur dans l'écosystème Apple, nécessite souvent des opérations pas toujours triviales, à la fois lors de la création de la bibliothèque et du produit final qui l'utilise.


Dans ce contexte, il était difficile de ne pas remarquer et extrêmement intéressant d'étudier, l'une des innovations d'Apple, présentée à la WWDC 2019 dans le cadre de la présentation des cadres binaires dans Swift - le format d'emballage des cadres est XCFramework.


XCFramework présente plusieurs avantages par rapport aux approches établies:


  1. Empaquetage de dépendance pour toutes les plates-formes et architectures cibles dans un seul paquet prêt à l'emploi.
  2. Ensemble de connexion au format XCFramework, en tant que dépendance unique pour toutes les plates-formes et architectures cibles.
  3. Pas besoin de construire un cadre gras / universel.
  4. Il n'est pas nécessaire de se débarrasser de la tranche x86_64 avant de charger les applications finales dans l'AppStore.

Dans cet article, nous expliquerons pourquoi ce nouveau format a été introduit, ce qu'il est et aussi ce qu'il donne au développeur.


Comment le nouveau format est apparu


Apple avait précédemment publié le gestionnaire de dépendances Swift Package Manager .
L'essentiel est que Swift PM vous permet de fournir des bibliothèques sous forme de code open source avec une description des dépendances.


Du point de vue du développeur fournissant la bibliothèque, je voudrais souligner deux aspects de Swift PM.


  • L'inconvénient évident est que, pour une raison ou une autre, tous les fournisseurs de bibliothèques ne souhaitent pas ouvrir leur code source aux consommateurs.
  • Un avantage évident - lors de la compilation de dépendances à partir de sources, nous nous débarrassons de la nécessité d'observer la compatibilité binaire des bibliothèques.

XCFramework Apple propose un nouveau format binaire pour les bibliothèques de packaging, le considérant comme une alternative aux packages Swift.


Ce format, ainsi que la possibilité de connecter la bibliothèque assemblée dans XCFramework, est disponible à partir de Xcode 11 et ses versions bêta.


Qu'est-ce que XCFramework


À la base, XCFramework est une nouvelle façon de conditionner et de livrer des bibliothèques, dans leurs différentes versions.


Entre autres choses, le nouveau format permet également de regrouper des bibliothèques statiques avec leurs fichiers d'en-tête, y compris ceux écrits en C / Objective-C.


Considérez le format plus en détail.


  1. Empaquetage de dépendance pour toutes les plates-formes et architectures cibles dans un seul paquet prêt à l'emploi


    Tous les assemblys de bibliothèques pour chacune des plates-formes et architectures cibles peuvent désormais être regroupés dans un seul ensemble avec l'extension .xcframework.
    Cependant, pour cela, à ce stade, vous devez utiliser des scripts pour appeler la xcodebuild avec le nouveau -create-xcframework pour Xcode 11.


    Le processus d'assemblage et d'emballage sera examiné plus en détail.


  2. Ensemble de connexion au format XCFramework, en tant que dépendance unique pour toutes les plates-formes et architectures cibles


    Étant donné que le bundle .xcframework contient toutes les options d'assemblage de dépendances nécessaires, nous n'avons pas à nous soucier de son architecture et de sa plateforme cible.


    Dans Xcode 11, une bibliothèque conditionnée en .xcframework est connectée comme un .framework normal.
    Plus précisément, cela peut être réalisé de la manière suivante dans les paramètres cibles:


    • ajout de .xcframework à la section «Frameworks And Libraries» de l'onglet «General»
    • ajout de .xcframework à «Lier le binaire aux bibliothèques» sur l'onglet «Phases de construction»

  3. Pas besoin de construire un cadre gras / universel


    Auparavant, afin de prendre en charge plusieurs bibliothèques et plusieurs architectures dans une bibliothèque de plug-ins, il était nécessaire de préparer les frameworks dits gras ou universels.
    Cela a été fait en utilisant la lipo pour coudre toutes les options du cadre assemblé en un seul binaire épais.


    Vous trouverez plus de détails à ce sujet, par exemple, dans les articles suivants:



  4. Il n'est pas nécessaire de se débarrasser de la tranche x86_64 avant de charger les applications finales dans l'AppStore


    En règle générale, une telle tranche est utilisée pour fournir des bibliothèques dans le simulateur iOS.
    Lorsque vous essayez de télécharger une application avec des dépendances contenant une tranche x86_64 dans l'AppStore, vous pouvez rencontrer l'erreur bien connue ITMS-90087 .



Créer et empaqueter XCFramework: théorie


Dans la présentation mentionnée précédemment, plusieurs étapes sont nécessaires pour assembler et empaqueter la bibliothèque au format XCFramework:


  1. Préparation du projet


    Pour commencer, dans toutes les cibles du projet responsables de la création de la bibliothèque pour les plates-formes cibles, vous devez activer le nouveau paramètre Créer des bibliothèques pour la distribution pour Xcode 11.



  2. Assemblage de projets pour plates-formes et architectures cibles


    Ensuite, nous devons collecter toutes les cibles pour les plates-formes et architectures cibles.
    Prenons des exemples d'appels de commandes à l'aide de l'exemple d'une configuration de projet spécifique.


    Disons que dans le projet, nous avons deux schémas «XCFrameworkExample-iOS» et «XCFramework-macOS».



    Le projet contient également deux cibles qui collectent la bibliothèque pour iOS et macOS.



    Pour créer toutes les configurations de bibliothèque requises, nous devons collecter les deux cibles à l'aide des schémas correspondants.
    Cependant, pour iOS, nous avons besoin de deux assemblys: l'un pour les terminaux (ARM) et l'autre pour le simulateur (x86_64).


    Au total, nous devons collecter 3 cadres.


    Pour ce faire, vous pouvez utiliser la commande 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 

    En conséquence, nous avons obtenu 3 cadres assemblés, que nous emballerons plus loin dans le conteneur .xcframework.


  3. Emballage assemblé .framework en .xcframework


    Vous pouvez le faire avec la commande suivante:


     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" 

    Il peut y avoir de nombreuses -framework paramètre -framework qui pointent vers tous les assemblys .framework que vous souhaitez attacher au conteneur .xcframework.



Préparation d'un projet de bibliothèque pour l'assemblage et le conditionnement futurs de XCFramework


TL; DR: le projet terminé peut être téléchargé depuis le référentiel sur Github .


À titre d'exemple, nous mettons en œuvre une bibliothèque qui sera disponible pour deux plates-formes: iOS et macOS.
Nous utiliserons la configuration de projet mentionnée dans la section précédente de l'article: deux schémas et deux cibles Framework correspondantes pour les plateformes iOS et macOS.


La bibliothèque elle-même nous fournira-t-elle une extension simple pour String? ( Optional where Wrapped == String ), avec une seule propriété.


Nous appelons cette propriété isNilOrEmpty et, comme son nom l'indique, nous fera-t-elle savoir quand à l'intérieur de String? valeur manquante ou la chaîne stockée à l'intérieur est vide.


Le code peut être implémenté comme suit:


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

Nous procédons directement à la création et à la configuration du projet.


  1. Pour commencer, nous devons créer un projet de type «Framework» pour l'une des deux plateformes cibles de votre choix: iOS ou macOS.


    Vous pouvez le faire dans Xcode via l'élément de menu "Fichier" => "Nouveau" => "Projet", ou en utilisant le raccourci clavier ⇧ + + N (par défaut).


    Ensuite, en haut de la boîte de dialogue, sélectionnez la plate-forme souhaitée (iOS ou macOS), sélectionnez le type de projet Framework et passez au bouton «Suivant».


    Sur l'écran suivant, nous devons définir le nom du projet dans le champ "Nom du produit".


    Alternativement, vous pouvez utiliser le nom "de base" du projet, dans la configuration mentionnée précédemment c'est "XCFrameworkExample".


    À l'avenir, lors de la configuration du projet, nous ajouterons des suffixes indiquant les plates-formes au nom de base utilisé dans le nom de la cible.


  2. Après cela, vous devez créer une autre cible de type «Framework» dans le projet pour une autre des plates-formes répertoriées (à l'exception de celle pour laquelle le projet a été créé à l'origine).


    Pour ce faire, utilisez l'élément de menu "Fichier" => "Nouveau" => "Cible".


    Ensuite, nous sélectionnons dans le dialogue une autre (par rapport à celle sélectionnée au paragraphe 1) des deux plates-formes, après quoi nous sélectionnons à nouveau le type de projet «Framework».


    Pour le champ «Product Name», nous pouvons immédiatement utiliser le nom avec le suffixe de la plateforme, pour lequel nous ajoutons target dans ce paragraphe. Donc, si la plate-forme est macOS, le nom pourrait être «XCFrameworkExample-macOS» (% base_name% -% platform%).


  3. Nous allons définir des cibles et des graphiques pour les rendre plus faciles à distinguer.


    Tout d'abord, renommez nos schémas et les cibles qui leur sont attachées afin que leurs noms reflètent les plates-formes, par exemple comme ceci:


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

  4. Ensuite, ajoutez le fichier avec le code de notre extension pour String? au projet .swift String?


    Ajoutez un nouveau fichier .swift au projet avec le nom "Optional.swift".
    Et dans le fichier lui-même, nous avons mis l'extension mentionnée précédemment pour Optional .


    Il est important de ne pas oublier d'ajouter le fichier de code aux deux cibles.




Nous avons maintenant un projet que nous pouvons assembler dans XCFramework en utilisant les commandes de l'étape précédente.


Processus d'assemblage et de conditionnement de la bibliothèque au format .xcframework


À ce stade, vous pouvez utiliser le script bash dans un fichier séparé pour créer la bibliothèque et la conditionner au format .xcframework. De plus, cela permettra à l'avenir d'utiliser ces développements pour intégrer la solution dans le système CI / CD.


Le script semble très simple et, en fait, rassemble les commandes d'assemblage mentionnées précédemment:


 #!/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" 

Contenu .Xcframework


À la suite du script d'assemblage du paragraphe précédent de l'article, nous obtenons le très convoité bundle .xcframework, qui peut être ajouté au projet.


Si nous regardons à l'intérieur de ce bundle, qui, comme .framework, est essentiellement un simple dossier, nous verrons la structure suivante:



Nous voyons ici qu'à l'intérieur de .xcframework se trouvent des assemblages au format .framework, ventilés par plate-forme et architecture. Aussi pour décrire le contenu du bundle .xcframework, à l'intérieur il y a un fichier Info.plist.


Le fichier Info.plist a le contenu suivant
 <?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> 

Vous pouvez remarquer que pour la clé "CFBundlePackageType", contrairement au format .framework, la nouvelle valeur "XFWK" est utilisée, pas "FMWK".


Résumé


Ainsi, le format d'empaquetage des bibliothèques dans XCFramework n'est rien de plus qu'un conteneur normal pour les bibliothèques compilées au format .framework.


Cependant, ce format vous permet de stocker séparément et d'utiliser indépendamment chacune des architectures et plates-formes présentées à l'intérieur. Cela élimine un certain nombre de problèmes inhérents à l'approche répandue de la construction de cadres universels / universels.


Quoi qu'il en soit, il existe actuellement une nuance importante concernant la question de l'utilisation de XCFramework dans des projets réels - la gestion des dépendances, qu'Apple n'a pas implémentée au format XCFramework.


À ces fins, Swift PM, Carthage, CocoaPods et d'autres systèmes de gestion des dépendances et leurs assemblages sont habituellement utilisés. Il n'est donc pas surprenant que le soutien au nouveau format soit déjà en cours précisément dans les projets CocoaPods et Carthage .

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


All Articles