Objective-C succinctement Blocs

Des blocs sont en fait une extension du langage de programmation C, mais ils sont largement utilisés par les frameworks Objective-C d’Apple. Ils sont similaires aux lambdas de C # en ce qu'ils vous permettent de définir un bloc d'instructions en ligne et de le transmettre à d'autres fonctions comme s'il s'agissait d'un objet..

Traiter des données avec des fonctions ou effectuer des actions arbitraires avec des blocs

Les blocs sont extrêmement pratiques pour définir les méthodes de rappel car ils vous permettent de définir les fonctionnalités souhaitées au moment de l'appel, plutôt qu'à un autre endroit de votre programme. De plus, les blocs sont implémentés comme fermetures (tout comme lambdas en C #), ce qui permet de capturer l’état local entourant le bloc sans travail supplémentaire.


Création de blocs

La syntaxe de blocage peut être un peu dérangeante par rapport à la syntaxe Objective-C que nous avons utilisée tout au long de ce livre, alors ne vous inquiétez pas si cela prend un certain temps pour être à l'aise avec elles. Nous allons commencer par regarder un exemple simple:

^ (int x) return x * 2; ;

Ceci définit un bloc qui prend un paramètre entier, X, et renvoie cette valeur multipliée par deux. En dehors du caret (^), cela ressemble à une fonction normale: elle contient une liste de paramètres entre parenthèses, un bloc d’instruction placé entre accolades et une valeur de retour (facultative). En C #, cela s’écrit:

x => x * 2;

Mais les blocs ne se limitent pas à de simples expressions, ils peuvent contenir un nombre arbitraire d'instructions, tout comme une fonction. Par exemple, vous pouvez ajouter un NSLog () appeler avant de renvoyer une valeur:

^ (int x) NSLog (@ "sur le point de multiplier% i par 2.", x); retourne x * 2; ;

Blocs sans paramètres

Si votre bloc ne prend aucun paramètre, vous pouvez omettre complètement la liste de paramètres:

^ NSLog (@ "C'est un joli bloc artificiel."); NSLog (@ "Il ne fait que sortir ces deux messages."); ;

Utilisation de blocs comme rappels

À lui seul, un bloc n'est pas très utile. En règle générale, vous les transmettez à une autre méthode en tant que fonction de rappel. C’est une fonctionnalité linguistique très puissante, car elle vous permet de traiter fonctionnalité en tant que paramètre plutôt que de se limiter à Les données. Vous pouvez passer un bloc à une méthode comme n'importe quelle autre valeur littérale:

[anObject doSomethingWithBlock: ^ (int x) NSLog (@ "Multiplier% i par deux"); retourne x * 2; ];

le Quelque chose de quelque chose: l'implémentation peut exécuter le bloc de la même manière qu'une fonction, ce qui ouvre la voie à de nombreux nouveaux paradigmes organisationnels.

Comme exemple plus pratique, jetons un coup d’œil à la sortUsingComparator: méthode définie par NSMutableArray. Ceci fournit exactement la même fonctionnalité que le trié TableauUtilisationFonction: méthode que nous avons utilisée dans le Chapitre Types de données, sauf que vous définissez l’algorithme de tri dans un bloc au lieu d’une fonction à part entière:

Échantillon de code inclus: SortUsingBlock

#importation  int principal (int argc, const char * argv []) @autoreleasepool NSMutableArray * numbers = [NSMutableArray arrayWithObjects: [NSNumber numberWithFloat: 3.0f], [NSNumber numberWithFloat: 5.5f], [NSNumber numberWithFloat: 1.0f], [ NSNuméro numéroWithFloat: 12.2f], nil]; [nombres sortUsingComparator: ^ NSComparisonResult (id obj1, id obj2) nombre float1 = [obj1 floatValue]; float number2 = [obj2 floatValue]; si (nombre1 < number2)  return NSOrderedAscending;  else if (number1 > nombre2) retour NSOrderedDescending;  else return NSOrderedSame; ]; pour (int i = 0; i<[numbers count]; i++)  NSLog(@"%i: %0.1f", i, [[numbers objectAtIndex:i] floatValue]);   return 0; 

Encore une fois, il s’agit d’un tri ascendant simple, mais pouvoir définir l’algorithme de tri au même endroit que l’invocation de fonction est plus intuitif que de devoir définir une fonction indépendante ailleurs dans le programme. Notez également que vous pouvez déclarer des variables locales dans un bloc comme vous le feriez dans une fonction..

Les frameworks Objective-C standard utilisent ce modèle de conception pour tout, du tri à l'énumération, en passant par l'animation. En fait, vous pourriez même remplacer la boucle for dans le dernier exemple par NSArrayde enumerateObjectsUsingBlock: méthode, comme indiqué ici:

[sortNumbers enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL * stop) NSLog (@ "% lu:% 0.1f", idx, [obj floatValue]); if (idx == 2) // Arrête d'énumérer à la fin de cette itération. * stop = YES; ];

le obj paramètre est l'objet actuel, idx est l'indice actuel, et *Arrêtez est un moyen de sortir du dénombrement prématurément. Réglage du *Arrêtez pointeur vers OUI indique à la méthode d'arrêter l'énumération après l'itération en cours. Tout ce comportement est spécifié par le enumerateObjectsUsingBlock: méthode.

Bien que l'animation soit un peu hors sujet pour ce livre, une brève explication mérite néanmoins d'être expliquée pour aider à comprendre l'utilité des blocs.. UIView est l'une des classes les plus utilisées dans la programmation iOS. C’est un conteneur graphique générique qui vous permet d’animer son contenu via le animateWithDuration: animations: méthode. Le deuxième paramètre est un bloc qui définit l'état final de l'animation et la méthode détermine automatiquement comment animer les propriétés à l'aide du premier paramètre. Il s'agit d'un moyen élégant et convivial pour définir les transitions et autres comportements basés sur une minuterie. Nous discuterons des animations beaucoup plus en détail dans les prochains iOS succinctement livre.


Stockage et exécution de blocs

En plus de les transmettre aux méthodes, les blocs peuvent également être stockés dans des variables pour une utilisation ultérieure. Ce cas d'utilisation sert essentiellement de moyen alternatif pour définir des fonctions:

#importation  int principal (int argc, const char * argv []) @autoreleasepool int (^ addIntegers) (int, int); addIntegers = ^ (int x, int y) retour x + y; ; int result = addIntegers (24, 18); NSLog (@ "% i", résultat);  retourne 0; 

Tout d'abord, examinons la syntaxe de déclaration des variables de bloc: int (^ addIntegers) (int, int). Le nom de cette variable est simplement addIntegers (sans le caret). Cela peut être déroutant si vous n'utilisez pas de blocs depuis très longtemps. Il est utile de considérer le curseur comme la version du bloc de l’opérateur de déréférence (*). Par exemple, un aiguille appelé addIntegers serait déclaré comme * addIntegers-de même, un bloc du même nom est déclaré comme ^ addIntegers. Cependant, gardez à l'esprit qu'il s'agit simplement d'une similitude superficielle.

En plus du nom de la variable, vous devez également déclarer toutes les métadonnées associées au bloc: le nombre de paramètres, leurs types et le type de retour. Cela permet au compilateur d'appliquer la sécurité de type avec des variables de bloc. Notez que le curseur est ne pas une partie du nom de la variable, elle n'est requise que dans la déclaration.

Ensuite, nous utilisons l’opérateur d’affectation standard (=) pour stocker un bloc dans la variable. Bien sûr, les paramètres du bloc ((int x, int y)) doit correspondre aux types de paramètres déclarés par la variable ((int, int)). Un point-virgule est également requis après la définition du bloc, tout comme une affectation de variable normale. Une fois qu’elle a été renseignée avec une valeur, la variable peut être appelée comme une fonction: addIntegers (24, 18).

Variables de bloc sans paramètre

Si votre bloc ne prend aucun paramètre, vous devez le déclarer explicitement dans la variable en plaçant vide dans la liste des paramètres:

void (^ artificiel) (void) = ^ NSLog (@ "C'est un joli bloc artificiel."); NSLog (@ "Il ne fait que sortir ces deux messages."); ; artificiel();

Travailler avec des variables

Les variables à l'intérieur des blocs se comportent de la même manière que dans les fonctions normales. Vous pouvez créer des variables locales dans le bloc, accéder à des paramètres d’accès au paramètre et utiliser des variables et des fonctions globales (par exemple,., NSLog ()). Mais les blocs ont aussi accès à variables non locales, qui sont des variables de la portée lexicale englobante.

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42

Dans ce cas, valeur initiale est considérée comme une variable non locale dans le bloc car elle est définie à l'extérieur du bloc (ne pas localement, par rapport au bloc). Bien entendu, le fait que les variables non locales soient en lecture seule implique que vous ne pouvez pas leur affecter:

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) initialValue = 5; // Cela provoquera une erreur de compilation. renvoyer valeur initiale + x; ;

