appicon

XCode: librairies statiques et catégories

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be found at: http://www.gnu.org/copyleft/fdl.html
Il est souvent pratique de scinder un projet XCode en plusieurs parties distinctes: le code permettant de contrôler les différents éléments de l’interface, et le code indépendant de l’interface (outils, librairies, etc).
Une telle découpe présente de nombreux avantage, notamment un meilleur contrôle des options de compilation pour chaque partie ainsi q’un temps de compilation réduit, puisque chaque partie est compilée séparément, et uniquement en cas de besoin.
Cet article explique comment paramétrer un projet XCode pour une telle utilisation.

Création d’un projet

Nous allons tout d’abord créer un nouveau projet XCode pour iPhone (la procédure est exactement similaire dans le cas d’une application Mac OS X).
XCode crée par défaut les fichiers nécessaires au fonctionnement d’une application basique.
La section «Targets» comprend les différents éléments pouvant être produits par notre projet XCode.
Dans ce cas là, il s’agit d’une application. Ce type de cible est composée de la copie des resources (les fichiers XIB – interface utilisateur), de la compilation du code source de l’application, et finalement de la liaison (lainage) du code objet généré avec les librairies et frameworks utilisés par l’application.
Il est possible de définir pour une cible des paramètres de compilation spécifiques, via le menu contextuel,

Cibles

Un projet XCode peut être composé de plusieurs cibles. Cela est nécessaire si l’on intègre des librairies externes, mais cela peut également être pratique pour séparer un projet et ainsi optimiser la compilation de l’application finale.
Nous allons commencer par créer une nouvelle classe, que nous allons nommer «MethodProvider», dont le but est, comme son nom l’indique, de mettre à disposition des méthode génériques pour notre application.
Nous allons y intégrer une méthode statique nommée doSomething.
/* MethodProvider.h */

#import <UIKit/UIKit.h>

@interface MethodProvider: NSObject
{}

+ ( void )doSomething;

@end
/* MethodProvider.m */

#import "MethodProvider.h"

@implementation MethodProvider

+ ( void )doSomething
{
NSLog( @"Method 'doSomething' called..." );
}

@end
Nous allons également appeler cette méthode depuis notre application, lorsque celle-ci aura terminé son chargement. Cela se passe dans la méthode «application:didFinishLaunchingWithOptions:» de la classe «MyAppAppDelegate»:
- ( BOOL )application: ( UIApplication * )application didFinishLaunchingWithOptions: ( NSDictionary * )launchOptions
{
[ window addSubview: viewController.view ];
[ window makeKeyAndVisible ];

[ MethodProvider doSomething ];

return YES;
}
Ne pas oublier d’include le fichier .h de la classe «MethodProvider» dans le fichier «MyAppAppController.m»:
#import "MyAppAppDelegate.h"
#import "MyAppViewController.h"
#import "MethodProvider.h"
Dans ce cas là, la classe «MethodProvider» est compilée avec les autres fichiers sources de notre application. Nous allons maintenant faire en sorte qu’elle le soit séparément.
En premier lieu, nous allons ôter cette classe de la cible de notre application. Cela peut être effectué en décochant la case à côté du fichier source.
A partir de ce moment là, la classe ne sera plus compilée en même temps que l’application. La compilation de l’application va donc échouer avec le message suivant:
_OBJC_CLASS_$_MethodProvider", referenced from:
objc-class-ref-to-MethodProvider in MyAppAppDelegate.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
Ce message veut tout simplement dire que le linker n’a pas réussi à trouver le symbole correspondant à notre classe, alors qu’il est utilisé depuis la classe MyAppAppDelegate.
Nous allons donc créer une nouvelle cible, qui aura pour but de compiler la classe «MethodProvider».

Librairie statique

