web-dev-qa-db-fra.com

Utilisation d'une NSString dans une instruction switch

Est-il possible d'utiliser une variable NSString dans une instruction switch?

Ou vaut-il mieux utiliser if/else if?

51
Thomas Clayson

l'instruction switch nécessite des constantes de nombre entier pour les cas où NSString ne peut pas être utilisé ici. Il semble donc que vous devez choisir l'option if/else. 

Un autre point est que vous devez comparer NSStrings avec la méthode isEqualToString: ou compare: ainsi, même si les valeurs de pointeur ont été autorisées pour les cas de commutation, vous ne pouvez pas les utiliser.

50
Vladimir

J'utilise ces macros dans mon application.

#define CASE(str)                       if ([__s__ isEqualToString:(str)]) 
#define SWITCH(s)                       for (NSString *__s__ = (s); ; )
#define DEFAULT   

SWITCH (string) {
    CASE (@"AAA") {
        break;
    }
    CASE (@"BBB") {
        break;
    }
    CASE (@"CCC") {
        break;
    }
    DEFAULT {
        break;
    }
 }
68
user1717750

En réponse et à l'appui de la réponse de @ Cœur .. Voici la même chose, mais écrite en Xcode 4.4+/clang/ N'importe quelle "syntaxe littérale" qui est encore plus proche d'une simple comparaison de if, else dans les urinaires (et c'est bien le but, n'est-ce pas .....)

NSDictionary *actionD = @{ @"A" : ^{ NSLog(@"BlockA!"); }, 
                           @"B" : ^{ NSLog(@"BlockB!"); }};

((void(^)()) actionD[@"A"])(); 

BlockA!

ou dire, vous voulez effectuer un sélecteur basé sur le titre d'un bouton ...

- (IBAction) multiButtonTarget:button { 

((void (^)())                           // cast
  @{ @"Click?" : ^{ self.click; }, 
     @"Quit!"  : ^{   exit(-1); }}      // define
        [((NSButton*)button).title])    // select
                                    (); // execute
}

Quit! ⟹ exit -1

Bref, comme w.string = kIvar == 0 ? @"StringA" : @"StringB";, et bien plus utile, car vous pouvez y glisser des blocs sans même penser à quelque chose de terrible (et limité, et compliqué) @selector!

EDIT: Ceci est plus évidemment construit comme tel:

[@[ @"YES", @"NO", @"SIRPOOPSALOT"] do:^(id maybe) {
    [maybe isEqual:@"YES"] ? ^{ NSLog(@"You got it!"); }()
  : [maybe isEqual:@"NO" ] ? ^{ NSLog(@"You lose!!!"); }() 
  :                          ^{ NSLog(@"Not sure!"); [self tryAgain]; }();
}]; 

*** You got it! ****** You lose!!! ****** Not sure! ***

Je dois admettre que je suis embarrassant EN ce genre de bêtise syntaxique. Une autre option est d'oublier ce qu'est la chaîne. Il suffit de l'exécuter, lol ...

[ @{ NSApplicationWillBecomeActiveNotification : @"slideIn",
     NSApplicationDidResignActiveNotification  : @"slideOut" } each:^( id key, id obj ) {
    [w observeObject:NSApp forName:obj calling: NSSelectorFromString ( obj ) ];       
}];

ou en prenant la Parole de l'interface utilisateur pour cela, littéralement ..

- (IBAction)setSomethingLiterallyWithSegmentedLabel:(id)sender {
   NSInteger selectedSegment = [sender selectedSegment];
   BOOL isSelected = [sender isSelectedForSegment:selectedSegment];
   BOOL *optionPtr = &isSelected;
   SEL fabricated = NSSelectorFromString
       ([NSString stringWithFormat:@"set%@:",[sender labelForSegment:selectedSegment]]);
   [self performSelector:fabricated withValue:optionPtr];
}
12
Alex Gray

L'instruction Switch ne fonctionnerait pas avec NSString: elle ne fonctionnerait qu'avec int.

Si/Else est trop de code et n'est souvent pas optimal.

La solution optimale consiste à utiliser un NSDictionary indexé par les possibilités de NSString (ou d’autres objets). Ensuite, vous accédez directement à la bonne valeur/fonction.