Avoir accès aux variables environnantes (non locales) est un gros problème lorsque vous utilisez des blocs en ligne comme paramètres de méthode. Il fournit un moyen pratique de représenter tout état requis dans le bloc..

Par exemple, si vous animiez la couleur d'un composant d'interface utilisateur et que la couleur cible était calculée et stockée dans une variable locale avant la définition du bloc, vous pouvez simplement utiliser la variable locale dans le bloc sans aucun travail supplémentaire requis. Si vous n'aviez pas accès à des variables non locales, vous auriez passé la valeur de couleur en tant que paramètre de bloc supplémentaire. Lorsque votre fonctionnalité de rappel repose sur une grande partie de l'état environnant, cela peut être très fastidieux.

Les blocs sont des fermetures

Cependant, les blocs n'ont pas seulement accès non-locales variables-ils assurent également que ces variables seront jamais changer, peu importe quand et où le bloc est exécuté. Dans la plupart des langages de programmation, on appelle cela fermeture.

Les fermetures fonctionnent en faisant une copie constante, en lecture seule, de toutes les variables non locales et en les stockant dans un fichier. tableau de référence avec les déclarations qui composent le bloc lui-même. Ces valeurs en lecture seule sont utilisées chaque fois que le bloc est exécuté, ce qui signifie que même si la variable non locale d'origine change, la valeur utilisée par le bloc est garantie identique à celle qui prévalait lors de la définition du bloc..