Cette cible sera de type «static library»:
Une librairie est un format spécifique de code object (compilé), dont le but est de fournir des fonctions aux applications désirant les utiliser. Une librairie peut être de deux types: statique ou dynamique.
Dans le cas d’une librairie statique, le code objet de la librairie est intégré au binaire de l’application lors de la compilation.
Il en résulte un seul fichier binaire, contenant à la fois le code de l’application et le code de la librairie. Le binaire est donc plus lourd, mais la libraire n’a pas besoin d’être présente sur la machine hôte.
Dans le cas d’une librairie dynamique, seul son adresse est inclue dans le code de l’application, et c’est le kernel (via un linker) qui chargera le code de la libraire en mémoire à chaque fois que l’application est lancée. Le binaire est dans ce cas plus léger, mais la librairie doit impérativement être présente sur la machine hôte. Le temps de lancement de l’application peut en outre être un peu plus long. Le principal avantage d’une librairie dynamique est que plusieurs applications peuvent reposer sur la même librairie.
Dans notre cas, c’est donc bien sûr une libraire statique qu’il nous faut. Qui plus est, sur iOS, il est actuellement impossible de générer une libraire dynamique.
Lorsque plusieurs cibles sont disponibles, XCode permet de passer de l’une à l’autre via un menu déroulant, se trouvant dans la barre d’outils:
Nous pouvons donc passer sur notre cible «Methods», et y ajouter le(s) fichiers devant être compilés:
Nous pouvons également voir, au passage, notre librairie dans la section «Products»:
Il nous reste à lier notre application principale avec cette librairie. Cela est effectué depuis la phase «Link Binary With Libraries» de la cible de l’application.
Nous pouvons maintenant y ajouter notre librairie.
La dernière étape consiste à indiquer à XCode que notre application dépend de la librairie, pour que cette dernière soit compilée avant l’application, en cas de besoin. Cette opération est effectuée en ajoutant une dépendance, dans la section «General», dans les informations de la cible de l’application:
Nous pouvons maintenant compiler notre application sans problème.
Cette procédure peut être utilisée, comme dans cet exemple, pour séparer différentes parties de notre code.
C’est également la procédure à utiliser lors de l’utilisation de sources et librairies externes, comme par exemple OpenCV (C++), JSON-Framework (Obj-C), etc.
Il est bien sûr possible d’inclure les fichiers sources dans la même cible que l’application, mais un tel scénario nous prive de la possibilité de spécifier des paramètres de compilation spécifiques et risque de rallonger inutilement le temps de compilation de la cible principale.

Catégories

Il faut encore noter que, dans le cas de l’utilisation de catégories, l’utilisation d’une librairie statique peut se révéler problématique.
Pour rappel, en Objective-C, une catégorie permet d’ajouter des méthodes dans une classe existante.
Ajoutons donc une catégorie sur UIApplication, dans la cible de notre librairie:
/* MyUIApp.h */

#import <UIKit/UIKit.h>

@interface UIApplication( MyUIApp )

- ( void )sayHello;

@end
/* MyUIApp.m */

#import "MyUIApp.h"

@implementation UIApplication( MyUIApp )

- ( void )sayHello
{
NSLog( @"Hello" );
}

@end
Et utilisons cette méthode dans notre application:
/* MyAppAppDelegate.m */

- ( BOOL )application: ( UIApplication * )application didFinishLaunchingWithOptions: ( NSDictionary * )launchOptions
{
[ window addSubview: viewController.view ];
[ window makeKeyAndVisible ];

[ MethodProvider doSomething ];
[ application sayHello ];

return YES;
}
Sans oublier d’inclure le fichier header:
#import "MyUIApp.h"
La compilation se déroulera sans aucun problème, mais l’application plantera, avec le message suivant (affiché dans la console):
- [ UIApplication sayHello ]: unrecognized selector sent to instance 0x5911700
Ce cas particulier est dû au côté dynamique d’Objective-C (la résolution des méthodes est faite par la couche run-time), est au spécificités du linker, qui ne génère par défaut pas de symboles pour les catégories.
Pour régler ce problème, il suffit de modifier les paramètres du linker, dans les informations de la cible de l’application, et y ajouter, dans la section «Other Linker Flags»:
-ObjC -all_load
Be Sociable, Share!

related articles

comments

  1. Merci :-)
    Super utile pour un débutant !

  2. Bonsoir,
    Complètement d’accord, très utile ! Vous avez trouvé tout de suite ce qui clochait dans ma p’tite application, ce fameux problème des symboles non créés par défaut pour les catégories. J’avais le problème en liant la librairie HessianKit (une librairie pour faire du webservice en binaire sur iOS, mais pas que).

add a comment:

*