web-dev-qa-db-fra.com

la meilleure façon d'implémenter le cas de commutateur lisible avec des chaînes dans Objective-C?

Dans d'autres langages dynamiques comme Ruby, javascript, etc., vous pouvez simplement faire ceci:

switch(someString) {
    case "foo":
       //do something;
       break;
    case "bar":
       // do something else;
       break;
    default:
       // do something by default;
}

Dans Objective-C, comme il provient très cols du langage C, vous ne pouvez pas faire cela. Ma meilleure pratique pour cela est:

#import "CaseDemo.h"

#define foo 1
#define bar 2

static NSMutableDictionary * cases;

@implementation CaseDemo

- (id)init 
{
    self = [super init];
    if (self != nil) {
        if (cases == nil) {
            // this dict can be defined as a class variable
            cases = [[NSMutableDictionary alloc] initWithCapacity:2];
            [cases setObject:[NSNumber numberWithInt:foo] forKey:@"foo"];
            [cases setObject:[NSNumber numberWithInt:bar] forKey:@"bar"];
        }
    }
    return self;
}

- (void) switchFooBar:(NSString *) param {
    switch([[cases objectForKey:param] intValue]) {
        case foo:
            NSLog(@"its foo");
            break;
        case bar:
            NSLog(@"its bar");
            break;
        default: 
            NSLog(@"its default");
            break;
    }
}
@end

Cela semble aller, mais #define rend foo and bar comme un mot réservé, et je ne peux pas l'utiliser dans mon code. Si je remplace les constantes de définition par des constantes de classe, ce problème est résolu car, dans d'autres classes, je dois utiliser MyClassName avant le nom de la constante. Mais comment puis-je minimiser l'affectation d'objets pour cette tâche simple? Quelqu'un a une "meilleure pratique" pour cela?

EDIT: Le code ci-dessous correspond à ce que je voulais faire, mais il est un peu inconfortable d’obtenir les valeurs de l’énum ou de #define. Parce que j'ai créé une application qui ne contient qu'une entrée où je peux écrire la chaîne pour obtenir ce hachage et revenir à xcode et définir les valeurs pour les enums. Donc, mon problème est que je ne peux pas le faire au moment de l’exécution, à cause du comportement principal de la déclaration de cas de commutation ... Ou si je le fais avec cette méthode NSDictionary -> il ya beaucoup de frais généraux comparé à cette solution.

#import "CaseDemo.h"

typedef enum {
    foo = 1033772579,
    bar = -907719821
} FooBar;

unsigned int APHash(NSString* s)
{
    const char* str = [s UTF8String];
    unsigned int len = [s length];    

    unsigned int hash = 0xAAAAAAAA;
    unsigned int i    = 0;

    for(i = 0; i < len; str++, i++)
    {
        hash ^= ((i & 1) == 0) ? (  (hash <<  7) ^ (*str) * (hash >> 3)) :
        (~((hash << 11) + ((*str) ^ (hash >> 5))));
    }

    return hash;
}

@implementation CaseDemo


- (void) switchFooBar:(NSString *) param {
    switch(APHash(param)) {
        case foo:
            NSLog(@"its foo");
            break;
        case bar:
            NSLog(@"its bar");
            break;
        default: 
            NSLog(@"its default");
            break;

    }
}

@end 

NOTE: la fonction de hachage peut être définie ailleurs dans l'espace de noms commun pour pouvoir être utilisée n'importe où. Généralement, je crée un fichier Utils.h ou Common.h pour ce genre de choses.

NOTE2: Dans "Real Word", nous devons utiliser une fonction de hachage cryptographique, mais maintenant, j'ai utilisé l'algorithme d'Arash Partow pour garder l'exemple simple.

Donc, ma dernière question: Existe-t-il un moyen d'évaluer ces valeurs avec le pré-processeur? Je pense non, mais peut-être? :-)

Quelque chose comme:

// !!!!!! I know this code is not working, I don't want comments about "this is wrong" !!!!
// I want a solution to invoke method with preprocessor, or something like that. 
typedef enum {
        foo = APHash(@"foo"),
        bar = APHash(@"bar")
    } FooBar;

UPDATE: J'ai trouvé une "solution possible" mais cela semble fonctionner avec g ++ 4.6> uniquement. expressions constantes généralisées peut être le faire pour moi. Mais je teste toujours ...

15
Dam
typedef enum {
    foo,
    bar
} FooBar;

- (void) switchFooBar:(NSString *) param {
    switch([[cases objectForKey:param] intValue]) {
        case foo:
            NSLog(@"its foo");
            break;
        case bar:
            NSLog(@"its bar");
            break;
        default: 
            NSLog(@"its default");
            break;
    }
}
7
kubi
NSString * extension = [fileName pathExtension];
NSString * directory = nil;

NSUInteger index = [@[@"txt",@"png",@"caf",@"mp4"] indexOfObjectPassingTest:^
                    BOOL(id obj, NSUInteger idx, BOOL *stop)
{
    return [obj isEqualToString:extension];
}];

switch (index)
{
    case 0:
        directory = @"texts/";
        break;
    case 1:
        directory = @"images/";
        break;
    case 2:
        directory = @"sounds/";
        break;
    case 3:
        directory = @"videos/";
        break;
    default:
        @throw [NSException exceptionWithName:@"unkonwnFileFormat"
                                       reason:[NSString stringWithFormat:@"Zip file contain nknown file format: %@",fileName]
                                     userInfo:nil];
        break;
}
1
Nicolas Manzini

La technique est extraite du code de production et modifiée pour utiliser des couleurs pour cet exemple. Le principe est qu'une chaîne vient avec le nom de texte d'une couleur provenant d'un flux externe. Ce nom de couleur entrant est mis en correspondance avec les noms de couleur Crayola connus dans le système. Si le nouveau nom de couleur correspond à une chaîne de nom de couleur Crayola connue, la valeur numérique correspondant au code HTML hexadécimal équivalent à ce nom de couleur Crayola est renvoyée.

Commencez par utiliser http://www.unit-conversion.info/texttools/crc/ et placez-y tous vos noms de couleurs Crayola connus pour obtenir des équivalents numériques. Ceux-ci seront utilisés dans les déclarations de cas. Ensuite, mettez ces valeurs dans un énuméré pour la propreté (par exemple LivingColors ci-dessous). Ces nombres deviennent équivalents à la chaîne de nom de couleur réelle.

Ensuite, au moment de l'exécution, le texte variable est soumis à la même fonction, mais interne à votre code, pour générer le même type de constante numérique. Si la constante numérique du code correspond à la constante générée de manière statique, les chaînes de texte qu'elles représentent sont exactement égales. 

La fonction de code interne est crc32() trouvée dans zlib.h. Cela génère un numéro unique basé sur le texte mis en travers, tout comme le convertisseur de page Web ci-dessus. Le numéro unique de crc32() peut ensuite être utilisé dans une instruction C switch() commune pour faire correspondre les couleurs connues qui ont été prétraitées en nombres dans l'énumération. 

Pour utiliser la fonction système native crc32() afin de générer des valeurs CRC32B, incluez le /usr/lib/libz.1.dylib dans votre projet pour la liaison. Assurez-vous d'inclure ou #import <zlib.h> dans votre source qui référence crc32()

Implémentez une catégorie Objective C sur NSString pour que la classe native NSString comprenne les messages crc32: et htmlColor:

Enfin, lisez/récupérez le nom de la couleur dans un objet NSString, puis envoyez le message htmlColor: à la chaîne, qui correspond aux «chaînes» et renvoie la valeur HTML hex équivalente équivalente à un nom de couleur Crayola.

#import <zlib.h>

#define typedefEnum( enumName )               typedef enum enumName enumName; enum enumName