Stockage de variables non locales dans une table de référence

Nous pouvons le voir en action en attribuant une nouvelle valeur à la valeur initiale variable de l'exemple précédent:

int initialValue = 32; int (^ addToInitialValue) (int) = ^ (int x) return initialValue + x; ; NSLog (@ "% i", addToInitialValue (10)); // 42 initialValue = 100; NSLog (@ "% i", addToInitialValue (10)); // Encore 42.

Peu importe où vous appelez addToInitialValue (), la valeur initiale utilisé par le bloc sera toujours être 32, parce que c'est ce que c'était quand il a été créé. Pour toutes fins utiles, c'est comme si le valeur initiale la variable a été transformée en une valeur littérale à l'intérieur du bloc.

Donc, l’utilité des blocs est double:

  1. Ils vous permettent de représenter la fonctionnalité sous forme d'objet.
  2. Ils vous permettent de représenter des informations d'état à côté de cette fonctionnalité.

L'idée derrière la fonctionnalité d'encapsulation dans un bloc est de pouvoir l'utiliser plus tard dans le programme. Les fermetures permettent d’assurer un comportement prévisible n'importe quand un bloc est exécuté en gelant l'état environnant. Cela les rend un aspect intégral de la programmation par blocs.

Variables de bloc mutables

Dans la plupart des cas, capturer un état avec des fermetures correspond intuitivement à ce que vous attendez d'un bloc. Il y a cependant des moments qui appellent le comportement opposé. Variables de bloc mutables sont des variables non locales désignées en lecture-écriture au lieu de la valeur par défaut en lecture seule. Pour rendre une variable non locale mutable, vous devez la déclarer avec le __bloc modificateur, qui crée un lien direct entre la variable utilisée en dehors du bloc et celle utilisée à l'intérieur du bloc. Cela ouvre la porte à l’utilisation de blocs comme itérateurs, générateurs et tout autre type d’objet traitant l’état.

Création d'un lien direct avec une variable de bloc mutable

L'exemple suivant montre comment rendre une variable non locale modifiable:

#importation  #import "Person.h" int main (int argc, const char * argv []) @autoreleasepool __block NSString * name = @ "Dave"; void (^ generateRandomName) (void) = ^ NSLog (@ "Modification de% @ en Frank", nom); name = @ "Frank"; ; NSLog (@ "% @", nom); // Dave // ​​Changez-le depuis l'intérieur du bloc. generateRandomName (); // Change de Dave en Frank. NSLog (@ "% @", nom); // Frank // Changez-le de l'extérieur du bloc. name = @ "Heywood"; generateRandomName (); // Changement de Heywood à Frank.  retourne 0; 

Cela ressemble presque exactement à l'exemple précédent, avec deux différences très importantes. Tout d'abord, le non-local prénom variable pouvez être assigné de l'intérieur du bloc. Deuxièmement, changer la variable en dehors du bloc Est-ce que met à jour la valeur utilisée dans le bloc. Il est même possible de créer plusieurs blocs qui manipulent tous la même variable non locale..

