.center.middle ## Cocoaheads Smalltalk # objcswitch .footnote[[github.com/n-b/objcswitch](http://github.com/n-b/objcswitch)] --- .center.middle ## Cocoaheads Smalltalk # objcswitch *Comment ajouter des fonctionnalités au language avec des hacks* .footnote[[github.com/n-b/objcswitch](http://github.com/n-b/objcswitch)] --- # Objectif Comme les switch C : int foo = ...; switch(foo) { case 1: ...; break; case 2: ...; break; case 3: ...; break; default: ...; } --- # Objectif Avec des objets, mais sans les trucs tordus : id obj = ...; switch(obj) { case @"foo": {...}; case [NSNumber numberWithInt:1]: {...}; case otherObject: {...}; default: {...}; } --- # Objectif Avec des objets, mais sans les trucs tordus : id obj = ...; switch(obj) { case @"foo": {...}; case [NSNumber numberWithInt:1]: {...}; case otherObject: {...}; default: {...}; } * Pas de `break;` * L'expression évaluée peut être n'importe quel objet, statique ou dynamique. * Chaque case a son propre scope. --- # Objectif Avec des objets, mais sans les trucs tordus : id obj = ...; switch(obj) { case @"foo": {...}; case [NSNumber numberWithInt:1]: {...}; case otherObject: {...}; default: {...}; } * Pas de `break;` * L'expression évaluée peut être n'importe quel objet, statique ou dynamique. * Chaque case a son propre scope. ***On ne peut pas écrire ça sans changer le compilateur.*** --- .center.middle # API La partie artistique --- # API Méthode variadique : id obj = ...; [obj switch: @"bar",^{ ... }, @"foo",^{ ... }, nil]; --- # API Avec un object intermédiaire `case` : @interface case : NSObject + (id) :(id)value_ :(void (^)(void))block; @end et une méthode variadique : id obj = ...; [obj switch: [case :@"bar" :^{ ... }], [case :@"foo" :^{ ... }], nil]; --- # API Déclaration de plusieurs méthodes : id obj = ...; [obj switch_case:@"bar" :^{ ... } case:@"foo" :^{ ... } ... ]; --- # API Déclaration de plusieurs méthodes : id obj = ...; [obj switch_case:@"bar" :^{ ... } case:@"foo" :^{ ... } ... ]; Variante avec block `default:` : id obj = ...; [obj switch_case:@"bar" :^{ ... } case:@"foo" :^{ ... } ... default:^{ ... } ]; --- # API Objet "Switch" intermediaire : id obj = ...; [[obj switch] case:@"bar" :^{ ... } case:@"foo" :^{ ... } ... ]; Variante avec block `default:` : id obj = ...; [[obj switch] case:@"bar" :^{ ... } case:@"foo" :^{ ... } ... default:^{ ... } ]; --- # API ***Il nous faut :*** Une méthode sur NSObject pour obtenir l'objet `switch` : @interface NSObject (objcswitch) - (ObjcSwitch *) switch; @end --- # API ***Il nous faut :*** Une méthode sur NSObject pour obtenir l'objet `switch` : @interface NSObject (objcswitch) - (ObjcSwitch *) switch; @end Des méthodes sur `case::case::`, avec et sans block `default:` : @interface ObjcSwitch : NSObject - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b ... default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b ... ; @end --- .center.middle # Implémentation La partie technique --- # Implémentation Une seule fonction sert d'implémentation pour toutes les méthodes. static void objcswitch(ObjcSwitch * self, SEL _cmd, ...) { ... } --- # Implémentation Une seule fonction sert d'implémentation pour toutes les méthodes. static void objcswitch(ObjcSwitch * self, SEL _cmd, ...) { ... } Ajoutée à la demande : + (BOOL)resolveInstanceMethod:(SEL)aSEL { const char* selector = sel_getName(aSEL); // Check selector looks like // case::case:: // or // case::case::default: ... class_addMethod(self, aSEL, (IMP) objcswitch, types); return YES; } --- # Implémentation `_cmd` est utilisé pour connaître le nombre d'arguments. static void objcswitch(ObjcSwitch * self, SEL _cmd, ...) { va_list args; va_start(args, _cmd); // Find out number of arguments in our va_list const char* selector = sel_getName(_cmd); BOOL hasDefaultBlock = (strlen(selector) % strlen("case::")) != 0; size_t caseCount = (strlen(selector)-(hasDefaultBlock?strlen("default:"):0)) / strlen("case::"); ... --- # Implémentation Il ne reste plus qu'à boucler sur les `case:`: ... for (size_t i=0; i
receiver isEqual:value]) { block(); goto cleanup; } } if(hasDefaultBlock) { void (^block)(void) = va_arg(args, void (^)(void)); block(); } cleanup: va_end(args); } --- .center.middle # Déclaration La partie gros hack. --- # Déclaration Il faut déclarer toutes les variantes des méthodes : - (void) case:(id)v :(void (^)(void))b default:(void (^)(void))b; --- # Déclaration Il faut déclarer toutes les variantes des méthodes : - (void) case:(id)v :(void (^)(void))b default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b default:(void (^)(void))b; --- # Déclaration Il faut déclarer toutes les variantes des méthodes : - (void) case:(id)v :(void (^)(void))b default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b default:(void (^)(void))b; --- # Déclaration Il faut déclarer toutes les variantes des méthodes : - (void) case:(id)v :(void (^)(void))b default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b default:(void (^)(void))b; --- # Déclaration Il faut déclarer toutes les variantes des méthodes : - (void) case:(id)v :(void (^)(void))b default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b default:(void (^)(void))b; - (void) case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b case:(id)v :(void (^)(void))b default:(void (^)(void))b; --- # Déclaration (et les mêmes sans les blocks `default:`) --- .center.middle # Mmm… --- .center.middle # Templates ! --- .center.middle #
Templates !
--- .center.middle # Preprocessor ! --- .center.middle # Preprocessor ! `__INCLUDE_LEVEL__` --- .center.middle # Preprocessor ! `__INCLUDE_LEVEL__` X-Macros --- # #include Récursifs objcswitch_switch.def : #if __INCLUDE_LEVEL__ < OBJCSWITCH_MAX_CASE_COUNT - (void) case:(id)v :(void (^)(void))b #include "objcswitch_case.def" default:(void (^)(void))b; #include "objcswitch_switch.def" // include myself for next method #endif --- # #include Récursifs objcswitch_switch.def : #if __INCLUDE_LEVEL__ < OBJCSWITCH_MAX_CASE_COUNT - (void) case:(id)v :(void (^)(void))b #include "objcswitch_case.def" default:(void (^)(void))b; #include "objcswitch_switch.def" // include myself for next method #endif objcswitch_case.def : #if __INCLUDE_LEVEL__ < OBJCSWITCH_MAX_CASE_COUNT case:(id)v :(void (^)(void))b #include "objcswitch_case.def" // include myself for next line #endif --- # X-Macros objcswitch_switch.def : #if __INCLUDE_LEVEL__ < OBJCSWITCH_MAX_CASE_COUNT - (void) case:(id)v :(void (^)(void))b #include "objcswitch_case.def" #if OBJCSWITCH_DEFAULT_BLOCK default:(void (^)(void))b; #else ; #endif #include "objcswitch_switch.def" // include myself for next method #endif --- # X-Macros objcswitch_switch.def : #if __INCLUDE_LEVEL__ < OBJCSWITCH_MAX_CASE_COUNT - (void) case:(id)v :(void (^)(void))b #include "objcswitch_case.def" #if OBJCSWITCH_DEFAULT_BLOCK default:(void (^)(void))b; #else ; #endif #include "objcswitch_switch.def" // include myself for next method #endif NSObject+objcswitch.h : #define OBJCSWITCH_DEFAULT_BLOCK TRUE #include "objcswitch_switch.def" #define OBJCSWITCH_DEFAULT_BLOCK FALSE #include "objcswitch_switch.def" --- .center.middle # Améliorations en bonus --- # Améliorations Selecteur de comparaison : [[@"foo" switchUsingSelector:@selector(localizedCompare:)] case:@"bar" :^{ ... } case:@"Foo" :^{ ... } ]; Ou block `NSComparator` : [[@"foo" switchUsingComparator:^(id obj1, id obj2) { ... }] case:@"bar" :^{ ... } case:@"Foo" :^{ ... } ]; --- # Améliorations Options faciles pour les comparaisons de strings (case-insensitive, diacritics-insensitive…) : [[@"foo" switchWithStringOptions:NSCaseInsensitiveSearch] case:@"bar" :^{ ... } case:@"Foo" :^{ ... } ]; --- # Améliorations Parallélisation, Execution de tous les `case` égaux à l'objet évalué : [[@"foo" switchWithOptions:OCSwitchOptionsAllMatchingBlocks] case:object1 :^{ ... } case:object2 :^{ ... } ]; --- # Améliorations Syntaxe avec Objective-C literals (Apple LLVM Compiler 4.0, clang v3.1) : id obj = ...; [[obj switch] case:@"abc" :^{ ... } case:@123 :^{ ... } case:@{@"a",@"b"} :^{ ... } ]; --- .center.middle # Liens [Trampoline Pattern](http://en.wikipedia.org/wiki/Trampoline_%28computing%29) [X-Macros](http://drdobbs.com/cpp/184401387) [Objective-C literals in clang](http://clang.llvm.org/docs/ObjectiveCLiterals.html) .footnote[[github.com/n-b/objcswitch](github.com/n-b/objcswitch)]