Les Nouveautés de Swift 2
Swift a été annoncé par Apple il y a seulement un an et pourtant il est déjà présent partout. Si, à l’époque, l’article que nous avions publié à ce sujet s’adressait surtout aux curieux, il a eu un succès important et aujourd’hui la plupart des développeurs semble confiante sur le fait de démarrer un projet iOS ou OS X en Swift. Plusieurs conférences sur le langage ont même eu lieu, comme la dotSwift en mars dernier à Paris. Il est déjà utilisé dans des applications en production, comme celle de Coursera, et nous avons commencé à l’utiliser aussi !
Après un an chargé en itérations rapides, le langage a fait l’objet d’évolutions considérables annoncées à l’occasion de la WWDC qui a eu lieu la semaine dernière à San Francisco. Apple semble prendre ce sujet au sérieux et confirme explicitement que Swift est bel et bien l’avenir de sa plate-forme.
Voyons de plus près les nouveautés les plus notables de cette version 2.
Open Source !
Avec seulement un an d’ancienneté, Swift est déjà dans le top 15 du classement TIOBE et remonte comme le langage le plus aimé dans sondage Stack Overflow. Et pourtant il promettait jusqu’à présent de ne servir qu’à développer des applications iPhone, iPad, OS X et AppleWatch, avec Apple comme seul et unique contributeur.
Alors imaginez ce qui pourra arriver après qu’Apple ait ouvert le langage. D’ici la fin de l’année, les sources du compilateur et des bibliothèques standards seront diffusées pour iOS, OS X et Linux, et Apple acceptera les contributions de la communauté.
Attention, ça ne veut pas dire que vous pourrez développer des applications iPhone sur Linux, de nombreuses API du Framework restant encore sous scellés.
Gestion des erreurs
Enfin ! diront certains.
Le langage Swift ne proposait aucun moyen pour gérer les erreurs, et la convention était d’ailleurs d’utiliser un tuple ou un enum
avec valeur associée dans les cas où “je vais peut-être retourner quelque chose, ou bien une erreur.”
Pour aider le compilateur à identifier les fonctions qui peuvent renvoyer des erreurs, on les marque avec le mot-clé throws
.
func thisCanFail() throws {
…
}
Lors de l’appel à cette fonction, le compilateur va renvoyer une erreur et demander l’usage de try
, mais il ne s’utilise pas comme le trycatch
auquel on peut être habitué. Ici, il est plus utilisé comme mot-clé pour communiquer explicitement le fait que l’appel peut rater.
func higherFunction throws {
try thisCanFail()
doSomethingAfter()
}
Dans le bout de code suivant, je ne fais que remonter l’erreur plus haut. J’ai marqué ma fonction higherFunction
comme ayant la possibilité de lever une erreur, mais comment puis-je gérer mon erreur moi-même ?
func higherFunction() throws -> Bool {
do {
try thisCanFail()
}
catch {
print("Oops !")
return false
}
doSomethingAfter()
return true
}
Et je peux utiliser du Pattern Matching dans la clause catch
pour spécifier le type d’erreur que je veux gérer. À propos des types d’erreurs, n’importe quel type (struct
, enum
, class
) qui se conforme au protocole ErrorType
peut être envoyé. Une enum
avec valeur associée marche très bien pour ça bien entendu, et NSError se conforme déjà à ce protocole.
Et si je dois exécuter du code quel que soit la raison pour laquelle je sors du contexte ? Avertir un délégué, par exemple.
Pour ceci je peux utiliser defer
:
func higherFunction() throws -> Bool {
defer { doSomethingAfterNoMatterWhat() }
do {
try thisCanFail()
}
catch {
print("Oops !")
return false
}
try thisCanAlsoFail()
doSomethingAfter()
return true
}
Ce qui se trouve dans le defer
sera exécuté en sortie de contexte quoi qu’il arrive.
Apple garantit que l’usage de ce nouveau système ne plombera pas les performances, qui sont annoncées similaires à l’utilisation de conditionnels en if
. A tester !
Availability Checking
On ne sait pas exactement comment mais Apple parvient à faire avancer son parc installé vers des nouvelles versions de l’OS à une vitesse fulgurante, si bien que de nombreux développeurs utilisent sans scrupule des API disponibles seulement aux dernières moutures.
Et faire le grand écart entre la version n et la version n-2 peut s’avérer très compliqué. En effet, il faut traquer soi-même ces usages d’API récentes, avant de les gérer avec des conditions bricolées et/ou l’utilisation de respondsToSelector
. Et comme on en loupe toujours, il y a des plantages à l’exécution dans la nature et on a le cafard.
Bref ce n’est pas l’idéal, du coup de nombreux développeurs finissent par déserter très vite les vieilles versions de l’OS et les utilisateurs sont malheureux.
J’ai une bonne nouvelle : nous allons tous pouvoir souffler. D’une part, parce que le nouveau compilateur va surveiller les versions cibles de notre application et nous avertir automatiquement à l’utilisation d’une API qui ne serait pas gérée par toutes ces versions.
D’autre part, parce qu’une fois le problème ciblé, on a des moyens de le résoudre beaucoup plus facilement qu’avant.
Pour mettre un appel dans une condition, il existe à présent une fonction #available()
, qui prend une liste de versions d’OS et que l’on peut utiliser dans un if
ou un guard
(j’y reviendrai) pour s’assurer que notre code ne s’exécute que sur l’environnement qui le gère.
if #available (IOS 8, *) {
…
}
Vous pouvez aussi calmer le compilateur en utilisant @available()
pour marquer tout une fonction ou une classe comme n’étant compatible qu’à partir d’une certaine version de l’OS. Ainsi le problème va remonter un cran au-dessus et vous pourrez le gérer de manière plus factorisée, par exemple.
On dit “ouf !”
Pattern Matching
Le Pattern Matching est une de ces fonctionnalités du langage qui manquent quand on ne les a plus. Je l’ai utilisé à quelques reprises mais je suis sûr de sous-exploiter encore son potentiel.
Il était d’abord principalement utilisé dans le switchcase
, dont il décuplait les possibilités en permettant de faire des choses comme ce système d’aiguillage en fonction d’un indexPath
pour une UITableView
switch (indexPath.section, indexPath.row) {
case (0, let row):
…
case (1, let row):
…
default:
}
Il peut maintenant être utilisé directement dans une clause if
avec la nouvelle clause if case
, comme ceci par exemple :
if case let (x, y) = getPoint() where x > 0 {
…
}
Ce qui permet d’avoir accès à la puissance du Pattern Matching sans avoir à faire un switchcase
alambiqué. Pas mal aussi, l’utilisation dans un while
, ou encore mieux un forin
:
for number in numbers where number % 2 == 0 {
…
}
Guard
Vous pouvez aussi l’utiliser dans une nouvelle clause appelée guard
, qui permet de traiter très facilement les sorties précoces.
guard let name = json["name"] as? String else {
return .None("missing name")
}
Le code à l’intérieur de la clause else
doit absolument sortir du scope, par un return
, throw
, break
,… L’intérêt de guard
est de garantir au compilateur qu’il peut partir du principe que ce test est passé pour la suite de la méthode ou du scope. Dans cet exemple, vous pouvez utiliser la variable name
sans avertissement. Et puis ça fait une belle convention pour traiter les cas d’exception, non ?
Si le Pattern Matching vous intéresse, je vous invite à lire la page de la doc sur le sujet.
Protocol Extensions
Vous connaissez déjà la possibilité de Swift d’ajouter des méthodes à des types à l’aide d’extensions (on les appelait catégories, en Objective-C).
Mais Swift utilise énormément les protocoles, notamment SequenceTypeCollectionType
sur lequel s’appuient les fonctions génériques comme filter
, find
ou map
. Ces fonctions sont donc de haut ordre et difficiles à chaîner, comme en Java 8 avec les Streams
par exemple. Apple avait dû créer des méthodes pour certaines classes, arbitrairement (Array
mais pas Set
par exemple).
Il est à présent possible de rajouter des méthodes à des protocoles, pour les rendre accessibles à tous les types qui respectent ce protocole. Oui, vos protocoles peuvent maintenant intégrer du comportement !
Les possibilités de conception offertes par ce nouveau principe sont assez vertigineuses. Il y avait même une session dédiée aux Protocols à la WWDC, intitulée Protocol-Oriented Programming (45:51).
Et les fonctions génériques de SequenceType
et CollectionType
sont maintenant disponibles dans des extensions de protocoles dans Swift 2 pour faciliter le chaînage.
Xcode
Il y a encore plein de petites pépites comme les OptionSets
et l’unification de la façon d’appeler les méthodes et les fonctions. Mais pour finir, je vous propose de voir quelques nouveautés de Xcode, l’IDE qui a fait du chemin depuis la version 5 !
Bien entendu l’assistant de migration a été amélioré, il faut bien ça pour un langage qui bouge aussi vite. Mais le compilateur va se permettre de vous aider à écrire du meilleur code avec des avertissements tels que :
- avertissement si vous assignez une valeur à une
var
et qu’elle n’est plus jamais changée par la suite, autant utiliserlet
- avertissement quand vous utilisez une fonction de haut ordre et que vous ne récupérez pas son résultat
Et aussi des messages d’erreur plus clairs, avec des propositions de correction plus pertinentes.
Enfin, les Playgrounds, qui permettent de jouer avec du code en live, sont encore plus puissants. On avait déjà rajouté les commentaires Markdown et les ressources associées, maintenant un playground peut avoir plusieurs pages avec des ressources partagées. J’y vois un sacré potentiel pour rédiger des tutoriels ou des cours interactifs.
Pour que tout ça soit disponible au tout-venant, il faudra attendre que Xcode 7 sorte en version finale, probablement autour de l’automne, comme tous les ans. En attendant, les détenteurs d’un compte Apple Developer payant peuvent télécharger la version beta et faire joujou avec toutes ces nouveautés !
Dans tous les cas, c’est sûrement le meilleur moment pour se mettre à apprendre Swift, qui semble avoir un bel avenir devant lui !
Pour en savoir plus :
- L’article sur Swift 2 sur le Blog du langage
- La vidéo de la session What’s New in Swift de la WWDC (50:16)
- Télécharger Xcode 6 ou Xcode 7 beta (il vous faudra un login Apple Developer pour la version beta)