Objective-C est un langage informatique simple conçu pour prendre en charge une véritable programmation orientée objet.
Objective-C étend le langage ANSI C standard en fournissant une syntaxe pour les définitions de classes, les méthodes et les propriétés, ainsi que d'autres constructions qui améliorent l'extensibilité dynamique des classes. La syntaxe et la conception des classes sont principalement basées sur Smalltalk, l'un des premiers langages de programmation orientés objet.
Si vous avez déjà utilisé d'autres langages de programmation orientés objet, les informations suivantes peuvent vous aider à apprendre la syntaxe de base d'Objective-C. De nombreux concepts traditionnels de la programmation orientée objet, tels que l'encapsulation, l'héritage et le polymorphisme, se retrouvent dans Objective-C. Il existe quelques différences importantes, mais ces différences seront expliquées dans cet article, et des informations plus détaillées sont disponibles si vous en avez besoin.
Si vous n'avez jamais programmé dans aucun langage de programmation, vous devriez au moins avoir une compréhension de base des concepts associés avant de commencer. L'utilisation d'objets et l'architecture orientée objet sont la base de la programmation iPhone ; comprendre comment ils interagissent est très important pour créer vos programmes. Pour en savoir plus sur les concepts orientés objet, veuillez vous référer à Programmation orientée objet avec Objective-C.
Objective-C : un sur-ensemble de C
Objective-C est un sur-ensemble strict de C -- tout programme C peut être compilé directement par un compilateur Objective-C sans modification, et il est parfaitement légal d'utiliser du code C dans Objective-C. Objective-C est décrit comme une fine couche au-dessus de C, car l'intention initiale d'Objective-C était d'ajouter des fonctionnalités orientées objet au langage C.
Extensions de fichiers pour le code Objective-C
| Extension | Type de contenu |
| .h | Fichier d'en-tête. Les fichiers d'en-tête contiennent les déclarations de classes, de types, de fonctions et de constantes. |
| .m | Fichier de code source. Il s'agit de l'extension typique d'un fichier de code source et peut contenir à la fois du code Objective-C et du code C. |
| .mm | Fichier de code source. Un fichier de code source avec cette extension peut contenir du code C++ en plus du code Objective-C et C. N'utilisez cette extension que lorsque vous avez vraiment besoin d'utiliser des classes ou des fonctionnalités C++ dans votre code Objective-C. |
Lorsque vous devez inclure des fichiers d'en-tête dans le code source, vous pouvez utiliser la directive de compilation standard #include, mais Objective-C offre une meilleure méthode. La directive #import est exactement identique à #include, sauf qu'elle garantit que le même fichier n'est inclus qu'une seule fois. Les exemples et la documentation Objective-C ont tendance à utiliser #import, et votre code devrait faire de même.
Syntaxe
La syntaxe orientée objet d'Objective-C provient du style de passage de messages de Smalltalk. Toute autre syntaxe non orientée objet, y compris les types de variables, le préprocesseur, le contrôle de flux, ainsi que la déclaration et l'appel de fonctions, est exactement identique à celle du C. Toutefois, certaines syntaxes C valides peuvent ne pas exprimer la même signification en Objective-C. Par exemple, certaines expressions booléennes renvoient true en C, mais si elles sont comparées directement à YES en Objective-C, la fonction générera une erreur, car YES en Objective-C a seulement une valeur de 1.
Le premier programme Objective-C, basé sur Xcode 4.3.1 :
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
NSLog(@"Hello World!");
}
return 0;
}
Passage de messages
La caractéristique la plus notable d'Objective-C est le modèle de passage de messages hérité de Smalltalk, qui est assez différent de l'approche dominante de style C++ actuelle. En Objective-C, il est plus exact de dire que les objets s'envoient des messages entre eux plutôt que d'appeler des méthodes les uns sur les autres. La principale différence entre les deux styles réside dans l'action d'appeler des méthodes / d'envoyer des messages. En C++, la relation entre les classes et les méthodes est stricte et claire : une méthode doit appartenir à une classe et est étroitement liée au moment de la compilation, il est donc impossible d'appeler une méthode qui n'existe pas dans une classe. Mais en Objective-C, la relation entre les classes et les messages est plus souple. Appeler une méthode est considéré comme l'envoi d'un message à un objet, et toutes les méthodes sont considérées comme des réponses aux messages. Toute la gestion des messages n'est déterminée dynamiquement qu'à l'exécution, et il appartient à la classe de décider comment traiter les messages reçus. Autrement dit, il n'est pas garanti qu'une classe réponde à chaque message reçu. Si une classe reçoit un message qu'elle ne peut pas traiter, le programme se contente de lever une exception ; il ne produit pas d'erreur ni ne plante.
En C++, la syntaxe pour envoyer un message à un objet (ou appeler une méthode) est la suivante :
obj.method(argument);
En Objective-C, cela s'écrit comme suit :
[obj method: argument];
Ces deux formes sont non seulement syntaxiquement différentes, mais diffèrent aussi par leur comportement fondamental.
Voici un exemple simple d'une classe car pour expliquer la fonctionnalité de passage de messages d'Objective-C :
[car fly];
L'interprétation typique en C++ est « appeler la méthode fly de la classe car. » Si la classe car ne définit pas de méthode fly, la compilation échouera à coup sûr. Mais en Objective-C, nous devons l'interpréter comme « envoyer un message fly à l'objet car. » fly est le message, et car est le récepteur du message. Après avoir reçu le message, car décide comment y répondre. Si la classe car définit une méthode fly, le code à l'intérieur de la méthode est exécuté ; si la classe car n'a pas de méthode fly, le programme compile quand même, et une exception est levée à l'exécution.
Les deux styles ont chacun leurs avantages et inconvénients. C++ exige que toutes les méthodes aient des actions correspondantes, et la liaison à la compilation rend les appels de fonction très rapides. L'inconvénient est qu'il ne peut fournir qu'une capacité de liaison dynamique limitée via le mot-clé virtual. Objective-C est intrinsèquement capable de duck typing et de liaison dynamique parce que les messages sont traités à l'exécution, ce qui permet d'envoyer des messages inconnus à des objets. Il peut envoyer des messages à une collection entière d'objets sans vérifier le type de chaque objet un par un, et il dispose également d'un mécanisme de transfert de messages. De plus, l'objet nul nil ne fait rien par défaut après avoir reçu un message, donc envoyer un message à nil ne fait pas planter le programme.
Chaînes de caractères
En tant que surensemble du C, Objective-C prend en charge les conventions de chaînes de caractères du langage C. Autrement dit, les caractères uniques sont entourés de guillemets simples, et les chaînes sont entourées de guillemets doubles. Cependant, la plupart du code Objective-C n'utilise généralement pas de chaînes de style C. Au lieu de cela, la plupart des frameworks transmettent les chaînes sous forme d'objets NSString. La classe NSString fournit une enveloppe de classe pour les chaînes, incluant tous les avantages que vous attendez, tels que la gestion intégrée de la mémoire pour stocker des chaînes de longueur arbitraire, la prise en charge d'Unicode, des utilitaires de formatage de style printf, etc. Comme ce type de chaîne est si fréquemment utilisé, Objective-C fournit un moyen mnémotechnique pour créer facilement des objets NSString à partir de valeurs constantes. Pour utiliser ce moyen mnémotechnique, tout ce que vous avez à faire est de placer un symbole @ avant une chaîne ordinaire entre guillemets doubles, comme le montre l'exemple suivant :
NSString* myString = @"My String\n"; NSString* anotherString = [NSString stringWithFormat:@"%d %s", 1, @"String"]; // 从一个C语言字符串创建Objective-C字符串 NSString* fromCString = [NSString stringWithCString:"A C string" encoding:NSASCIIStringEncoding];
Classe
Comme dans tous les autres langages orientés objet, les classes sont la structure de base en Objective-C pour encapsuler les données et les comportements qui opèrent sur ces données. Un objet est une instance d'une classe à l'exécution ; il contient sa propre copie en mémoire des variables d'instance déclarées par la classe, ainsi que des pointeurs vers les membres de la classe. La spécification de classe Objective-C se compose de deux parties : l'interface et l'implémentation. La partie interface contient la déclaration de classe, les définitions des variables d'instance et les méthodes liées à la classe. La partie implémentation contient le code réel des méthodes de classe.
Le diagramme suivant montre la syntaxe pour déclarer une classe appelée MyClass, qui hérite de la classe de base NSObject. Une déclaration de classe commence toujours par la directive de compilation @interface et se termine par la directive de compilation @end. Après le nom de la classe (séparé par deux-points) se trouve le nom de la classe parente. Les variables d'instance (ou membres) de la classe sont déclarées dans un bloc de code délimité par des accolades. Après le bloc de variables d'instance se trouve une liste de méthodes déclarées par la classe. Chaque déclaration de variable d'instance et de méthode se termine par un point-virgule.
Les fichiers de définition de classe suivent les conventions du langage C, avec un suffixe .h ; les fichiers d'implémentation utilisent un suffixe .m.
Diagramme de déclaration de classe