Exemple 1, lorsque vous souhaitez tester @ "A" ou @ "B" et exécuter methodA ou methodB:

NSDictionary *action = @{@"A" : [NSValue valueWithPointer:@selector(methodA)],
                         @"B" : [NSValue valueWithPointer:@selector(methodB)],
                         };
[self performSelector:[action[stringToTest] pointerValue]];

Exemple 2, lorsque vous souhaitez tester @ "A" ou @ "B" et exécuter blockA ou blockB:

NSDictionary *action = @{@"A" : ^{ NSLog (@"Block A"); },
                         @"B" : ^{ NSLog (@"Block B"); },
                         };
((void (^)())action[stringToTest])();
9
Cœur

inspiré par Alex Gray, j'ai créé une méthode de catégorie qui applique des filtres chaînés à son objet:

.h

#import <Foundation/Foundation.h>
typedef id(^FilterBlock)(id element,NSUInteger idx, BOOL *stop);

@interface NSObject (Functional)
-(id)processByPerformingFilterBlocks:(NSArray *)filterBlocks;
@end

.m

#import "NSObject+Functional.h"

@implementation NSObject (Functional)
-(id)processByPerformingFilterBlocks:(NSArray *)filterBlocks
{
    __block id blockSelf = self;
    [filterBlocks enumerateObjectsUsingBlock:^( id (^block)(id,NSUInteger idx, BOOL*) , NSUInteger idx, BOOL *stop) {
        blockSelf = block(blockSelf, idx, stop);
    }];

    return blockSelf;
}
@end

Vous pouvez l'utiliser comme 

FilterBlock fb1 = ^id(id element, NSUInteger idx, BOOL *stop){ if ([element isEqualToString:@"YES"]) { NSLog(@"You did it");  *stop = YES;} return element;};
FilterBlock fb2 = ^id(id element, NSUInteger idx, BOOL *stop){ if ([element isEqualToString:@"NO"] ) { NSLog(@"Nope");        *stop = YES;} return element;};

NSArray *filter = @[ fb1, fb2 ];
NSArray *inputArray = @[@"NO",@"YES"];

[inputArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    [obj processByPerformingFilterBlocks:filter];
}];

mais vous pouvez aussi faire des choses plus compliquées, comme des calculs chianés appliqués:

FilterBlock b1 = ^id(id element,NSUInteger idx, BOOL *stop) {return [NSNumber numberWithInteger:[(NSNumber *)element integerValue] *2 ];};
FilterBlock b2 = ^id(NSNumber* element,NSUInteger idx, BOOL *stop) {
    *stop = YES;
    return [NSNumber numberWithInteger:[element integerValue]*[element integerValue]];
};
FilterBlock b3 = ^id(NSNumber* element, NSUInteger idx,BOOL *stop) {return [NSNumber numberWithInteger:[element integerValue]*[element integerValue]];};

NSArray *filterBlocks = @[b1,b2, b3, b3, b3];
NSNumber *numberTwo = [NSNumber numberWithInteger:2];
NSNumber *numberTwoResult = [numberTwo processByPerformingFilterBlocks:filterBlocks];    
NSLog(@"%@ %@", numberTwo, numberTwoResult);
1
vikingosegundo

Je sais que je suis un peu en retard pour le parti, mais voici mon argument en faveur d’une déclaration d’object-c switch. C'est un peu complexe, alors supportez les macros laides.

Caractéristiques:

  • ressemble à une instruction switch 
  • Atomicité de thread intégrée
  • Fonctionne avec tous les objets objective-c, pas seulement NSString (en utilisant le sélecteur -isEqual:)
  • Peut être étendu pour fonctionner avec les types C aussi, en excluant structs (car ils n'ont pas d'opérateur ==)
  • Une seule étiquette de cas peut être évaluée par instruction de commutateur (break n'est pas obligatoire)
  • Pas de risque de boucle infinie si aucun cas n'est sélectionné (break n'est pas obligatoire)
  • Ne réserve qu'un seul nom de variable, ____dontuse_switch_var (tous les autres sont dans la portée statique et peuvent être remplacés dans la portée locale)
  • N'entraîne aucun problème de conservation impair (utilise les références __weak)

