Objc

Crée en 1981 par Brad Cox pour profiter la syntaxe “productive” de Smalltalk tout en restant compatible avec les outils UNIX et l’environnement C existant.1

Il s’execute sur une machine virtuelle qui permet de modifier dynamiquement la configuration des objets, en leur ajoutant, modifiant ou enlevant des méthodes, et de modifier la hierarchie entre eux pendant l’execution du programme, sans necesiter de recompilation, de la même manière que Smalltalk.

Selon Aaron Hillegass, une personne exérimentée en C peut apprendre Objective C en deux heures 2

Implementation

GNUstep met son implemtentation en libre acces via github, ce qui peut etre utile pour comprendre les mecaniques internes du langage et de son runtime en particulier.

On peut y voir par exemple que les methodes enregistrées dans une classe sont accessibles par une liste chainée, ou comment une classe est crée “en vif” dans runtime.c.

Premier programme en Objective C

// fichier oc.c
#include <stdio.h>

int main()
{
    printf("bonjour objective C");
}

On le compile et execute avec la commande cc -ObjC oc.c && ./a.out, et il affiche bien bonjour objective C. Un programme C est en effet un programme objective C valide.

Si le fichier a l’extension .m au lieu de .c, le compilateur fera automatiquement la compilation en mode -ObjC et il n’est pas nécessaire de rajouter l’option au compilateur.

Exemple plus avancé

Voici un exemple de programme qu’on peut réaliser en Objective-C mais pas en C ou C++.

Si vous êtes dans un environnement GnuStep, objc.h inclut tout l’environnement et toutes les fonctions du langage.

Sur Apple, on y trouve uniquement quelques types de base du langage comme id, SEL, BOOL, et unde dizaine de fonctions rajoutées avec Mac OS X parmi lesquelles sel_getName, sel_registerName, object_getClassName… il faudra donc rajouter soi meme <objc/runtime.h> pour faire quoi que ce soit d’intéressant sur Apple.

D’autre part, un objet qui ne déclare pas de parent doit au minimum déclarer les méthodes methodSignatureForSelector: et doesNotRecognizeSelector:. Pour gagner du temps nous allons utiliser la classe NSObject fournie pour nous par le framework Foundation. Pour que le programme compile il faudra ajouter l’option -framework Foundation à la ligne de commande.

#include <objc/objc.h>
#include <objc/runtime.h>
#include <stdio.h>
#include <Foundation/Foundation.h>

// @ est utilisé par le préprocesseur du langage
// pour générer au runtime la hiérarchie et la
// constitution des classes. On declare que nos 
// objets heritent des fonctionnalités de NSObject,
// notamment sa methode `alloc`, `respondsToSelector`, etc.
@interface chien: NSObject 
-(void)aboye;
@end
@implementation chien
-(void)aboye {
    printf("ouaf (chien)");
}
@end

@interface loup: NSObject
-(void)aboye;
@end
@implementation loup
-(void)aboye {
    printf("ouaf (loup)");
} 
@end

@interface chat: NSObject
@end
@implementation chat
@end

void miaule(void)
{
    printf("miaou");
}

