Tour d'horizon du développement iPhone

le 08/07/2009 par Olivier Martin
Tags: Software Engineering

Article paru dans le magazine Programmez le 30 Juin 2009.

Avez-vous vu les dernières publicités pour l’iPhone ? Apple ne met plus en avant les qualités de son téléphone mais communique exclusivement sur les applications. Alors, comment ça se code au juste ce genre d’applis ?

L’AppStore est la plateforme de distribution d’applications pour iPhone, lancée par Apple il y a un peu moins d’un an au travers de son magasin en ligne iTunes Music Store. Il permet à n’importe qui de potentiellement vendre son application aux millions de possesseurs d’iPhone. Indéniable succès : le magasin virtuel propose à ce jour environ 35.000 applications et vient de fêter son premier milliard de téléchargements. Outre des éditeurs du monde Mac, on trouve également sur l’AppStore de grands éditeurs de jeux vidéo (Vivendi, Electronic Arts) et nombre d’indépendants espérant faire fortune dans ce nouvel eldorado.

Prêts à vous lancer dans l’aventure ou juste curieux ? Cet article montre comment développer et distribuer une application iPhone avec les outils du SDK (Software Development Kit).

1. Quelques pré-requis

Un mac

Avant de se lancer tête baissée dans le code, il convient de rappeler certains pré-requis. L’iPhone utilise comme système d’exploitation une version allégée de Mac OS X et Apple a construit l’ensemble du kit de développement autour de sa plateforme de développement existante : XCode pour l’éditeur de code, Interface Builder (ici nommé IB) pour la création d’interfaces en WYSIWYG, Cocoa Touch une surcouche de Cocoa pour les APIs et enfin Objective-C pour le langage.

Le SDK est téléchargeable gratuitement et le site Apple Developer Connection regorge de documentation, de vidéos et d’exemples. Même un parfait novice peut commencer à développer pour peu qu’il soit motivé.

Un iPhone

Le SDK fournit un simulateur d’iPhone, mais ce dernier est assez limité en termes de fonctionnalités. Par exemple, il ne permet pas de faire de la 3D ou de recevoir les évènements de l’accéléromètre. Un iPhone est par conséquent obligatoire, d’autant plus que certains bugs peuvent n’apparaître que sur l’appareil.

…et un certificat

Abordons la partie qui fâche : pour développer sur iPhone, il faut payer. Avant de pouvoir, ne serait-ce que tester une application sur son propre iPhone, il est nécessaire de souscrire à l’« iPhone Developer Program » pour la modique somme de 99$ par an. Ce programme donne accès à un portail Web où il est possible de générer un certificat permettant de signer une application et ainsi de la déployer.

2. Ouvrons le capot

Nous allons développer une « boîte à meuh », ce n’est pas novateur mais nécessite plusieurs des APIs de l’iPhone, à savoir les animations, l’audio et l’accéléromètre. Ce chapitre présente à la fois les premiers outils du SDK et les grands concepts du développement iPhone, les plus pressés pourront se rendre au chapitre suivant où la partie code démarre réellement.

Créons un nouveau projet « Moo » dans XCode avec le modèle « View-Based Application ». XCode se charge de créer la structure minimale du projet :

Figure 1 - XCode

L’éternel main

Même si à ce stade l’application ne fait qu’afficher une fenêtre grise, regardons de plus près ce qu’il se passe lorsqu’on lance le programme. Classiquement en C et ici Objective-C, le point de départ d’une application est le fichier main.m :

