[iOS] Création d’un librairie statique comportant des librairies pods

mercredi 19 août 2015

Je ne vous cache pas que ça été une des plus grande difficulté qu’il m’ait eu à surmonté dans l’univers de la programmation IOS. Tout d’abord, j’ai voulu m’obstiner de le faire au travers d’une librairie dynamique de type framework sans succès à mon grand regret. J’ai trouvé pas mal de documentation à ce sujet, mais très peu tenait compte de la possibilité d’embarqué aussi dans le framework qu’on souhaite développé d’autres librairies tierce très utile. Au final, même si ca peut n’avoir aucune différence dans la manière d’embarquée une librarire statique ou dynamique, dans mon cas je ne suis jamais arrivé à le faire au travers d’une librairie dynamique. Donc je vais vous présentez pas à pas ma démarche qui a fonctionné pour moi. Il faut savoir que je me suis basé sur les 2 tutoriels suivants très bien expliqué: Etape 1 et Epate 2

Etape numéro 1 bien sur est de créer un projet de type static librairie qui contiendra notre librairie.
XCode > New Project > cliquez sur Cocoa Touch Static Library dans Framework & Library.
Ensuite comme notre librairie comporte aussi un pod RestKit pour pouvoir interrogé des webservices, il faut faire

pod init
Ensuite, on rajoute notre pod dans le podFile.
platform :ios, '8.0'

target 'BCMS' do
pod 'RestKit'
end

target 'BCMSTests' do
end
Ensuite, nous allons créer un fichier podspec car nous allons créer notre propre librairie cocoapods. Pour plus d’info sur la réalisation de cet pod, vous pouvez suivre le lien suivant dont j’ai précédemment déjà parlé. Ce fichier podspec comporte toutes les directives nécessaires à la bonne utilisation de votre librairie, on y trouvera par exemple les dépendances à certains framework ou pod. On retrouvera aussi les sources de notre librairie, le fichier exécutable .a, les fichiers entête .h qui sont nécessaire pour que le compilateur puisse s’assurer que vous appelez et utilisez correctement les fonctions définies dans la librairie. En faite, le fichier podspec est le fichier qui permet de gérer les dépendances avec d’autres librairies, on utilise le fichier podFile pour inclure une librairie toute seule, mais on utilise un fichier podspec si notre librairie dépend d’autres librairies ou framework. Voici la commande pour créer le fichier podspec dans le dossier de votre projet concernant votre librairie statique
pod spec create BCMS
Le contenu du fichier podspec:
Pod::Spec.new do |s|
  s.name         = "BCMS"
  s.version      = "0.0.1"
  s.summary      = "A short description of BCMS."

  s.description  = <<-DESC
                   A longer description of BCMS in Markdown format.

                   * Think: Why did you write this? What is the focus? What does it do?
                   * CocoaPods will be using this to generate tags, and improve search results.
                   * Try to keep it short, snappy and to the point.
                   * Finally, don't worry about the indent, CocoaPods strips it!
                   DESC

  s.homepage     = "http://example.be"
  # s.screenshots  = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"
  s.license      = "MIT (example)"
  # s.license      = { :type => "MIT", :file => "FILE_LICENSE" }
  s.author             = { "" => "" }
  # Or just: s.author    = ""
  # s.authors            = { "" => "" }
  # s.social_media_url   = "http://twitter.com/"
  # s.platform     = :ios
   s.platform     = :ios, "8.0"

  #  When using multiple platforms
  # s.ios.deployment_target = "5.0"
  # s.osx.deployment_target = "10.7"

  # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
  #
  #  Specify the location from where the source should be retrieved.
  #  Supports git, hg, bzr, svn and HTTP.
  #

  #s.source       = { :git => "http://EXAMPLE/BCMS.git", :tag => "0.0.1" }

  # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
  #
  #  CocoaPods is smart about how it includes source code. For source files
  #  giving a folder will include any h, m, mm, c & cpp files. For header
  #  files it will include any header in the folder.
  #  Not including the public_header_files will make all headers public.
  #
  s.source_files  = "BCMS/*.h"
  s.vendored_libraries = 'BCMS/Library/libBCMS.a'
  s.public_header_files = "BCMS/*.h"

  # s.resource  = "icon.png"
  s.resources = "BCMS/*.png"

  # s.preserve_paths = "FilesToSave", "MoreFilesToSave"

  s.framework  = "SystemConfiguration"
  # s.frameworks = "SomeFramework", "AnotherFramework"

  # s.library   = "iconv"
  # s.libraries = "iconv", "xml2"

  s.requires_arc = true

  # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
  s.dependency "RestKit"