int main()
{
    id t = [chien alloc]; // id est un pointeur générique
    printf("%s \n", object_getClassName(t)); // "chien"

    id chi = class_createInstance([chien class], 0); // autre facon d'initialiser un chien
    chi = [chi init]; // alloc aloue juste la memoire, init y met des valeurs qui sieent a un objet fraichement crée (par ex tout a zero)
    [t aboye]; // "ouaf (chien)"
    [chi aboye]. // "ouaf (chien)"

    SEL s = sel_registerName("aboye"); // ajoute un selecteur dans la 
    // base de données puis retourne un pointeur dessus, 
    // on associera plus tard le selecteur a une fonction
    printf("%s \n", sel_getName(s)); // "aboye"
    loup * lo = [loup alloc]; // on ne se soucie pas de "init" ici
    chat * ch = [chat alloc]; // idem
    NSArray* arr = @[chi, lo, ch]; // NSArray est générique

    for(int i = 0; i < [arr count]; i++) {
        printf("%s ", object_getClassName(arr[i]));
        if([arr[i] respondsToSelector:s]) {
            [arr[i] performSelector:s];
        }
        puts("");
    }
    // la boucle ci dessus affiche
    // chien ouaf (chien)
    // loup ouaf (loup)
    // chat 
    // le chat ne possède pas de methode "aboye" donc
    // ne fait rien.

    class_addMethod([chat class], s, miaule, "vv"); 
    // on lui on ajoute une methode qui "repond" au selecteur "aboye".
    // Noter que le nom du selecteur n'a pas de rapport avec le nom
    // de la fonction. Il s'agit juste d'une chaine de caracteres
    // convertie en un pointeur de type SEL par souci de performance.

    puts("take2");

    for(int i = 0; i < [arr count]; i++) {
        printf("%s ", object_getClassName(arr[i]));
        if([arr[i] respondsToSelector:s]) {
            [arr[i] performSelector:s];
        }
        puts("");
    }
    // affiche
    // chien: ouaf (chien)
    // loup: ouaf (loup)
    // chat: miaou

    NSLog(@"%@\n%lu\n%lu\n", [arr description], [arr retainCount], [ch retainCount]);
    // affiche
    // 2026-02-12 06:39:58.618 a.out[34915:48582009] (
    //     "<chien: 0x12de10d30>",
    //     "<loup: 0x12de10b50>",
    //     "<chat: 0x12de10da0>"
    // )
    // 1
    // 2
    //
    // `NSLog` peut afficher les objets de type NSString, NSNumber, etc.
    // [NSOject description] affiche une vue utile au debugage
    // [NSOject retainCount] affiche le nombre de propriétaires d'un objet
    // on voit donc que NSArray prend possession des objets qu'on
    // lui fournit
    [arr release]; // detruit le NSArray
    NSLog(@"retain %lu\n", [ch retainCount]); 
    // "1", supprimer le tableau ne supprime pas les objets 
    // qu'il contient, a moins d'avoir appellé `release` sur
    // ces dits objets une fois placés dans le tableau.
}

Version fin 2009 a fin 2011 : Snow Lepoard (10.6)

A partir de cette version, une nouvelle version de MacOS sort chaque année, au lieu de tous les deux ans jusque ici.

GCD et NSBlockOperation

Ajout des blocs et du GCD (grand central dispatch), s’appuye sur les NSOperation et les NSOperationQueue qui ont remplacé les threads POSIX, leur ajoute NSBlockOperation.

ARC

Premiere version de l’ARC, plus besoin de gerer -(void)dealloc, plus besoin d’appeler [alloc] : [[NSString alloc] initWithFormat:@"..."] devient [NSString stringWithFormat: @"..."].

Ce systeme ajoute des instructions release, autorelease, retain automatiquement pour nous (on doit toujours appeller alloc) en fonction des conventions de nommage de nos fonctions, il génère la fonction -(void)dealloc à notre place.

Il se base sur les conventions de nommage des fonction : toute fonction commencant par alloc, new, copy, mutableCopy ou init est supposée allouer de la memoire, ARC ajoute pour nous autorelease pour tous les abjets renvoyés par ces fonctions.

Appeller les fonctions manuellement devient non necessaire, du code bricolé comme ceci n’est plus autorisé

while([x retainCount])
  [x release];

Il faut maintenant réflechir en termes de pointeurs strong et weak pour gérer les reeférences cycliques. Les nouveaux pointeurs __weak se mettent automatiquement à nil quand l’objet auxquels ils réfères est déaloué. Il faut remplacer @property (assign) par @property (weak)

Un pointeur __strong qui réfère à un objet est considéré comme ajoutant un retain dans le compte de références : quand un tel pointeur sort de sa portée, release est rajouté par ARC, ce qui rend dealloc inutile.