int main(int argc, char *argv[]) {
    
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

Notre application iPhone est lancée par l’invocation de la fonction UIApplicationMain, elle fait partie de UIKit, un des frameworks composant Cocoa Touch. Elle est précédée par la création d’un pool mémoire NSAutoreleasePool, qui sera détruit une fois que la fonction UIApplicationMain aura rendu la main, c'est-à-dire une fois que le programme aura quitté. On peut donc déjà voir ici que la gestion de la mémoire est entièrement à la charge du développeur et c’est le cas pour toutes les applications Objective-C. Il est nécessaire d’être très rigoureux pour ne pas risquer de fuites mémoire, mais en contrepartie on obtient de meilleures performances.

InterfaceBuilder entre en scène

Une fois cette méthode lancée, elle va charger le fichier Info.plist. Il s’agit d’un ensemble de clés-valeur au format XML, définissant différentes propriétés de l’application comme son nom, sa version ou bien encore l’icône à afficher dans SpringBoard. L’une de ces propriétés nous intéresse ici :

<key>NSMainNibFile</key>
<string>MainWindow</string>

C’est cette clé qui fait le lien avec InterfaceBuilder, elle spécifie le nom de l’interface à charger. On trouve donc assez logiquement le fichier MainWindow.xib dans les ressources de notre projet. Double-cliquons dessus pour lancer IB.

Figure 2 - InterfaceBuilder

IB permet de créer une interface graphique par drag&drop de composants, dont plusieurs catégories sont dédiées à l’iPhone. Son côté intéressant est qu’il est complètement dissocié du code : il permet de créer une définition d’interface stockée dans un fichier XML (.xib) qui au moment de la compilation est transformée en un graphe d’objets sérialisés remontés en mémoire au chargement de l’application. Pour pouvoir interagir avec ces composants, on branche la référence d’un composant graphique à un pointeur dans le code ou bien un évènement généré par l’interface à une fonction. De cette façon les deux mondes code et interface sont complètement dissociés et peuvent évoluer indépendamment.

La hiérarchie d’objets déjà présente va nous permettre d’expliquer quelques concepts de programmation iPhone :

  • File’s Owner : c’est celui qui « détient » ce fichier xib et n’est pas créé à son chargement, dans notre cas il s’agit du singleton UIApplication.
  • First Responder : le premier objet à être invoqué lorsqu’un événement est déclenché.
  • MooAppDelegate : cette classe fait partie de notre projet et elle implémente UIApplicationDelegate, c’est à dire qu’elle peut recevoir certains messages envoyés à l’application. Il s’agit d’un design pattern très utilisé dans Cocoa : la délégation permet à un objet de recevoir des messages spécifiés par un protocole, ici UIApplicationDelegate, il s’agit d’une alternative à l’héritage de classe pour faire de la réutilisation.
  • MooViewController : un autre design pattern, plus classique MVC. Détail intéressant, sa vue est définie dans un autre fichier xib, ceci permet de découper l’interface en autant de fichiers que nécessaires qui peuvent être chargés tardivement et améliore le travail collaboratif sur ces fichiers.
  • Window : la fenêtre principale.

Retour au code

L’interface graphique va donc être chargée en mémoire, et La méthode applicationDidFinishLaunching est ensuite invoquée sur la classe MooAppDelegate avec la variable window pointant sur le bon objet en mémoire, comme par magie.

@implementation MooAppDelegate

@synthesize window;
@synthesize viewController;


- (void)applicationDidFinishLaunching:(UIApplication *)application {    
    
    // Override point for customization after app launch    
    [window addSubview:viewController.view];
    [window makeKeyAndVisible];
}

- (void)dealloc {
    [viewController release];
    [window release];
    [super dealloc];
}

@end

3. Du code, du code !

L’interface graphique

Maintenant ces concepts expliqués, il est temps de se lancer dans l’ajout de fonctionnalités à notre boîte à meuh. Pour commencer nous allons faire en sorte d’afficher une image, qui jouera un son lorsqu’elle sera cliquée, il faudrait plutôt dire « touchée » puisqu’il n’y a pas de souris, vous me pardonnerez cet abus de langage.

Dans XCode, on commence par rajouter une image dans les ressources du projet : Figure 3 – Ressource graphique

Editons maintenant notre fichier MooViewController.xib dans IB où l’on rajoute un nouveau bouton (Round Rect Button). On aurait pu simplement rajouter une ImageView mais les boutons permettent plus simplement de recevoir des évènements utilisateur. On spécifie ensuite le type du bouton à Custom et son arrière plan à l’image que nous avions rajouté plus haut.

Figure 4 – Custom button

Pour que notre code puisse être invoqué lorsque la vache est cliquée, il faut maintenant faire le lien entre XCode et IB. Nous allons donc rajouter la fonction suivante dans XCode : MooViewController.h :

-(IBAction)cowClicked:(id)sender;

MooViewController.m :

-(IBAction)cowClicked:(id)sender {	
	NSLog(@"Cow clicked");
}

Compilez le projet et repassez dans IB, sélectionnez le « File’s Owner » qui est notre MooViewController et et dans la fenêtre d’information ouvrez le deuxième onglet qui correspond aux connections de l’objet. Vous pouvez voir maintenant l’action « cowClicked » que nous venons de rajouter.

Branchons maintenant notre bouton à cette action, pour cela il suffit de faire glisser le rond en face de l’action sur notre bouton. Une fenêtre apparaît alors affichant les différentes actions disponibles : Figure 5 – Connexion de l’action

On choisit ensuite « Touch Up Inside » qui correspond à un click sur le bouton. Notre programme va pouvoir maintenant recevoir ce type d’événement.

Animons tout ça

CoreAnimation est l’une des briques de base de Mac OS X qui rendent ce système si riche visuellement, il est à la base par exemple de Spaces ou bien encore Exposé. C’est un élément indispensable pour maximiser l’expérience utilisateur, et à mon sens l’un des ingrédients du succès de l’iPhone. Ne nous privons donc pas de l’utiliser, nous allons simuler le mouvement d’une boîte à meuh avec une double rotation lorsqu’elle est cliquée.

Pour le côté technique, CoreAnimation est une API bas niveau permettant d’appliquer des matrices de transformations sur un layer. Heureusement pour nous Apple a simplifié son utilisation dans le SDK iPhone en rajoutant des appels directement sur la classe UIView (la classe parente des contrôles graphiques).

Modifions donc notre callback cowClicked de manière à effectuer une première rotation de 180° et ensuite à revenir à la position initiale. Cette action sera lancée sur la vue de notre bouton qui doit d’abord être déclarée au niveau de la classe MooViewController et branchée dans IB.

MooViewController.h :

@interface MooViewController : UIViewController {
	UIButton *cowButton;
}
@property (nonatomic, retain) IBOutlet UIButton *cowButton;
...

MooViewController.m :

@implementation MooViewController

@synthesize cowButton;

...

-(IBAction)cowClicked:(id)sender {
	
	NSLog(@"Cow clicked");
	
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:0.3];
	[UIView setAnimationBeginsFromCurrentState:YES];
	[UIView setAnimationDelegate:self];
	[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
	[UIView setAnimationDidStopSelector:@selector(endCowAnimation)]; // Callback de fin
	cowButton.transform = CGAffineTransformRotate(cowButton.transform, M_PI); // Rotation de 180 degrés
	[UIView commitAnimations];
}


-(void)endCowAnimation {
	
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:1];
	[UIView setAnimationBeginsFromCurrentState:YES];
	[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
	cowButton.transform = CGAffineTransformRotate(cowButton.transform, -M_PI); // Rotation inverse
	[UIView commitAnimations];
}

Un bloc animation se déclare de la manière suivante :

  • L’initialisation avec l’appel [UIView beginAnimations]
  • La possibilité de paramétrer cette animation : durée, vélocité (vitesse constante, accélération sur la fin et/ou début), callback appelé à la fin de l’animation permettant d’enchainer plusieurs transformations, etc
  • La ou les transformations que l’on applique aux vues via leur propriété transform (elles sont appliquées par rapport à la position initiale)
  • La fin du bloc et le lancement de l’animation à l’appel [UIView commitAnimations]

Ce qui donne : Figure 6 – Rotation

Du son

Il ne reste plus maintenant qu’à rajouter un son pour avoir une première version de notre exemple. Tout d’abord on rajoute un fichier son moo.wav dans XCode par simple drag & drop. Dans la section Frameworks on pointe vers une nouvelle bibliothèque gérant ce genre de fonctionnalités : AudioToolbox.

On modifie ensuite le fichier MooViewController.h pour rajouter l’import approprié et une référence vers un objet SystemSoundID qui représente une ressource sonore.

MooViewController.h :

#include <audiotoolbox /AudioToolbox.h>

@interface MooViewController : UIViewController {
	...
	SystemSoundID mooSound;
}
...
</audiotoolbox>

Dans l’implémentation MooAppDelegate.m on initialise cette ressource sonore et on la lance dans notre méthode cowClicked :

MooViewController.m :

...
- (void)viewDidLoad {
    // Initialize the Moo sound
    id soundPath=[[NSBundle mainBundle] pathForResource:@"moo" ofType:@"wav"];
    CFURLRef baseURL=(CFURLRef) [[NSURL alloc] initFileURLWithPath:soundPath];
    AudioServicesCreateSystemSoundID(baseURL, &mooSound);
    CFRelease(baseURL);
	
}

- (void)dealloc {
	AudioServicesDisposeSystemSoundID(mooSound);
    [super dealloc];
}

-(IBAction)cowClicked:(id)sender {
	...
	AudioServicesPlayAlertSound(mooSound);
	...
}

La touche finale : l’accéléromètre

Notre vache est maintenant capable de se retourner et d’émettre un son délicat lorsqu’on la clique, mais un exemple de boîte à meuh ne serait pas complet sans pousser jusqu’à l’une des fonctionnalités les plus fun de l’iPhone : l’accéléromètre.

Il existe un objet global [UIAccelerometer sharedAccelerometer] envoyant périodiquement des évènements au travers d’une méthode contenant en paramètre l’accélération sur les 3 axes x, y et z. Il nous suffira de répondre au protocole UIAccelerometerDelegate et de spécifier à l’accéléromètre que notre contrôleur est son delegate.

Le comportement d’une boite à meuh est de sonner une fois qu’on l’a retournée puise remise à l’endroit, nous allons donc le détecter et déclencher notre méthode cowClicked comme ci-dessous :

#define kUpdateFrequency 20  // Hz
#define kFilteringFactor 0.05
BOOL upsideDown = NO;
float accelX = 0;
float accelY = 0;

- (void)viewDidLoad {
     ...
	// Request events from the accelerometer
    [[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / kUpdateFrequency)];
    [[UIAccelerometer sharedAccelerometer] setDelegate:self];	
}

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
	
    // Filter events with a low pass filter
    accelX = acceleration.x * kFilteringFactor + accelX * (1.0 - kFilteringFactor);
    accelY = acceleration.y * kFilteringFactor + accelY * (1.0 - kFilteringFactor);
    float angle = 180 * atan2(accelY, accelX) / M_PI;
	
    NSLog(@"angle : %f, (accels x / y): %f, %f", angle, accelX, accelY);
	
    if (angle > 10) {
        upsideDown = YES;
    }
    else if (angle < -10 && upsideDown == YES) {
        upsideDown = NO;
        [self cowClicked:nil];
    }
}
...