end

Pour vérifier la synthaxe de votre fichier:
pod spec lint BCMS.podspec

Vous remarquez que ce fichier comporte les directives pour importer dans un nouveau projet le fichier .a qui comporte le code source compilé et donc non visible ainsi que les fichiers headers .h nécessaire pour le compilateur. dans mon cas je souhaite que les sources ne soit pas visible pour l’utilisateur de la librairie et aussi que le pod soit privé, c’est pourquoi la directive source vers le git n’est pas activé car pas disponible sur internet. Si vous souhaitez aussi que vos sources soit accessible, il suffit de retirer les deux directives
s.vendored_libraries = 'BCMS/Library/libBCMS.a'
s.public_header_files = "BCMS/*.h"
et de rajouter dans la directive s.source_files, les fichiers .m. Il ne faut surtout pas non plus oublié de définir les pods qui sont nécessaires pour el bon fonctionnement de la librairie au travers de la directive depency!.
Voilà le plus gros de la configuration pour utiliser notre librairie au travers d’un autre projet et via cocoapods est en place.

Maintenant, il est temps de créer notre librairie en y associant toutes nos sources .m/.h dans le projet. Attention de bien utiliser des #import « xxxx.h » et non les directives de type framework.
Une fois tout le code source installé, compilé, tester et builder, il faudra aussi convertir votre binaire en fat binary c’est-à-dire compatible pour toutes les architectures d’IOS.

Toujours dans le projet de votre librairie faire un New > target > other > Agreggate. Ensuite il faut rajouter dans Build Phase sur le petit plus en haut à gauche Add Run Script phase. agreggate_fat_binary Dans le shell mettez le code suivant:

# Build device and Simulator version
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
# Create Framework director in project
mkdir -p ${PROJECT_DIR}/${PROJECT_NAME}/Library

#Create fat binary
lipo -create "${TARGET_BUILD_DIR}/../Debug-iphoneos/lib${PROJECT_NAME}.a" "${TARGET_BUILD_DIR}/../Debug-iphonesimulator/lib${PROJECT_NAME}.a" -output "${PROJECT_DIR}/${PROJECT_NAME}/Library/lib${PROJECT_NAME}.a"
# open Finder library
open "${PROJECT_DIR}/${PROJECT_NAME}/Library"
Ce code permet de créer un binaire compatible sur les différentes architectures de IOS, sans quoi vous risquez d’avoir des erreur du type architecture comme ceci lorsque vous compilerez votre projet important la librairie:
Missing required architecture X86_64 in file
Pour vérifier que le binaire supporte bien toutes les architectures nécessaires:
lipo -info libBCMS.a
Ce code se charge donc aussi de rassembler la librairie correctement construite dans le dossier /Library de votre projet sous vos sources. D’ailleurs dans le fichier podspec il est bien stipuler d’aller chercher le .a dans ce même dossier! Il peut être aussi intéressant de rajouter le paramètre ${CURRENT_PROJECT_VERSION} dans le nom de votre librairie finale pour identifier al version de votre librairie. Bien entendu, il faudra définir cette variable CURRENT_PROJECT_VERSION dans Build settings. Attention: il ne faut surtout pas oublier de faire un Run de votre librairie en prenant comme scheme celui de votre agreggate quand vous souhaitez builder votre librairie finale! run_agreggate

Voilà tout ce qui concerne la création de votre librairie, maintenant nous allons voir comment importer automatiquement avec cocoapods la librairie dans un autre projet.
Pour cela, créer un nouveau projet au même niveau que votre projet BCMS par exemple. New Project > Single View application
Vous faites aussi un pod init et vous devez juste demander d’aller chercher votre pod via la directive path:

pod 'BCMS', :path => '../BCMS'
Et donc, on voit bien que c’est un pod privé car il va chercher sur le disque le pod.
Il reste à faire un pod install et le tour est joué.
En effet, cocoapods se charge de tout, et va chercher votre librairie ainsi que tous les pods nécessaires et les installent dans votre projet automatiquement.
Voilà la boucle est bouclée, la méthode parait simple après moulte tentative à trouver la bonne solution! J’ai essayé avec les directives pour le .framework mais sans succès. Je suis aussi tombé sur des conversation comme quoi cocoapods ne savaient pas gérer ce type de format .framework.

Tags: framework , library , Xcode