__unsafe_unretained simule les pointeurs classiques qu’il faut gérer soi meme.

Voila comment (copy) est réduit

/// objc 1.X

-(NSString*)title {
    return title;
}
-(void)setTitle:(NSString*)newTitle {
    if(title!=newTitle) {
        [title release];
        title = [newTitle copy];
    }
}

/// objc 2.X
@property (copy) NSString* title; // dans le .h
@synthetize title = _title; // dans le .m, inutile avec autosynthetize

nonatomic est utile pour les méthodes qui ne le sont pas.

Slides disponibles dans la WWDC 2011 : https://archive.org/details/wwdc2011-slides/322_objective_c_advancements_in_depth/page/n27/mode/1up 🌍⤴ Video de la WWDC 2012 identique aux slides : WWDC 2012: Adopting Automatic Reference Counting 🌍⤴

Version fin 2011 a fin 2012 : Lion (10.7)

Améliorations de l’ARC. Migration depuis GCC vers LLVM/Clang, de gdb vers lldb, preliminaire a swift.

Apport de @autoreleasepool qui remplace ‘[[NSAutoreleasePool alloc] init]’ et [pool drain]. Vu que NSAutoreleasePool n’est pas un vrai objet que l’on peut retain, ARC ne peut pas travailler avec, une nouvelle construction de langage devient nécessaire.

Version 2012 : Mountain Lion (10.8) XCode 4.4

NSNumber *n = @42; // au lieu de [NSNumber numberWithInteger:42]
NSNumber *ari = @(40 + 2); // arithmetique
NSArray *a = @[@42, @"hello", @12];
NSDictionnary *d = @{foo: @"bar", toto: @:"tata"};

NSString*s = @("toto"); // "toto" peut egalement etre une fonction retournant char*

a[0];
d[foo]; // syntaxe avec crochets pour tableaux et dictionnaires

Plus de détails : What’s new in Cocoa, session 204, WWDC 2012, 35:00 🌍⤴

ARC : autosynthese

ARC remplace la gestion manuelle via retain/release/autorelease.

Les variables d’instance dont les clients de l’interface n’ont pas besoin de connaitre peuvent etre déclarés dans l’implementation. L’autosynthese reduit encore le code necessaire pour les propriétés d’un objet.

methode et propriétés privées implicites

Le compilateur de 2012 peut trouver les attributs et methodes d’interface déclarés dans le fichier d’implementation, plus besoin de déclarer des catégories ou des extensions de classe au sein de fichier .m pour les méthodes. Les attributs privés peuvent etre déclares dans les crochet du @implementation

@interface Thing {
    NSString* private;  /// detail visible aux usagers
}
@end

/// devient
@interface Thing { /// fichier .h, allégé
}
@end

@implementation Thing {  /// .m, invisible aux tiers
    NSString* private;  /// trouvable par le compilateur
}
@end

Les méthodes “privées” peuvent maintenant simplement etre présentes dans l’implémentation, plus besoin d’écrire des catégories d’extension de son interface dans le .m, voila du code qui devient inutile

@interface MonObjet (Prive) /// categorie, typage faible
-(void)methodePrive;
-(void)autreMethodePrive;
@end

@interface MonObjet ()  /// extension de classe, erreur compilateur si methodes manquent
-(void)methodePrive;
-(void)autreMethodePrive;
@end

Plus d’info sur la doc : https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocCategories.html 🌍⤴ Video explicative : Migrating to modern Objective C : 18:00 🌍⤴

Cours en ligne

Blogs en parlant

Interface utilisateur

Emulateur NeXTStep

Archives WWDC (avant WWDC 2014)

References


  1. https://hopl4.sigplan.org/details/hopl-4-papers/18/The-Origins-of-Objective-C-at-PPI-Stepstone-and-its-Evolution-at-NeXT 🌍⤴  ↩︎

  2. Cocoa Programming for Mac Os X, Aaron Hillegass, Adison Weseley Professional, 2002, p.4 ↩︎