Désavantages:

  • Très difficile à comprendre la source
  • Toutes les instructions de cas sont évaluées avant que celle-ci ne soit sélectionnée (mettez donc les plus fréquentes en haut)
  • La atomicité des threads a un coût en termes de performances: elle ne nécessite aucun verrou, mais utilise NSThread assez souvent.
  • Sans utiliser les crochets { ou }, Xcode n'aime pas formater correctement les instructions (cela est dû au libellé implicite goto qu'il contient.
  • Pas seulement l'en-tête (nécessite un fichier .m pour fonctionner, pour NSValue références faibles)

Exemple:

#include "OBJC_SWITCH.h"

int main()
{
    NSArray *items = @[ @"A", @"B", @"C", @"D", @"E", @"F" ];

    for (int i = 0; i < items.count; i++)
    {
        $switch(items[i]) {
            $case(@"A"):
            {
                NSLog(@"It was A!");
                break;
            }
            $case(@"B"): // no brackets, no break, still works
                NSLog(@"It was B!");

            $case(@"C"): // continue works as well, there's no difference
            {
                NSLog(@"It was C!");
                continue;
            }

            $default: // brackets, but no break.
            {
                NSLog(@"Neither A, B, or C.");
            } 
        }
    }
}

Sans plus tarder, voici le code (moche):

OBJC_SWITCH.h:

#import "NSValue+WeakRef.h"

// mapping of threads to the values being switched on
static NSMutableDictionary *____dontuse_switch_variable_dictionary;

// boolean flag to indicate whether or not to stop switching
static NSMutableDictionary *____dontuse_switch_bool_dictionary;

// simple function to return the current thread's switch value
static inline id current_thread_switch_value()
{
    // simple initializer block
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!____dontuse_switch_variable_dictionary)
            ____dontuse_switch_variable_dictionary = [NSMutableDictionary dictionary];
    });

    return [[____dontuse_switch_variable_dictionary objectForKey:[NSValue valueWithWeakObject:[NSThread currentThread]]] weakObjectValue];
}

// simple function to set the current thread's switch value
static inline void set_current_thread_switch_value(id val)
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!____dontuse_switch_variable_dictionary)
            ____dontuse_switch_variable_dictionary = [NSMutableDictionary dictionary];
    });

    [____dontuse_switch_variable_dictionary setObject:[NSValue valueWithWeakObject:val] forKey:[NSValue valueWithWeakObject:[NSThread currentThread]]];
}

// check if the current thread has switched yet
static inline BOOL current_thread_has_switched()
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!____dontuse_switch_bool_dictionary)
            ____dontuse_switch_bool_dictionary = [NSMutableDictionary dictionary];
    });

    return [[____dontuse_switch_bool_dictionary objectForKey:[NSValue valueWithWeakObject:[NSThread currentThread]]] boolValue];
}

// set the current thread's switch state
static inline void set_current_thread_has_switched(BOOL b)
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!____dontuse_switch_bool_dictionary)
            ____dontuse_switch_bool_dictionary = [NSMutableDictionary dictionary];
    });

    [____dontuse_switch_bool_dictionary setObject:[NSNumber numberWithBool:b] forKey:[NSValue valueWithWeakObject:[NSThread currentThread]]];
}

// concatenate two tokens
#define $_concat(A, B) A ## B
#define $concat(A, B) $_concat(A, B)

/* start of switch statement */
#define $switch(value) { \
/* set this thread's switch value */ \
set_current_thread_switch_value(value); \
/* make sure we reset the switched value for the thread */ \
set_current_thread_has_switched(0); \
/* if statement to ensure that there is a scope after the `switch` */ \
} if (1)

/* a case 'label' */
#define $case(value) \
/* make sure we haven't switched yet */ \
if(!current_thread_has_switched() && \
/* check to see if the values are equal */ \
[current_thread_switch_value() isEqual:value]) \
/* basically, we are creating a 'for' loop that only iterates once, so that we support the 'break' statement, without any harm */ \
/* this also sets the 'switched' value for this thread */ \
for (int ____dontuse_switch_var = 0; set_current_thread_has_switched(1), ____dontuse_switch_var != 1; ____dontuse_switch_var++) \
/* this creates the case label (which does nothing) so that it 'looks' like a switch statement. */ \
/* technically, you could to a goto with this, but I don't think anyone would purposely do that */ \
$concat(__objc_switch_label, __COUNTER__)

