La plupart des projets iOS passent partiellement ou entièrement à Swift. Swift est un excellent langage, et c'est avec lui que réside l'avenir du développement iOS. Mais le langage est inextricablement lié à la boîte à outils, et il y a des inconvénients à la boîte à outils Swift.
Il y a encore des bogues dans le compilateur Swift qui le font planter ou génèrent du code incorrect. Swift n'a pas d'ABI stable. Et, ce qui est très important, les projets Swift durent depuis trop longtemps.
À cet égard, les projets existants pourraient être plus rentables pour poursuivre le développement de l'objectif-C. Et Objective-C n'est plus ce qu'il était!
Dans cette série d'articles, nous allons vous montrer les fonctionnalités utiles et les améliorations d'Objective-C, avec lesquelles il devient beaucoup plus agréable d'écrire du code. Tous ceux qui écrivent dans Objective-C trouveront quelque chose d'intéressant pour eux-mêmes.

let
et var
Objective-C n'a plus besoin de spécifier explicitement les types de variables: dans Xcode 8, l'extension de langage __auto_type
est apparue, et avant que l'inférence de type Xcode 8 ne soit disponible dans Objective-C ++ (en utilisant le mot auto
clé auto
avec l'avènement de C ++ 0X).
Tout d'abord, ajoutons les macros let
et var
:
#define let __auto_type const #define var __auto_type
Si vous aviez l'habitude d'écrire const
après un pointeur sur une classe Objective-C, c'était un luxe inadmissible, mais maintenant la déclaration implicite de const
(via let
) a été prise pour acquise. La différence est particulièrement visible lors de l'enregistrement d'un bloc dans une variable.
Pour nous, nous avons développé une règle pour utiliser let
et var
pour déclarer toutes les variables. Même lorsqu'une variable est initialisée à nil
:
- (nullable JMSomeResult *)doSomething { var result = (JMSomeResult *)nil; if (...) { result = ...; } return result; }
La seule exception est lorsque vous devez vous assurer qu'une variable se voit attribuer une valeur dans chaque branche de code:
NSString *value; if (...) { if (...) { value = ...; } else { value = ...; } } else { value = ...; }
Ce n'est que de cette manière que nous obtiendrons un avertissement du compilateur si nous oublions d'affecter une valeur à l'une des branches.
Et enfin: pour utiliser let
et var
pour les variables de type id
, vous devez désactiver l'avertissement auto-var-id
(ajoutez -Wno-auto-var-id
aux "Autres drapeaux d'avertissement" dans les paramètres du projet).
Valeur de retour du type de bloc automatique
Peu de gens savent que le compilateur peut déduire le type de la valeur de retour d'un bloc:
let block = ^{ return @"abc"; };
C'est très pratique. Surtout si vous écrivez du code réactif à l'aide de ReactiveObjC . Mais il existe un certain nombre de restrictions en vertu desquelles vous devez spécifier explicitement le type de la valeur de retour.
S'il y a plusieurs return
dans un bloc qui renvoient des valeurs de différents types.
let block1 = ^NSUInteger(NSUInteger value){ if (value > 0) { return value; } else {
S'il y a une return
dans le bloc qui retourne nil
.
let block1 = ^NSString * _Nullable(){ return nil; }; let block2 = ^NSString * _Nullable(BOOL flag) { if (flag) { return @"abc"; } else { return nil; } };
Si le bloc doit retourner BOOL
.
let predicate = ^BOOL(NSInteger lhs, NSInteger rhs){ return lhs > rhs; };
Les expressions avec un opérateur de comparaison en C (et donc en Objective-C) sont de type int
. Par conséquent, il vaut mieux en faire une règle pour toujours spécifier explicitement le type de retour BOOL
.
Génériques et for...in
Dans Xcode 7, des génériques (ou plutôt des génériques légers) sont apparus dans Objective-C. Nous espérons que vous les utilisez déjà. Sinon, vous pouvez regarder la session de la WWDC ou la lire ici ou ici .
Pour nous, nous avons développé une règle pour toujours spécifier des paramètres génériques, même si c'est id
( NSArray<id> *
). Ainsi, il est facile de distinguer le code hérité dans lequel les paramètres génériques ne sont pas encore spécifiés.
Avec les macros let
et var
, nous nous attendons à pouvoir les utiliser dans une boucle for...in
:
let items = (NSArray<NSString *> *)@[@"a", @"b", @"c"]; for (let item in items) { NSLog(@"%@", item); }
Mais un tel code ne se compile pas. Très probablement, __auto_type
pas pris en charge dans for...in
, car for...in
ne fonctionne qu'avec les collections qui implémentent le protocole NSFastEnumeration
. Et pour les protocoles dans Objective-C, il n'y a pas de support pour les génériques.
Pour corriger cet inconvénient, essayez de créer votre macro foreach
. La première chose qui vient à l'esprit: toutes les collections de Foundation ont une propriété objectEnumerator
, et la macro pourrait ressembler à ceci:
#define foreach(object_, collection_) \ for (typeof([(collection_).objectEnumerator nextObject]) object_ in (collection_))
Mais pour NSDictionary
et NSMapTable
méthode du protocole NSMapTable
NSFastEnumeration
sur les clés, pas sur les valeurs (vous devrez utiliser keyEnumerator
, pas objectEnumerator
).
Nous devrons déclarer une nouvelle propriété qui ne sera utilisée que pour obtenir le type dans l'expression typeof
:
@interface NSArray<__covariant ObjectType> (ForeachSupport) @property (nonatomic, strong, readonly) ObjectType jm_enumeratedType; @end @interface NSDictionary<__covariant KeyType, __covariant ObjectType> (ForeachSupport) @property (nonatomic, strong, readonly) KeyType jm_enumeratedType; @end #define foreach(object_, collection_) \ for (typeof((collection_).jm_enumeratedType) object_ in (collection_))
Maintenant, notre code est bien meilleur:
Extrait pour Xcode foreach (<#object#>, <#collection#>) { <#statements#> }
Génériques et copy
/ mutableCopy
Un autre endroit où la saisie n'est pas disponible dans Objective-C est les -mutableCopy
et -mutableCopy
(ainsi que les -copyWithZone:
et -mutableCopyWithZone:
mais nous ne les appelons pas directement).
Pour éviter d'avoir besoin de transtypages explicites, vous pouvez redéclarer les méthodes avec le type de retour. Par exemple, pour NSArray
déclarations seraient:
@interface NSArray<__covariant ObjectType> (TypedCopying) - (NSArray<ObjectType> *)copy; - (NSMutableArray<ObjectType> *)mutableCopy; @end
let items = [NSMutableArray<NSString *> array];
warn_unused_result
Puisque nous avons re-déclaré les -mutableCopy
-copy et -mutableCopy
, il serait bien de garantir que le résultat de l'appel de ces méthodes sera utilisé. Pour ce faire, Clang a l'attribut warn_unused_result
.
#define JM_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
@interface NSArray<__covariant ObjectType> (TypedCopying) - (NSArray<ObjectType> *)copy JM_WARN_UNUSED_RESULT; - (NSMutableArray<ObjectType> *)mutableCopy JM_WARN_UNUSED_RESULT; @end
Pour le code suivant, le compilateur générera un avertissement:
let items = @[@"a", @"b", @"c"]; [items mutableCopy];
overloadable
Peu de gens savent que Clang vous permet de redéfinir des fonctions en langage C (et donc en Objective-C). En utilisant l'attribut overloadable
, vous pouvez créer des fonctions avec le même nom, mais avec différents types d'arguments ou avec des nombres différents.
Les fonctions remplaçables ne peuvent pas différer uniquement par le type de la valeur de retour.
#define JM_OVERLOADABLE __attribute__((overloadable))
JM_OVERLOADABLE float JMCompare(float lhs, float rhs); JM_OVERLOADABLE float JMCompare(float lhs, float rhs, float accuracy); JM_OVERLOADABLE double JMCompare(double lhs, double rhs); JM_OVERLOADABLE double JMCompare(double lhs, double rhs, double accuracy);
Expressions encadrées
En 2012, lors d'une session WWDC 413, Apple a présenté des littéraux pour NSNumber
, NSArray
et NSDictionary
, ainsi que des expressions encadrées. Vous pouvez en savoir plus sur les littéraux et les expressions encadrées dans la documentation de Clang .
En utilisant des littéraux et des expressions encadrées, vous pouvez facilement obtenir un objet représentant un nombre ou une valeur booléenne. Mais pour obtenir un objet qui enveloppe la structure, vous devez écrire du code:
Des méthodes et propriétés d'assistance sont définies pour certaines classes (comme la méthode +[NSValue valueWithCGPoint:]
et les propriétés CGPointValue
), mais ce n'est toujours pas aussi pratique qu'une expression encadrée!
Et en 2015, Alex Denisov a créé un patch pour Clang, vous permettant d'utiliser des expressions encadrées pour envelopper toutes les structures dans NSValue
.
Pour que notre structure objc_boxable
en charge les expressions encadrées, il vous suffit d'ajouter l'attribut objc_boxable
pour la structure.
#define JM_BOXABLE __attribute__((objc_boxable))
typedef struct JM_BOXABLE JMDimension { JMDimensionUnit unit; CGFloat value; } JMDimension;
Et nous pouvons utiliser la syntaxe @(...)
pour notre structure:
let dimension = (JMDimension){ ... }; let boxedValue = @(dimension);
Vous devez toujours -[NSValue getValue:]
structure via la méthode -[NSValue getValue:]
ou la méthode category.
CoreGraphics définit sa propre macro, CG_BOXABLE
et les expressions encadrées sont déjà prises en charge pour les CGPoint
, CGSize
, CGVector
et CGRect
.
Pour d'autres structures couramment utilisées, nous pouvons ajouter nous-mêmes la prise en charge des expressions encadrées:
typedef struct JM_BOXABLE _NSRange NSRange; typedef struct JM_BOXABLE CGAffineTransform CGAffineTransform; typedef struct JM_BOXABLE UIEdgeInsets UIEdgeInsets; typedef struct JM_BOXABLE NSDirectionalEdgeInsets NSDirectionalEdgeInsets; typedef struct JM_BOXABLE UIOffset UIOffset; typedef struct JM_BOXABLE CATransform3D CATransform3D;
Littéraux composés
Une autre construction de langage utile est le littéral composé . Les littéraux composés sont apparus dans GCC en tant qu'extension de langage et ont ensuite été ajoutés à la norme C11.
Si plus tôt, après avoir rencontré l'appel à UIEdgeInsetsMake
, nous ne pouvions que deviner quelle indentation nous obtiendrions (nous devions regarder la déclaration de la fonction UIEdgeInsetsMake
), puis avec les littéraux composés, le code parle de lui-même:
Il est encore plus pratique d'utiliser une telle construction lorsque certains des champs sont nuls:
(CGPoint){ .y = 10 }
Bien sûr, dans les littéraux composés, vous pouvez utiliser non seulement des constantes, mais aussi toutes les expressions:
textFrame = (CGRect){ .origin = { .y = CGRectGetMaxY(buttonFrame) + textMarginTop }, .size = textSize };
Extraits pour Xcode (NSRange){ .location = <#location#>, .length = <#length#> } (CGPoint){ .x = <#x#>, .y = <#y#> } (CGSize){ .width = <#width#>, .height = <#height#> } (CGRect){ .origin = { .x = <#x#>, .y = <#y#> }, .size = { .width = <#width#>, .height = <#height#> } } (UIEdgeInsets){ .top = <#top#>, .left = <#left#>, .bottom = <#bottom#>, .right = <#right#> } (NSDirectionalEdgeInsets){ .top = <#top#>, .leading = <#leading#>, .bottom = <#bottom#>, .trailing = <#trailing#> } (UIOffset){ .horizontal = <#horizontal#>, .vertical = <#vertical#> }
Annulation
Dans Xcode 6.3.2, les annotations de nullité sont apparues dans Objective-C. Les développeurs Apple les ont ajoutés pour importer l'API Objective-C dans Swift. Mais si quelque chose est ajouté à la langue, vous devriez essayer de le mettre à votre service. Et nous expliquerons comment nous utilisons la nullité dans un projet Objective-C et quelles sont les limitations.
Pour rafraîchir vos connaissances, vous pouvez regarder la session WWDC .
La première chose que nous avons faite a été de commencer à écrire les NS_ASSUME_NONNULL_END
NS_ASSUME_NONNULL_BEGIN
/ NS_ASSUME_NONNULL_END
dans tous les fichiers .m
. Afin de ne pas le faire à la main, nous corrigeons les modèles de fichiers directement dans Xcode.
Nous avons également commencé à définir correctement la nullité pour toutes les propriétés et méthodes privées.
Si nous ajoutons les macros NS_ASSUME_NONNULL_BEGIN
/ NS_ASSUME_NONNULL_END
à un fichier .m
existant, nous ajoutons immédiatement les null_resettable
, null_resettable
et _Nullable
dans l'ensemble du fichier.
Tous les avertissements utiles du compilateur de nullité sont activés par défaut. Mais il y a un avertissement extrême que je voudrais inclure: -Wnullable-to-nonnull-conversion
(défini dans les "Autres drapeaux d'avertissement" dans les paramètres du projet). Le compilateur génère cet avertissement lorsqu'une variable ou une expression avec un type nullable est implicitement convertie en un type non nul.
+ (NSString *)foo:(nullable NSString *)string { return string;
Malheureusement, pour __auto_type
(et donc let
et var
), cet avertissement ne fonctionne pas. Le type déduit via __auto_type
supprime l'annotation de nullité. Et, à en juger par le commentaire du développeur Apple dans rdar: // 27062504 , ce comportement ne changera pas. Il a été observé expérimentalement que l'ajout de _Nullable
ou _Nonnull
à __auto_type
n'affecte rien.
- (NSString *)test:(nullable NSString *)string { let tmp = string; return tmp;
Pour supprimer l' nullable-to-nonnull-conversion
nous avons écrit une macro qui "force le débouclage". Idée tirée de la macro RBBNotNil
. Mais en raison du comportement de __auto_type
il a été possible de se débarrasser de la classe auxiliaire.
#define JMNonnull(obj_) \ ({ \ NSCAssert(obj_, @"Expected `%@` not to be nil.", @#obj_); \ (typeof({ __auto_type result_ = (obj_); result_; }))(obj_); \ })
Un exemple d'utilisation de la macro JMNonnull
:
@interface JMRobot : NSObject @property (nonatomic, strong, nullable) JMLeg *leftLeg; @property (nonatomic, strong, nullable) JMLeg *rightLeg; @end @implementation JMRobot - (void)stepLeft { [self step:JMNonnull(self.leftLeg)] } - (void)stepRight { [self step:JMNonnull(self.rightLeg)] } - (void)step:(JMLeg *)leg {
Notez qu'au moment de l'écriture, l'avertissement de nullable-to-nonnull-conversion
ne fonctionne pas nullable-to-nonnull-conversion
: le compilateur ne comprend pas encore qu'une variable nullable
, après avoir vérifié l'inégalité, nil
peut être interprétée comme non nonnull
.
- (NSString *)foo:(nullable NSString *)string { if (string != nil) { return string;
Dans le code Objective-C ++, vous pouvez contourner cette limitation en utilisant la construction if let
, car Objective-C ++ autorise les déclarations de variables dans l'expression d'une if
.
- (NSString *)foo:(nullable NSString *)stringOrNil { if (let string = stringOrNil) { return string; } else { return @""; } }
Liens utiles
Il y a aussi un certain nombre de macros et de mots clés plus connus que je voudrais mentionner: le mot clé @available
, les macros NS_DESIGNATED_INITIALIZER
, NS_UNAVAILABLE
, NS_REQUIRES_SUPER
, NS_NOESCAPE
, NS_ENUM
, NS_OPTIONS
(ou vos macros pour les mêmes attributs) et la macro de bibliothèque @keypath. Nous vous recommandons également de consulter le reste de la bibliothèque libextobjc .
→ Le code de l'article est affiché dans gist .
Conclusion
Dans la première partie de l'article, nous avons essayé de parler des principales fonctionnalités et des améliorations simples du langage, qui facilitent grandement l'écriture et la prise en charge du code Objective-C. Dans la partie suivante, nous montrerons comment vous pouvez augmenter votre productivité à l'aide d'énumérations comme dans Swift (ce sont des classes Case; ce sont des types de données algébriques , ADT) et la possibilité de mettre en œuvre des méthodes au niveau du protocole.