Interface
La partie interface définit clairement le nom, les membres de données et les méthodes de la classe. Elle commence par le mot-clé @interface et se termine par @end.
@interface MyObject : NSObject {
int memberVar1; // 实体变量
id memberVar2;
}
+(return_type) class_method; // 类方法
-(return_type) instance_method1; // 实例方法
-(return_type) instance_method2: (int) p1;
-(return_type) instance_method3: (int) p1 andPar: (int) p2;
@end
Le signe +/- avant une méthode indique le type de fonction : plus (+) représente une méthode de classe, qui peut être appelée sans instance et est similaire à une fonction membre statique en C++. Moins (-) représente une méthode d'instance ordinaire.
Voici une comparaison avec une syntaxe C++ similaire, comme suit :
class MyObject : public NSObject {
protected:
int memberVar1; // 实体变量
void * memberVar2;
public:
static return_type class_method(); // 類方法
return_type instance_method1(); // 实例方法
return_type instance_method2( int p1 );
return_type instance_method3( int p1, int p2 );
}
Lorsqu'Objective-C définit une nouvelle méthode, le deux-points (:) dans le nom représente le passage de paramètres, contrairement au C, qui utilise des parenthèses comme les fonctions mathématiques pour passer des paramètres. Les méthodes Objective-C permettent d'intercaler les paramètres dans le nom, plutôt que de tous les ajouter à la fin du nom de la méthode, ce qui améliore la lisibilité du programme. Prenons comme exemple la méthode permettant de définir les valeurs RGB d'une couleur :
- (void) setColorToRed: (float)red Green: (float)green Blue:(float)blue; /* 宣告方法*/ [myColor setColorToRed: 1.0 Green: 0.8 Blue: 0.2]; /* 呼叫方法*/
La signature de cette méthode est setColorToRed:Green:Blue:. Chaque deux-points est suivi d'un paramètre de type float, représentant respectivement le rouge, le vert et le bleu.
Implémentation
Le bloc d'implémentation contient les implémentations des méthodes publiques, ainsi que les définitions de variables et de méthodes privées. Il commence par le mot-clé @implementation et se termine par @end.
@implementation MyObject {
int memberVar3; //私有實體變數
}
+(return_type) class_method {
.... //method implementation
}
-(return_type) instance_method1 {
....
}
-(return_type) instance_method2: (int) p1 {
....
}
-(return_type) instance_method3: (int) p1 andPar: (int) p2 {
....
}
@end
Il convient de mentionner que non seulement les variables d'instance peuvent être définies dans le bloc Interface, mais le bloc Implementation peut également définir des variables d'instance. La différence entre les deux réside dans le contrôle d'accès. Les variables d'instance dans le bloc Interface sont par défaut protected, tandis que les variables d'instance déclarées dans le bloc Implementation sont par défaut private. Par conséquent, définir des membres privés dans le bloc Implementation correspond mieux au principe d'encapsulation orientée objet, car ainsi les informations privées de la classe n'ont pas besoin d'être exposées dans l'interface publique (fichier .h).
Création d'objets
En Objective-C, la création d'un objet nécessite deux messages : alloc et init. alloc sert à allouer de la mémoire, tandis que init sert à initialiser l'objet. init et alloc sont tous deux des méthodes définies dans NSObject. Ce n'est qu'après que l'objet parent a reçu ces deux messages et y a répondu correctement que le nouvel objet est complètement créé. Voici un exemple :
MyObject * my = [[MyObject alloc] init];
En Objective-C 2.0, si la création d'un objet ne nécessite pas de paramètres, vous pouvez directement utiliser new.
MyObject * my = [MyObject new];
Ce n'est qu'un raccourci syntaxique ; l'effet est exactement le même.
Si vous souhaitez personnaliser le processus d'initialisation, vous pouvez redéfinir la méthode init pour ajouter un traitement supplémentaire. (Son objectif est similaire au constructeur en C++.)
Méthodes
Les classes en Objective-C peuvent déclarer deux types de méthodes : les méthodes d'instance et les méthodes de classe. Une méthode d'instance est une méthode qui s'exécute dans la portée d'une instance spécifique de la classe. Autrement dit, avant d'appeler une méthode d'instance, vous devez d'abord créer une instance de la classe. Une méthode de classe, par comparaison, signifie que vous n'avez pas besoin de créer une instance.
Une déclaration de méthode comprend l'identificateur de type de méthode, le type de retour, un ou plusieurs mots-clés de sélecteur de méthode, ainsi que les informations sur le type et le nom des paramètres. La figure suivante montre la déclaration de la méthode d'instance insertObject:atIndex:. La déclaration commence par un signe moins (-), indiquant qu'il s'agit d'une méthode d'instance. Le nom réel de la méthode (insertObject:atIndex:) est la concaténation de tous les mots-clés de sélecteur de méthode, y compris les deux-points. Les deux-points indiquent la présence d'un paramètre. Si une méthode n'a pas de paramètres, vous pouvez omettre les deux-points après le premier (et unique) mot-clé de sélecteur de méthode. Dans cet exemple, la méthode a deux paramètres.
Syntaxe de déclaration de méthode

