Usar una NSString en una instrucción switch


¿Es posible usar un NSString en una instrucción switch?

O es mejor simplemente usar if / else if?

Author: Josh Caswell, 2010-11-19

9 answers

La instrucción Switch requiere constantes enteras para sus casos, por lo que NSString no se puede usar aquí, por lo que parece que debe optar por la opción if/else.

Un punto más es que debe comparar NSStrings usando el método isEqualToString: o compare:, por lo que incluso si se han permitido valores de puntero para casos de conmutación, no podría usarlos de todos modos

 47
Author: Vladimir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-19 11:29:46

Utilizo estas macros en mi aplicación.

#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;
    }
 }
 67
Author: user1717750,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-21 13:57:43

En respuesta y en apoyo de la respuesta de @Cœur.. Aquí está lo mismo, pero escrito en Xcode 4.4+ / clang / Cualquier "sintaxis literal" que sea aún más cercana a una simple comparación urnaria if, else (y ese es el punto, ¿no?).....)

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

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

BlockA!

O digamos, desea realizar un selector basado en el título de un botón...

- (IBAction) multiButtonTarget:button { 

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

¡Renuncia!exit -1

Breve, como w.string = kIvar == 0 ? @"StringA" : @"StringB";, y mucho más útil, ya que puede empujar bloques allí sin siquiera un pensamiento de algunos espantoso (y limitado, y enrevesado) @selector!

EDITAR: Esto es más obviamente construido como tal:

[@[ @"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! ***

Tengo que admitir, estoy vergonzosamente EN este tipo de estupidez sintáctica. Otra opción es olvidarse de lo que es la cadena.. solo ejecútalo, lol...

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

O tomar la palabra de la interfaz de usuario para ello, literalmente..

- (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
Author: Alex Gray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-24 12:57:19

La instrucción Switch no funcionaría con NSString: solo funciona con int.

La instrucción If/Else es demasiado código y a menudo no es óptima.

La solución óptima es usar un NSDictionary indexado por las posibilidades NSString (u otros objetos). Luego accede directamente al valor/función correcto.

Ejemplo 1, cuando desea probar @ " A "o @" B " y realizar methodA o methodB:

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

Ejemplo 2, cuando desea probar @ " A "o @" B " y realizar blockA o BlockB:

NSDictionary *action = @{@"A" : ^{ NSLog (@"Block A"); },
                         @"B" : ^{ NSLog (@"Block B"); },
                         };
((void (^)())action[stringToTest])();
 9
Author: Cœur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-06 20:39:17

Inspirado por alex gray, creé un método de categoría, que aplica filtros encadenados a su objeto:

.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

Puede usarlo como

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];
}];

Pero también puedes hacer cosas más complicadas, como cálculos de chianed aplicados:

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
Author: vikingosegundo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-03 21:34:19

Sé que llego un poco tarde a la fiesta, pero aquí está mi presentación para una declaración de cambio de objective-c. Es un poco complejo, así que ten paciencia con las macros feas.

Características:

  • parece como una sentencia switch
  • Tiene atomicidad de hilo incorporada
  • Funciona con todos los objetos objective-c, no solo NSString (usando el selector -isEqual:)
  • Podría extenderse para trabajar con tipos C también, salvo struct s (ya que no tienen == operator)
  • Solo se puede evaluar una etiqueta de caso por instrucción switch (break no se requiere)
  • No hay posibilidades de bucle infinito si no se selecciona ningún caso (break no se requiere)
  • Solo reserva un nombre de variable, ____dontuse_switch_var (todos los demás están en el ámbito estático y se pueden sobrescribir en el ámbito local)
  • No resulta en ningún problema de retención impar (usa referencias __weak)

Inconvenientes:

  • Muy difícil de comprender la fuente
  • Todas las sentencias de caso se evalúan antes de que se seleccione una (así que ponga las más frecuentes en la parte superior)
  • La atomicidad del hilo tiene un costo de rendimiento: no requiere ningún bloqueo, pero usa NSThread bastante ampliamente.
  • Sin usar corchetes { o }, a Xcode no le gusta formatear las instrucciones correctamente (esto se debe a la etiqueta implícita goto allí.
  • No solo encabezado (requiere un archivo .m para trabajar, para NSValue referencias débiles)

Ejemplo:

#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.");
            } 
        }
    }
}

Sin más preámbulos, aquí está el código (feo):

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__)

No voy a proporcionar documentación para esto, ya que se explica por sí mismo. Si es realmente tan difícil de entender, deja un comentario y documentaré el código.

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
Author: Richard J. Ross III,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-29 01:47:50

Como todos los demás han señalado, probablemente sea más fácil usar if/else, pero puede crear algo que se parezca mucho a una instrucción switch. Creé un proyecto en GitHub que hace exactamente eso: WSLObjectSwitch. Es una implementación bastante ingenua, no optimiza el uso de hashes, etc. pero funciona.

 1
Author: Stephen Darlington,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-04 06:59:12

Esto es típicamente donde uso algo como una enumeración. Si tengo que administrar tantos valores, simplemente creo una enumeración con el mismo nombre que la cadena que habría pasado de lo contrario y la paso allí, por ejemplo:

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
Author: PopKernel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-05 18:26:33

Puede cambiar fácilmente entre los botones para diferentes acciones utilizando sus etiquetas.

Ejemplo:

- (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
Author: Gurjot Kalsi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-06 07:34:20