La seule réserve à utiliser le __bloc modificateur est que ce ne peux pas être utilisé sur des tableaux de longueur variable.

Définir des méthodes acceptant des blocs

On peut soutenir que la création de méthodes acceptant les blocs est plus utile que leur stockage dans des variables locales. Cela vous donne la possibilité d’ajouter votre propre enumerateObjectsUsingBlock:-les méthodes de style aux classes personnalisées.

Considérez l’interface suivante pour la La personne classe:

// Person.h @interface Person: NSObject @property int age; - (void) celebred BirthdayWithBlock: (void (^) (int)) activité; @fin

le vide (^) (int) code est le type de données du bloc que vous souhaitez accepter. Dans ce cas, nous accepterons un bloc sans valeur de retour et avec un seul paramètre entier. Notez que, contrairement aux variables de bloc, cela n’exige pas un nom pour le bloc, mais uniquement un non orné. ^ personnage.

Vous avez maintenant toutes les compétences nécessaires pour créer des méthodes qui acceptent les blocs en tant que paramètres. Une implémentation simple pour le La personne L’interface montrée dans l’exemple précédent pourrait ressembler à quelque chose comme:

// Person.m #import "Person.h" @implementation Person @synthesize age = _age; - (void) celebrationBirthdayWithBlock: (void (^) (int)) activité NSLog (@ "C'est une fête !!!"); activité (self.age);  @fin

Ensuite, vous pouvez transmettre une activité personnalisable à exécuter sur une La personneL'anniversaire est comme ça:

// main.m int main (int argc, const char * argv []) @autoreleasepool Person * dave = [[Person alloc] init]; dave.age = 37; [dave celebrationBirthdayWithBlock: ^ (int age) NSLog (@ "Woot! Je tourne% i", age + 1); ];  retourne 0; 

Il est évident que l’utilisation de blocs en tant que paramètres est infiniment plus flexible que les types de données standard que nous utilisions jusqu’à présent. Vous pouvez réellement dire à une instance de faire quelque chose, plutôt que de simplement traiter des données.


Résumé

Les blocs vous permettent de représenter des instructions en tant qu’objets Objective-C, ce qui vous permet de transmettre des informations de manière arbitraire. actes à une fonction au lieu d'être limité à Les données. Cela est utile pour tout, des itérations sur une séquence d'objets à l'animation de composants d'interface utilisateur. Les blocs sont une extension polyvalente du langage de programmation C et un outil indispensable si vous envisagez de travailler avec les frameworks iOS standard. Dans ce chapitre, nous avons appris à créer, stocker et exécuter des blocs et à comprendre les subtilités des fermetures et des __bloc modificateur de stockage. Nous avons également discuté de certains paradigmes d’utilisation courants pour les blocs.

Ainsi s'achève notre parcours à travers Objective-C. Nous avons traité de tout, de la syntaxe de base aux types de données de base, classes, protocoles, propriétés, méthodes, gestion de la mémoire, gestion des erreurs et même utilisation avancée des blocs. Nous nous sommes concentrés davantage sur les fonctionnalités linguistiques que sur la création d'applications graphiques, mais cela a fourni une base solide pour le développement d'applications iOS. J'espère que vous vous sentez très à l'aise avec le langage Objective-C.

Rappelez-vous qu'Objective-C repose sur bon nombre des mêmes concepts orientés objet que les autres langages POO. Bien que nous n'ayons abordé que quelques modèles de conception orientés objet dans ce livre, pratiquement tous les paradigmes organisationnels disponibles dans d'autres langues sont également possibles dans Objective-C. Cela signifie que vous pouvez facilement exploiter votre base de connaissances existante orientée objet avec les outils présentés dans les chapitres précédents..


iOS succinctement

Si vous êtes prêt à créer des applications fonctionnelles pour iPhone et iPad, ne manquez pas la deuxième partie de cette série., iOS succinctement. Ce guide pratique pour le développement d'applications applique toutes les compétences d'Objective-C acquises dans ce livre à des situations de développement réelles. Nous allons parcourir tous les principaux frameworks d'Objective-C et apprendre à effectuer des tâches diverses, notamment: configurer les interfaces utilisateur, capturer les entrées, dessiner des graphiques, enregistrer et charger des fichiers, et bien plus encore..

Cette leçon représente un chapitre de Objective-C Succinctly, un eBook gratuit de l’équipe de Syncfusion..