Lorsque vous souhaitez appeler une méthode, vous envoyez un message à l'objet correspondant. Ici, le message est le sélecteur de méthode accompagné des informations sur les paramètres transmis à la méthode. Tous les messages envoyés à un objet sont distribués dynamiquement, ce qui facilite le comportement polymorphe dans les classes Objective-C. Autrement dit, si une sous-classe définit une méthode avec le même sélecteur que la classe parente, la sous-classe reçoit le message en premier et peut ensuite éventuellement transférer le message (ou choisir de ne pas le transférer) à sa classe parente.
Les messages sont placés entre crochets ([ et ]). À l'intérieur des crochets, l'objet qui reçoit le message se trouve à gauche, et le message (y compris tous les paramètres requis par le message) se trouve à droite. Par exemple, pour envoyer le message insertObject:atIndex: à la variable myArray, vous utiliseriez la syntaxe suivante :
[myArray insertObject:anObj atIndex:0];
Pour éviter de déclarer trop de variables locales pour stocker des résultats temporaires, Objective-C vous permet d'utiliser des messages imbriqués. La valeur de retour de chaque message imbriqué peut être utilisée comme argument ou comme cible pour d'autres messages. Par exemple, vous pouvez remplacer n'importe quelle variable de l'exemple précédent par n'importe quel message qui obtient une valeur de ce type. Ainsi, si vous avez un autre objet appelé myAppObject avec des méthodes qui peuvent accéder à l'objet tableau et insérer un objet dans un tableau, vous pouvez réécrire l'exemple précédent comme suit :
[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];
Bien que les exemples précédents envoient tous des messages à une instance d'une classe, vous pouvez également envoyer des messages à la classe elle-même. Lorsque vous envoyez un message à une classe, la méthode que vous spécifiez doit être définie comme une méthode de classe, et non comme une méthode d'instance. Vous pouvez considérer les méthodes de classe comme quelque peu similaires aux membres statiques des classes C++ (mais pas exactement identiques).
Les utilisations typiques des méthodes de classe sont comme méthodes de fabrique pour créer de nouvelles instances de classe, ou comme un moyen d'accéder à des informations partagées liées à la classe. La syntaxe de déclaration des méthodes de classe est presque exactement la même que celle des méthodes d'instance, avec une seule petite différence. Contrairement aux méthodes d'instance qui utilisent un signe moins comme identifiant de type de méthode, les méthodes de classe utilisent un signe plus (+).
L'exemple suivant montre comment une méthode de classe peut servir de méthode de fabrique pour une classe. Ici, arrayWithCapacity est une méthode de classe de la classe NSMutableArray qui alloue et initialise une nouvelle instance de la classe, puis vous la renvoie.
NSMutableArray* myArray = nil; // nil 基本上等同于 NULL // 创建一个新的数组,并把它赋值给 myArray 变量 myArray = [NSMutableArray arrayWithCapacity:0];
Propriétés
Les propriétés sont un moyen pratique de remplacer la déclaration des méthodes d'accès. Les propriétés ne créent pas de nouvelle variable d'instance dans la déclaration de votre classe. Elles constituent simplement une notation abrégée pour définir des méthodes qui accèdent à des variables d'instance existantes. Une classe qui expose des variables d'instance peut utiliser la notation de propriété au lieu de la syntaxe des getters et setters. Les classes peuvent aussi utiliser des propriétés pour exposer des variables d'instance "virtuelles", qui résultent du calcul dynamique d'une partie des données plutôt que d'être réellement stockées dans une variable d'instance.
En fait, on peut dire que les propriétés vous évitent d'écrire une grande quantité de code redondant. Comme la plupart des méthodes d'accès sont implémentées de manière similaire, les propriétés évitent de devoir fournir des getters et setters différents pour chaque variable d'instance exposée par la classe. À la place, vous spécifiez le comportement souhaité avec une déclaration de propriété, et les méthodes getter et setter réelles sont synthétisées au moment de la compilation en fonction de la déclaration.
Les déclarations de propriété doivent être placées là où se trouvent les déclarations de méthodes dans l'interface de classe. La définition de base utilise la directive de compilateur @property, suivie des informations de type et du nom de la propriété. Vous pouvez également configurer la propriété avec des options personnalisées, qui déterminent le comportement des méthodes d'accès. L'exemple suivant montre quelques déclarations de propriété simples :
@interface Person : NSObject {
@public
NSString *name;
@private
int age;
}
@property(copy) NSString *name;
@property(readonly) int age;
-(id)initWithAge:(int)age;
@end
Les méthodes d'accès de la propriété sont implémentées par le mot-clé @synthesize, qui génère automatiquement une paire de méthodes d'accès à partir de la déclaration de propriété. Alternativement, le mot-clé @dynamic peut être utilisé pour indiquer que les méthodes d'accès seront fournies manuellement par le programmeur.
@implementation Person
@synthesize name;
@dynamic age;
-(id)initWithAge:(int)initAge
{
age = initAge; // 注意:直接赋给成员变量,而非属性
return self;
}
-(int)age
{
return 29; // 注意:并非返回真正的年龄
}
@end
Les propriétés sont accessibles à l'aide d'expressions de message traditionnelles, d'expressions avec point, ou de la paire de méthodes "valueForKey:" / "setValue:forKey:".
Person *aPerson = [[Person alloc] initWithAge: 53];
aPerson.name = @"Steve"; // 注意:点表达式,等于[aPerson setName: @"Steve"];
NSLog(@"Access by message (%@), dot notation(%@), property name(%@) and direct instance variable access (%@)",
[aPerson name], aPerson.name, [aPerson valueForKey:@"name"], aPerson->name);
Pour utiliser des expressions avec point afin d'accéder aux propriétés d'une instance, vous devez utiliser le mot-clé "self" :
-(void) introduceMyselfWithProperties:(BOOL)useGetter
{
NSLog(@"Hi, my name is %@.", (useGetter ? self.name : name)); // NOTE: getter vs. ivar access
}
Les propriétés d'une classe ou d'un protocole peuvent être lues dynamiquement.
int i;
int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList([aPerson class], &propertyCount);
for ( i=0; i < propertyCount; i++ ) {
objc_property_t *thisProperty = propertyList + i;
const char* propertyName = property_getName(*thisProperty);
NSLog(@"Person has a property: '%s'", propertyName);
}
Énumération rapide
Par rapport à l'utilisation d'objets NSEnumerator ou à l'énumération de collections en séquence, Objective-C 2.0 fournit une syntaxe d'énumération rapide. Dans Objective-C 2.0, les boucles suivantes sont fonctionnellement équivalentes, mais elles ont des caractéristiques de performance différentes.
// 使用NSEnumerator
NSEnumerator *enumerator = [thePeople objectEnumerator];
Person *p;
while ( (p = [enumerator nextObject]) != nil ) {
NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用依次枚举
for ( int i = 0; i < [thePeople count]; i++ ) {
Person *p = [thePeople objectAtIndex:i];
NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用快速枚举
for (Person *p in thePeople) {
NSLog(@"%@ is %i years old.", [p name], [p age]);
}
L'énumération rapide peut produire du code plus efficace que l'énumération standard, car les méthodes invoquées par l'énumération sont remplacées par des opérations d'arithmétique de pointeur fournies par le protocole NSFastEnumeration.
Protocoles
Un protocole est une liste de méthodes sans implémentations. Toute classe peut adopter un protocole et implémenter spécifiquement cet ensemble de méthodes.
À l'époque de NeXT, Objective-C a autrefois tenté d'introduire le concept d'héritage multiple, mais il n'a pas été implémenté en raison de l'émergence des protocoles.
Les protocoles sont similaires aux « interfaces » de Java et C#. En Objective-C, il existe deux façons de définir des protocoles : les « protocoles formels » garantis par le compilateur, et les « protocoles informels » établis dans des buts spécifiques.
Protocoles informelsIl s'agit d'une liste de méthodes qui peuvent être implémentées de manière facultative. Bien que les protocoles informels soient appelés protocoles, ils sont en fait une désignation pour une catégorie non implémentée attachée à NSObject. Il n'existe pas de protocole informel dans le mécanisme du langage Objective-C. Après OS X 10.6, l'introduction du mot-clé @optional a donné aux protocoles formels la même capacité, de sorte que les protocoles informels ont été dépréciés et ne sont plus utilisés.
Protocoles formelsSemblable aux « interfaces » en Java, il s'agit d'une liste de méthodes. Une classe peut déclarer qu'elle implémente un certain protocole. Avant Objective-C 2.0, une classe devait implémenter toutes les méthodes du protocole auquel elle déclarait se conformer ; sinon, le compilateur signalait une erreur indiquant que la classe n'implémentait pas toutes les méthodes du protocole auquel elle déclarait se conformer. Objective-C 2.0 permet de marquer certaines méthodes d'un protocole comme facultatives, de sorte que le compilateur n'impose pas l'implémentation de ces méthodes facultatives.
Les protocoles sont souvent utilisés dans Cocoa pour la délégation et le déclenchement d'événements. Par exemple, une classe de champ de texte inclut généralement un objet délégué, qui peut implémenter un protocole pouvant contenir une méthode pour implémenter l'autocomplétion de la saisie de texte. Si l'objet délégué implémente cette méthode, la classe de champ de texte déclenchera l'événement d'autocomplétion au moment approprié et appellera cette méthode pour la fonctionnalité d'autocomplétion.
Le concept de protocoles en Objective-C n'est pas exactement le même que le concept d'interfaces en Java. C'est-à-dire qu'une classe peut implémenter les méthodes contenues dans un protocole sans déclarer qu'elle se conforme à ce protocole, se conformant ainsi effectivement au protocole ; cette différence est invisible pour le code externe. La déclaration d'un protocole formel ne fournit pas d'implémentations ; elle indique simplement que les classes se conformant au protocole ont implémenté les méthodes du protocole, garantissant que le côté appelant peut invoquer les méthodes en toute sécurité.
Syntaxe
Un protocole commence par le mot-clé @protocol comme début du bloc et se termine par @end, avec la liste des méthodes entre les deux.
@protocol Locking - (void)lock; - (void)unlock; @end
Ceci est un exemple de protocole. En programmation multithread, il est souvent nécessaire de garantir qu’une ressource partagée ne peut être utilisée que par un seul thread à la fois, c’est pourquoi un verrou est attaché à la ressource avant utilisation. Ce qui précède est un protocole qui exprime le concept de « verrou ». Le protocole possède deux méthodes, qui ont des noms mais ne sont pas encore implémentées.
Dans ce qui suit, SomeClass déclare qu’il adopte le protocole Locking :
@interface SomeClass : SomeSuperClass <Locking> @end
Une fois que SomeClass déclare qu’il adopte le protocole Locking, SomeClass est tenu d’implémenter les deux méthodes du protocole Locking.
@implementation SomeClass
- (void)lock {
// 實現lock方法...
}
- (void)unlock {
// 實現unlock方法...
}
@end
Comme SomeClass se conforme déjà au protocole Locking, l’appelant peut envoyer en toute sécurité des messages lock ou unlock à une variable d’instance SomeClass sans craindre qu’elle ne puisse pas répondre aux messages.
Les plugins sont un autre exemple d’utilisation de définitions abstraites, permettant de définir le comportement souhaité sans se soucier de l’implémentation du plugin.
Typage dynamique
Comme Smalltalk, Objective-C dispose d’un typage dynamique : des messages peuvent être envoyés à n’importe quelle entité objet, que l’interface publique de l’objet ait ou non une méthode correspondante. Par rapport à des langages à typage statique comme C++, le compilateur bloquera les appels de méthode sur des pointeurs (void*). Mais en Objective-C, vous pouvez envoyer n’importe quel message à id (id ressemble beaucoup à void*, mais est strictement limité aux objets) ; le compilateur émet seulement un avertissement indiquant que « l’objet peut ne pas répondre au message ». Le programme peut toujours être compilé, et ce qui se produit réellement dépend de la forme réelle de l’objet à l’exécution. Si l’objet peut effectivement répondre au message, la méthode correspondante s’exécute quand même.
Après qu’un objet reçoit un message, il dispose de trois façons possibles de le traiter. Premièrement, il peut répondre au message et exécuter la méthode. S’il ne peut pas répondre, il peut transmettre le message à un autre objet. Si aucune des deux actions ci-dessus n’est effectuée, il doit gérer l’exception levée pour non-réponse. Tant que l’une des trois est effectuée, le message est considéré comme terminé et rejeté. Si un message est envoyé à "nil" (pointeur d’objet nul), le message est généralement ignoré ; selon les options du compilateur, il peut lever une exception.
Bien qu’Objective-C dispose de capacités de typage dynamique, la vérification statique des types à la compilation peut toujours être appliquée aux variables. Les trois déclarations suivantes sont complètement équivalentes à l’exécution, mais chacune fournit des informations de type de plus en plus explicites. Les informations de type supplémentaires permettent au compilateur de vérifier les types des variables à la compilation et d’émettre des avertissements pour les variables incompatibles.
Les trois méthodes suivantes ne diffèrent que par la forme du paramètre :
- setMyValue:(id) foo;
La forme id indique que le paramètre "foo" peut être une instance de n’importe quelle classe.
- setMyValue:(id <aProtocol>) foo;
id<aProtocol> indique que "foo" peut être une instance de n’importe quelle classe, mais doit adopter le protocole "aProtocol".
- setMyValue:(NSNumber*) foo;
Cette déclaration indique que "foo" doit être une instance de "NSNumber".
Le typage dynamique est une fonctionnalité puissante. Lors de l’implémentation de classes conteneurs dans des langages à typage statique dépourvus de génériques (comme les versions antérieures à Java 5), les programmeurs devaient écrire une classe conteneur pour des objets de type générique, puis effectuer constamment des conversions entre le type générique et le type réel. Cependant, la conversion de type brise le typage statique ; par exemple, écrire un "integer" et le lire comme une "string" provoque une erreur d’exécution. De tels problèmes sont résolus par les génériques, mais les classes conteneurs exigent des types cohérents pour les objets qu’elles contiennent, alors que les langages à typage dynamique n’ont pas du tout ce problème.
Transfert
Objective-C permet d'envoyer un message à un objet, qu'il puisse y répondre ou non. En plus de répondre aux messages ou de les rejeter, un objet peut également transférer le message à un objet capable d'y répondre. Le transfert peut servir à simplifier certains patrons de conception, tels que le patron observateur ou le patron proxy.
Le runtime Objective-C définit une paire de méthodes dans Object :
Méthode de transfert :
- (retval_t) forward:(SEL) sel :(arglist_t) args; // with GCC - (id) forward:(SEL) sel :(marg_list) args; // with NeXT/Apple systems
Méthode de réponse :
- (retval_t) performv:(SEL) sel :(arglist_t) args; // with GCC - (id) performv:(SEL) sel :(marg_list) args; // with NeXT/Apple systems
Un objet qui souhaite implémenter le transfert n'a qu'à redéfinir les méthodes ci-dessus avec de nouvelles méthodes pour définir son comportement de transfert. Il n'est pas nécessaire de réécrire la méthode de réponse performv::, car cette méthode envoie simplement un message à l'objet répondant et transmet les paramètres. Parmi eux, le type SEL est le type des messages en Objective-C.
Le code suivant démontre le concept de base du transfert :
Code du fichier Forwarder.h :
#import <objc/Object.h>
@interface Forwarder : Object
{
id recipient; //该对象是我们希望转发到的对象。
}
@property (assign, nonatomic) id recipient;
@end
Code du fichier Forwarder.m :
#import "Forwarder.h"
@implementation Forwarder
@synthesize recipient;
- (retval_t) forward: (SEL) sel : (arglist_t) args
{
/*
*检查转发对象是否响应该消息。
*若转发对象不响应该消息,则不会转发,而产生一个错误。
*/
if([recipient respondsTo:sel])
return [recipient performv: sel : args];
else
return [self error:"Recipient does not respond"];
}
Code du fichier Recipient.h :
#import <objc/Object.h> // A simple Recipient object. @interface Recipient : Object - (id) hello; @end
Code du fichier Recipient.m :
#import "Recipient.h"
@implementation Recipient
- (id) hello
{
printf("Recipient says hello!\n");
return self;
}
@end
Code du fichier main.m :
#import "Forwarder.h"
#import "Recipient.h"
int main(void)
{
Forwarder *forwarder = [Forwarder new];
Recipient *recipient = [Recipient new];
forwarder.recipient = recipient; //Set the recipient.
/*
*转发者不响应hello消息!该消息将被转发到转发对象。
*(若转发对象响应该消息)
*/
[forwarder hello];
return 0;
}
Lors de la compilation avec GCC, le compilateur signale :
$ gcc -x objective-c -Wno-import Forwarder.m Recipient.m main.m -lobjc main.m: In function `main': main.m:12: warning: `Forwarder' does not respond to `hello' $
Comme mentionné précédemment, le compilateur signale que la classe Forwarder ne répond pas au message hello. Dans ce cas, comme le transfert est implémenté, cet avertissement peut être ignoré. L'exécution du programme produit la sortie suivante :
$ ./a.out Recipient says hello!
Catégories
Dans la conception d'Objective-C, une considération majeure est la maintenance de grands frameworks de code. L'expérience en programmation structurée montre qu'un moyen majeur d'améliorer le code est de le diviser en plus petites parties. Objective-C emprunte et étend le concept de « catégorie » de l'implémentation Smalltalk pour aider à atteindre l'objectif de décomposition du code.
Une catégorie peut décomposer les implémentations de méthodes en une série de fichiers séparés. Les programmeurs peuvent placer un groupe de méthodes liées dans une catégorie pour rendre le programme plus lisible. Par exemple, une catégorie nommée « vérification orthographique » peut être ajoutée à la classe de chaînes, et le code de vérification orthographique pertinent peut être placé dans cette catégorie.
De plus, les méthodes d'une catégorie sont ajoutées à la classe à l'exécution. Cette fonctionnalité permet aux programmeurs d'ajouter des méthodes à des classes existantes sans disposer du code original ni recompiler la classe originale. Par exemple, si l'implémentation de la classe de chaînes fournie par le système n'inclut pas de fonctionnalité de vérification orthographique, une telle fonctionnalité peut être ajoutée sans modifier le code original de la classe de chaînes.
À l'exécution, les méthodes d'une catégorie ne sont pas différentes des méthodes originales de la classe, et leur code peut accéder à toutes les variables membres, y compris les variables membres privées de la classe.
Si une catégorie déclare une méthode portant le même nom qu'une méthode existante dans la classe, la méthode de la catégorie sera appelée. Par conséquent, les catégories peuvent non seulement ajouter des méthodes à une classe, mais aussi remplacer des méthodes originales. Cette fonctionnalité peut être utilisée pour corriger des bogues dans le code original, et peut même modifier fondamentalement le comportement des classes originales d'un programme. Si les méthodes de deux catégories ont le même nom, la méthode appelée est imprévisible.
D'autres langages ont également tenté d'ajouter cette fonctionnalité de langage par différentes méthodes. TOM va plus loin à cet égard, en permettant non seulement d'ajouter des méthodes, mais aussi des variables membres. D'autres langages utilisent des solutions orientées déclaration, la plus notable étant le langage Self.
C# et Visual Basic.NET implémentent des fonctionnalités similaires via des méthodes d'extension et des classes partielles. Ruby et certains langages dynamiques appellent cette technique « monkey patch ».
Exemple d'utilisation des catégories
Cet exemple crée une classe Integer, qui ne définit elle-même que la propriété integer, puis ajoute deux catégories, Arithmetic et Display, pour étendre les fonctionnalités de la classe. Bien que les catégories puissent accéder aux membres privés de la classe, il est généralement préférable d'y accéder via des méthodes d'accès aux propriétés, ce qui rend les catégories plus indépendantes de la classe d'origine. C'est un usage typique des catégories — un autre usage consiste à remplacer des méthodes de la classe d'origine par des catégories, bien que l'utilisation de catégories plutôt que de l'héritage pour remplacer des méthodes ne soit pas considérée comme une bonne pratique.
Code du fichier Integer.h :
#import <objc/Object.h>
@interface Integer : Object
{
@private
int integer;
}
@property (assign, nonatomic) integer;
@end
Code du fichier Integer.m :
#import "Integer.h" @implementation Integer @synthesize integer; @end
Code du fichier Arithmetic.h :
#import "Integer.h" @interface Integer(Arithmetic) - (id) add: (Integer *) addend; - (id) sub: (Integer *) subtrahend; @end
Code du fichier Arithmetic.m :
#import "Arithmetic.h"
@implementation Integer(Arithmetic)
- (id) add: (Integer *) addend
{
self.integer = self.integer + addend.integer;
return self;
}
- (id) sub: (Integer *) subtrahend
{
self.integer = self.integer - subtrahend.integer;
return self;
}
@end
Code du fichier Display.h :
#import "Integer.h" @interface Integer(Display) - (id) showstars; - (id) showint; @end
Code du fichier Display.m :
#import "Display.h"
@implementation Integer(Display)
- (id) showstars
{
int i, x = self.integer;
for(i=0; i < x; i++)
printf("*");
printf("\n");
return self;
}
- (id) showint
{
printf("%d\n", self.integer);
return self;
}
@end
Code du fichier main.m :
#import "Integer.h"
#import "Arithmetic.h"
#import "Display.h"
int
main(void)
{
Integer *num1 = [Integer new], *num2 = [Integer new];
int x;
printf("Enter an integer: ");
scanf("%d", &x);
num1.integer = x;
[num1 showstars];
printf("Enter an integer: ");
scanf("%d", &x);
num2.integer = x;
[num2 showstars];
[num1 add:num2];
[num1 showint];
return 0;
}
Compilez avec la commande suivante :
gcc -x objective-c main.m Integer.m Arithmetic.m Display.m -lobjc
Au moment de la compilation, vous pouvez expérimenter en omettant #import "Arithmetic.h", la commande [num1 add:num2] et le fichier Arithmetic.m. Le programme fonctionnera toujours, ce qui montre que les catégories peuvent être chargées dynamiquement et à la demande ; si la fonctionnalité d'une catégorie n'est pas nécessaire, vous pouvez simplement ne pas la compiler.
Ramasse-miettes
Objective-C 2.0 fournit un ramasse-miettes optionnel. En mode rétrocompatible, l'environnement d'exécution Objective-C transforme les opérations de comptage de références telles que « retain » et « release » en opérations sans effet. Lorsque le ramasse-miettes est activé, tous les objets sont gérés par le ramasse-miettes. Les pointeurs C ordinaires peuvent être modifiés avec « __strong », ce qui indique que l'objet pointé par le pointeur est encore utilisé. Les pointeurs marqués « __weak » ne sont pas comptés par le ramasse-miettes et sont réécrits en « nil » lorsque l'objet est collecté. L'implémentation d'Objective-C 2.0 sur iOS n'inclut pas de ramasse-miettes. Le ramasse-miettes s'exécute sur un thread d'arrière-plan à faible priorité et peut se mettre en pause lorsque l'utilisateur agit, préservant ainsi une bonne expérience utilisateur.
Lien de référence : https://zh.wikipedia.org/wiki/Objective-C