/* the default 'label' */
#define $default \
/* this only evaluates if we haven't switched yet (obviously) */ \
if (!current_thread_has_switched()) \
/* exact same loop as for the 'case' statement, which sets that we have switched, and only executes once. */ \
for (int ____dontuse_switch_var = 0; set_current_thread_has_switched(1), ____dontuse_switch_var != 1; ____dontuse_switch_var++) \
/* once again, create a case label to make it look like a switch statement */ \
$concat(__objc_switch_label, __COUNTER__)

Je ne vais pas fournir de documentation à ce sujet, car cela se passe d'explication. Si c'est vraiment difficile à comprendre, laissez un commentaire et je documenterai le code.

NSValue + WeakRef.h:

#import <Foundation/Foundation.h>

@interface NSValue(WeakRef)

+(id) valueWithWeakObject:(__weak id) val;
-(id) initWithWeakObject:(__weak id) val;

-(__weak id) weakObjectValue;

@end

NSValue + WeakRef.m:

#import "NSValue+WeakRef.h"

@interface ConcreteWeakValue : NSValue
{
    __weak id _weakValue;
}

@end

@implementation NSValue(WeakRef)

+(id) valueWithWeakObject:(id) val
{
    return [ConcreteWeakValue valueWithWeakObject:val];
}

-(id) initWithWeakObject:(id)val
{
    return [NSValue valueWithWeakObject:val];
}

-(id) weakObjectValue
{
    [self doesNotRecognizeSelector:_cmd];

    return nil;
}

@end

@implementation ConcreteWeakValue

+(id) valueWithWeakObject:(__weak id)val
{
    return [[self alloc] initWithWeakObject:val];
}

-(id) initWithWeakObject:(__weak id)val
{
    if ((self = [super init]))
    {
        _weakValue = val;
    }

    return self;
}

-(const char *) objCType
{
    return @encode(__weak id);
}

-(__weak id) weakObjectValue
{
    return _weakValue;
}

-(void) getValue:(void *)value
{
    * ((__weak id *) value) = _weakValue;
}

-(BOOL) isEqual:(id)object
{
    if (![object isKindOfClass:[self class]])
        return NO;

    return [object weakObjectValue] == [self weakObjectValue];
}

@end
1

Comme tout le monde l’a noté, il est probablement plus simple d’utiliser if/else, mais vous pouvez créer quelque chose qui ressemble beaucoup à une instruction switch. J'ai créé un projet sur GitHub qui fait exactement cela: WSLObjectSwitch . C'est une implémentation assez naïve, elle n'optimise pas l'utilisation de hachages, etc., mais ça fonctionne.

1
Stephen Darlington

Ceci est généralement où j'utilise quelque chose comme une enum. Si je dois gérer autant de valeurs, je crée simplement une énumération portant le même nom que la chaîne que j'aurais transmise autrement et la transmettrai ici, par exemple:

enum {
    EGLFieldSelectionToolbarItem = 0,
    EGLTextSelectionToolbarItem,
};
+(NSImage *)iconForIdentifier:(int)alias WithSize:(NSSize)size; //Alias should be an enum value with the same name
NSImage *icon = [[NSImage alloc]initWithSize:size];
  NSBezierPath *bezierPath = [NSBezierPath bezierPath];
[icon lockFocus];
switch (alias) {
    case EGLFieldSelectionToolbarItem:
        …//Drawing code
        break;
    case EGLTextSelectionToolbarItem:
        …//More drawing code
    default:
        break;
}
[bezierPath stroke];
[icon unlockFocus];
return icon;
}
0
PopKernel

vous pouvez facilement basculer entre les boutons pour différentes actions en utilisant leurs balises. 

Exemple : 

- (IBAction)addPost:(id)sender {
switch ([sender tag]) {
    case 1:
        break;
    case 2:
        break;
    case 3:
        break;
    case 4:
        break;
    case 5:
        break;
    default:
        break;
}

}

0
Gurjot Kalsi