4. Optimisation et distribution

Notre boite à meuh est maintenant terminée, on arrive finalement à coder beaucoup de fonctionnalités en écrivant peu de lignes de code, signe que les APIs sont de bonne qualité. Nous arrivons donc aux étapes finales : les optimisations et enfin la distribution de l’application.

Les outils d’aide au diagnostic

La phase d’optimisation n’est pas à sous estimer, rappelez vous que la gestion de la mémoire est à la charge du développeur et une fuite mémoire est un motif suffisant pour être refusé sur l’AppStore. Fort heureusement, le SDK fournit plusieurs outils d’aide au diagnostic :

  • Instruments permet de collecter des métriques sur les ressources de l’iPhone : CPU, consommation mémoire, etc.
  • Shark permet d’avoir une vue sur ce qui se passe au niveau système : threads, interrupts, appels système

Figure 7 – Instruments

Figure 8 – Shark

Ad-Hoc, In-House et AppStore

La distribution d’applications passe par l’utilisation d’iTunes qui permet de synchroniser les iPhone ou bien encore de déployer les certificats et applications, Program Portal un portail web pour gérer ses applications et enfin iTunes Connect un autre portail dédié à l’AppStore.

Avant tout il existe un mode de distribution nommé « ad-hoc » qui permet de distribuer directement son application à 100 personnes maximum dans le but de réaliser du beta testing. Program Portal permet d’associer un identifiant iPhone à une application et génère un certificat à installer sur l’appareil en question avec l’application via iTunes.

« In-house » est un mode de distribution réservé aux grandes entreprises qui peuvent par ce biais gérer complètement la distribution d’applications maison.

Et enfin, l’AppStore est la plateforme de distribution grand public gérée entièrement par Apple. N’importe qui peut ainsi bénéficier de toute la puissance marketing d’Apple et d’un canal de vente gratuit. Une fois l’application prête, elle doit être soumise par le biais d’iTunes Connect et ce sont les employés d’Apple qui décideront si oui ou non elle peut rejoindre l’AppStore et surtout quand elle le sera. Ce processus peut se révéler très frustrant, les causes de refus étant multiples : ergonomie en désaccord avec les principes d’Apple, mauvaises performances ou tout simplement jugée de mauvais goût, les exemples sont légions sur le net.

Figure 9 – iTunes Connect

5. Le mot de la fin

Ce tour d’horizon du développement d’applications iPhone est maintenant terminé, pourtant nous n’avons qu’effleuré la surface tant il est vaste : le développement d’applications Web optimisées iPhone, la géo-localisation, ou encore la 3D sont autant d’autres sujets intéressants à creuser…