/**
    @see Crayola Web Colors https://www.w3schools.com/colors/colors_crayola.asp
    @see CRC32B value generator for static case strings http://www.unit-conversion.info/texttools/crc/ or http://www.md5calc.com
*/

#define typedefEnum( enumName ) typedef enum enumName enumName; enum enumName

typedefEnum( LivingColors ) {
    kRedColor                   = 0xc22c196f,    // "Red" is 0xED0A3F in HTML
    kBlueberryColor             = 0xfbefa670,    // "Blueberry" is 0x4F86F7 in HTML
    kLightChromeGreenColor      = 0x44b77242,    // "Light Chrome Green" is 0xBEE64B in HTML
    kPermanentGeraniumLakeColor = 0xecc4f3e4,    // "Permanent Geranium Lake" is 0xE12C2C in HTML
    kIlluminatingEmeraldColor   = 0x4828d5f2,    // "Illuminating Emerald" is 0x319177 in HTML
    kWildWatermelonColor        = 0x1a17c629,    // "Wild Watermelon" is 0xFD5B78 in HTML
    kWashTheDogColor            = 0xea9fcbe6,    // "Wash the Dog" is 0xFED85D in HTML
    kNilColor                   = 0xDEADBEEF     // could use '0' but what fun is that?
};


// generates the CRC32B, same used to check each ethernet packet on the network you receive so it’s fast
- (NSUInteger) crc32 {
    NSUInteger theResult;

    theResult = (NSUInteger)crc32(  0L,
                                    (const unsigned char *) [self UTF8String],
                                    (short)self.length);

    return theResult;
}

/// @return the HTML hex value for a recognized color name string.
- (NSUInteger) htmlColor {
    NSUInteger      theResult               = 0x0;
    LivingColors    theColorInLivingColor   = kNilColor;

    theColorInLivingColor = (LivingColors) [self crc32];
     // return the HTML value for a known color by effectively switching on a string.
    switch ( theColorInLivingColor ) {

        case kRedColor : {
            theResult = 0xED0A3F;
        }
            break;

        case kBlueberryColor : {
            theResult = 0x4F86F7;
        }
            break;

        case kLightChromeGreenColor : {
            theResult = 0xBEE64B;
        }
            break;

        case kPermanentGeraniumLakeColor : {
            theResult = 0xE12C2C;
        }
            break;

        case kIlluminatingEmeraldColor : {
            theResult = 0x319177;
        }
            break;

        case kWildWatermelonColor : {
            theResult = 0xFD5B78;
        }
            break;

        case kWashTheDogColor : {
            theResult = 0xFED85D;
        }
            break;

        case kNilColor :
        default : {
            theResult = 0x0;
        }
            break;

    }

    return theResult;
}

Pour l'exemple, une catégorie d'objectif C a été créée pour ajouter les deux méthodes de la classe Cocoa existante NSString, plutôt que de la sous-classer. 

Le résultat final est qu'un objet NSString apparaît pour pouvoir obtenir de manière native une valeur CRC32B de lui-même (très pratique au-delà de cet exemple) et peut essentiellement switch() sur la chaîne de nom de la couleur éventuellement issue du utilisateur, un fichier texte, etc. pour identifier une correspondance beaucoup plus rapidement que toute comparaison de chaîne de texte peut avoir lieu.

Rapide, efficace et fiable, cette approche peut facilement être adaptée à tout type de texte variable correspondant au texte de valeur statique connu. N'oubliez pas que les totaux de contrôle CRC32B sont générés par des opérations au niveau du bit et que l'instruction C switch utilise des opérations au niveau du bit optimisées au moment de la compilation. N’oubliez pas que c’est rapide, car CRC32B est la fonction hautement optimisée utilisée pour vérifier chaque paquet Ethernet reçu par votre Mac/iPhone/iPad ... même lorsque vous téléchargez des fichiers de plusieurs gigaoctets comme macOS Sierra.

0
William Cerniuk