Frage

Ich mache einig Objective-C-Programmierung, die eine NSXMLDocument und bevölkert eine Objekt Eigenschaften aus dem Ergebnis beinhaltet das Parsen.

Die erste Version sah wie folgt aus:

if([elementName compare:@"companyName"] == 0) 
  [character setCorporationName:currentElementText]; 
else if([elementName compare:@"corporationID"] == 0) 
  [character setCorporationID:currentElementText]; 
else if([elementName compare:@"name"] == 0) 
  ...

Aber ich weiß nicht wie das if-else-if-else Muster erzeugt dies. Mit Blick auf die switch Aussage, die ich sehe, dass ich nur ints umgehen kann, chars etc und nicht Objekte ... so gibt es eine bessere Umsetzung Muster Ich bin mir nicht bewusst?

BTW habe ich komme eigentlich mit einer besseren Lösung sich für die Eigenschaften des Objekts einstellen, aber ich möchte in Objective-C speziell über die if-else vs switch Muster wissen,

War es hilfreich?

Lösung

Ich hoffe, Sie alle mich auf einem Glied zum Ausgehen hier vergeben werden, aber ich mag die allgemeinere Frage der Parsen von XML-Dokumenten in Cocoa adressieren, ohne die Notwendigkeit von if-else-Anweisungen. Die Frage, wie ursprünglich angegeben weist das aktuelle Element Text an eine Instanzvariable des Figurobjekts. Wie JMAH wies darauf hin, kann dieser Schlüssel-Wert-Codierung verwendet werden gelöst. in einem komplexeren XML-Dokument Dies ist jedoch nicht möglich sein könnte. Betrachten Sie zum Beispiel die folgende.

<xmlroot>
    <corporationID>
        <stockSymbol>EXAM</stockSymbol>
        <uuid>31337</uuid>
    </corporationID>
    <companyName>Example Inc.</companyName>
</xmlroot>

Es gibt mehrere Ansätze, dies zu tun. Aus der Spitze von meinem Kopf, kann ich mich zwei mit NSXMLDocument. Die erste verwendet NSXMLElement. Es ist recht einfach und beinhaltet nicht das if-else überhaupt Problem. Sie einfach das Wurzelelement erhalten und seine genannten Elemente nacheinander durchlaufen.

NSXMLElement* root = [xmlDocument rootElement];

// Assuming that we only have one of each element.
[character setCorperationName:[[[root elementsForName:@"companyName"] objectAtIndex:0] stringValue]];

NSXMLElement* corperationId = [root elementsForName:@"corporationID"];
[character setCorperationStockSymbol:[[[corperationId elementsForName:@"stockSymbol"] objectAtIndex:0] stringValue]];
[character setCorperationUUID:[[[corperationId elementsForName:@"uuid"] objectAtIndex:0] stringValue]];

Der nächste verwendet die allgemeinere NSXMLNode, Spaziergänge durch den Baum, und direkt verwendet die if-else-Struktur.

// The first line is the same as the last example, because NSXMLElement inherits from NSXMLNode
NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
    if([[aNode name] isEqualToString:@"companyName"]){
        [character setCorperationName:[aNode stringValue]];
    }else if([[aNode name] isEqualToString:@"corporationID"]){
        NSXMLNode* correctParent = aNode;
        while((aNode = [aNode nextNode]) == nil && [aNode parent != correctParent){
            if([[aNode name] isEqualToString:@"stockSymbol"]){
                [character setCorperationStockSymbol:[aNode stringValue]];
            }else if([[aNode name] isEqualToString:@"uuid"]){
                [character setCorperationUUID:[aNode stringValue]];
            }
        }
    }
}

Dies ist ein guter Kandidat für die if-else-Struktur zu beseitigen, aber wie das ursprüngliche Problem, wir können einfach nicht switch-case hier verwenden. Wir können jedoch nach wie vor durch die Verwendung perform if-else beseitigen. Der erste Schritt ist, die ein Verfahren für jedes Element zu definieren.

- (NSNode*)parse_companyName:(NSNode*)aNode
{
    [character setCorperationName:[aNode stringValue]];
    return aNode;
}

- (NSNode*)parse_corporationID:(NSNode*)aNode
{
    NSXMLNode* correctParent = aNode;
    while((aNode = [aNode nextNode]) == nil && [aNode parent != correctParent){
        [self invokeMethodForNode:aNode prefix:@"parse_corporationID_"];
    }
    return [aNode previousNode];
}

- (NSNode*)parse_corporationID_stockSymbol:(NSNode*)aNode
{
    [character setCorperationStockSymbol:[aNode stringValue]];
    return aNode;
}

- (NSNode*)parse_corporationID_uuid:(NSNode*)aNode
{
    [character setCorperationUUID:[aNode stringValue]];
    return aNode;
}

Die Magie geschieht im invokeMethodForNode: Präfix: Methode. Wir erzeugen, um den Wähler auf den Namen des Elements basiert, und durchzuführen, daß der Wähler mit Anode als einzigen Parameter. Presto bango, haben wir die Notwendigkeit für eine if-else-Anweisung eliminiert. Hier ist der Code für diese Methode.

- (NSNode*)invokeMethodForNode:(NSNode*)aNode prefix:(NSString*)aPrefix
{
    NSNode* ret = nil;
    NSString* methodName = [NSString stringWithFormat:@"%@%@:", prefix, [aNode name]];
    SEL selector = NSSelectorFromString(methodName);
    if([self respondsToSelector:selector])
        ret = [self performSelector:selector withObject:aNode];
    return ret;
}

Nun, statt unserer größeren if-else-Anweisung (die, die zwischen und corporationID differenziert company), können wir einfach eine Zeile Code schreiben

NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
    aNode = [self invokeMethodForNode:aNode prefix:@"parse_"];
}

Jetzt entschuldige ich mich, wenn ich irgendwelche dieser falsch verstanden, es ist eine Weile her, dass ich etwas mit NSXMLDocument geschrieben habe, ist es spät in der Nacht und ich habe tatsächlich diesen Code nicht testen. Also, wenn Sie etwas sehen falsch, lassen Sie einen Kommentar oder diese Antwort bearbeiten.

Aber ich glaube, ich habe gerade in Cocoa gezeigt, wie richtig genannten Selektoren verwendet werden, ganz wie diese if-else-Anweisungen in den Fällen zu beseitigen. Es gibt ein paar Fallstricke und Eckfällen. Die perform: Familie von Verfahren dauert nur 0, 1 oder 2 Methoden Argument, deren Argumente und Rückgabetypen sind Objekte, so dass, wenn die Typen der Argumente und Rückgabetyp sind keine Objekte, oder wenn es mehr als zwei Argumente sind, dann würden Sie haben eine NSInvocation zu verwenden, um es aufzurufen. Sie müssen sicherstellen, dass die Methodennamen generieren Sie werden nicht andere Methoden aufrufen, vor allem, wenn das Ziel des Anrufs eine weitere Aufgabe ist, und diese besondere Regelung Verfahren Namensgebung auf Elemente mit nicht-alphanumerischen Zeichen nicht funktionieren. Sie könnten um das bekommen, indem sie die XML-Elementnamen in Ihrer Methode Namen zu entkommen irgendwie oder durch eine NSDictionary Aufbau der Methodennamen als Schlüssel verwenden und die Wähler als Werte. Dies kann recht speicherintensiv bekommen und am Ende eine längere Zeit. perform Versand wie beschrieben ich ist ziemlich schnell. Für sehr große if-else-Anweisungen kann dieses Verfahren auch schneller als eine if-else-Anweisung.

Andere Tipps

Sie sollten auch die Vorteile von Schlüsselwert-Codierung übernehmen:

[character setValue:currentElementText forKey:elementName];

Wenn die Daten nicht vertrauenswürdig ist, könnten Sie überprüfen möchten, dass der Schlüssel gültig ist:

if (![validKeysCollection containsObject:elementName])
    // Exception or error

Wenn Sie wollen so wenig Code wie möglich verwenden, und Ihre Elementnamen und Setter alle genannt werden, so dass, wenn element ist @ „foo“ dann Setter ist setFoo :, Sie so etwas wie tun könnte:

SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", [elementName capitalizedString]]);

[character performSelector:selector withObject:currentElementText];

oder möglicherweise sogar:

[character setValue:currentElementText forKey:elementName]; // KVC-style

Obwohl diese wird natürlich ein wenig langsamer als eine Reihe von if-Anweisungen verwendet wird.

[Edit: Die zweite Option bereits von jemandem erwähnt wurde; oops!]

Darf ich vorschlagen, mit einem Makro?

#define TEST( _name, _method ) \
  if ([elementName isEqualToString:@ _name] ) \
    [character _method:currentElementText]; else
#define ENDTEST { /* empty */ }

TEST( "companyName",      setCorporationName )
TEST( "setCorporationID", setCorporationID   )
TEST( "name",             setName            )
:
:
ENDTEST

Ein Weg, ich dies mit NSStrings getan haben, ist durch eine NSDictionary und Aufzählungen verwenden. Es ist vielleicht nicht die eleganteste, aber ich denke, dass es den Code ein wenig besser lesbar macht. Der folgende Pseudo-Code wird extrahiert von einem meiner Projekte :

typedef enum { UNKNOWNRESIDUE, DEOXYADENINE, DEOXYCYTOSINE, DEOXYGUANINE, DEOXYTHYMINE } SLSResidueType;

static NSDictionary *pdbResidueLookupTable;
...

if (pdbResidueLookupTable == nil)
{
    pdbResidueLookupTable = [[NSDictionary alloc] initWithObjectsAndKeys:
                          [NSNumber numberWithInteger:DEOXYADENINE], @"DA", 
                          [NSNumber numberWithInteger:DEOXYCYTOSINE], @"DC",
                          [NSNumber numberWithInteger:DEOXYGUANINE], @"DG",
                          [NSNumber numberWithInteger:DEOXYTHYMINE], @"DT",
                          nil]; 
}

SLSResidueType residueIdentifier = [[pdbResidueLookupTable objectForKey:residueType] intValue];
switch (residueIdentifier)
{
    case DEOXYADENINE: do something; break;
    case DEOXYCYTOSINE: do something; break;
    case DEOXYGUANINE: do something; break;
    case DEOXYTHYMINE: do something; break;
}

Die if-else Implementierung Sie haben, ist der richtige Weg, dies zu tun, da switch nicht mit Objekten arbeiten. Außer vielleicht ein bisschen schwieriger zu lesen ist (was subjektiv ist), gibt es keinen wirklichen Nachteil bei der Verwendung if-else Aussagen auf diese Weise.

Auch wenn es nicht unbedingt ein besserer Weg, so etwas wie die für die einmaligen Gebrauch zu machen, warum verwenden Sie „vergleichen“, wenn Sie „isEqualToString“ verwenden können? Das scheint mehr performant zu sein, da der Vergleich bei dem ersten nicht-passenden Charakter stoppen würde, anstatt durch die ganze Sache gehe ein gültiges Vergleichsergebnis zu berechnen (wenn auch kommen, daran denken der Vergleich klar sein könnte, an der gleichen Stelle) -. auch wenn es ein wenig sauberer aussehen würde, weil dieser Anruf ein BOOL zurückgibt

if([elementName isEqualToString:@"companyName"] ) 
  [character setCorporationName:currentElementText]; 
else if([elementName isEqualToString:@"corporationID"] ) 
  [character setCorporationID:currentElementText]; 
else if([elementName isEqualToString:@"name"] ) 

Es ist eigentlich eine ziemlich einfache Art und Weise mit Cascading umgeht if-else-Anweisungen in einer Sprache wie Objective-C. Ja, können Sie Subclassing und überwiegendes, die Schaffung einer Gruppe von Unterklassen verwenden, die die gleiche Methode unterschiedlich implementieren, Berufung auf die korrekte Implementierung zur Laufzeit eine gemeinsame Nachricht mit. Das funktioniert gut, wenn Sie eine der wenige Implementierungen wählen wollen, aber es kann in einer unnötigen Vielzahl von Unterklassen führen, wenn Sie viele kleine, leicht unterschiedliche Implementierungen haben, wie Sie in langen if-else zu neigen oder Aussagen wechseln.

Stattdessen ausklammern den Körper von jeder if / else-if-Klausel in seine eigene Methode, die alle in der gleichen Klasse. Nennen Sie die Nachrichten, die sie in ähnlicher Weise aufrufen. Erstellen Sie nun eine NSArray die Wähler dieser Nachrichten, die (unter Verwendung @selector ()). Coerce die Zeichenfolge Sie testeten in den conditionals in einen Selektor NSSelectorFromString mit () (Sie können zusätzliche Wörter oder Doppelpunkte es verketten müssen zunächst je nachdem, wie Sie diese Nachrichten genannt, und ob sie nehmen Argumente). Jetzt haben sich die Wähler mit performdurchführen.

Dieser Ansatz hat den Nachteil, dass sie die Klasse mit vielen neuen Nachrichten Krempel-up, aber es ist wahrscheinlich besser, eine einzige Klasse als die gesamte Klassenhierarchie mit neuen Subklassen Krempel-up.

Posting diese als Antwort auf Wevah Antwort oben - ich bearbeitet haben würde, aber ich habe nicht hoch genug Ruf noch:

leider die erste Methode bricht für Felder mit mehr als ein Wort in ihnen - wie xPosition. capitalizedString wird das zu Xposition umwandeln, die, wenn sie mit dem Format kombiniert, um Ihnen geben setXposition. Auf jeden Fall nicht, was hier wollte. Hier ist, was ich in meinem Code bin mit:

NSString *capName = [elementName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[elementName substringToIndex:1] uppercaseString]];
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", capName]);

Nicht so schön wie die erste Methode, aber es funktioniert.

Ich habe mit einer Lösung zu kommen, die Blöcke verwendet einen Schalter artige Struktur für Objekte zu erstellen. Es geht es:

BOOL switch_object(id aObject, ...)
{
    va_list args;
    va_start(args, aObject);

    id value = nil;
    BOOL matchFound = NO;

    while ( (value = va_arg(args,id)) )
    {
        void (^block)(void) = va_arg(args,id);
        if ( [aObject isEqual:value] )
        {
            block();
            matchFound = YES;
            break;
        }
    }

    va_end(args);
    return matchFound;
}

Wie Sie sehen können, ist dies eine oldschool C-Funktion mit variabler Argumentliste. Ich gehe das Objekt in dem ersten Argumente geprüft werden, die von den case_value-case_block Paaren gefolgt. (Es sei daran erinnert, dass die Objective-C-Blöcke sind nur Objekte). Die while Schleife hält diese Paare Extrahieren, bis der Objektwert übereinstimmt oder es sind keine Fälle links (siehe Anmerkungen unten).

Verbrauch:

NSString* str = @"stuff";
switch_object(str,
              @"blah", ^{
                  NSLog(@"blah");
              },
              @"foobar", ^{
                  NSLog(@"foobar");
              },
              @"stuff", ^{
                  NSLog(@"stuff");
              },
              @"poing", ^{
                  NSLog(@"poing");
              },
              nil);   // <-- sentinel

// will print "stuff"

Weitere Informationen:

  • das ist eine erste Näherung ohne Fehlerprüfung
  • die Tatsache, dass die Sachbearbeiter Blöcke sind, erfordert zusätzliche Pflege, wenn es um die Sichtbarkeit, den Umfang und die Speicherverwaltung von Variablen aus
  • verwiesen kommt
  • , wenn Sie die Sentinel vergessen, Sie sind zum Scheitern verurteilt: P
  • Sie können den boolean Rückgabewert verwenden, um eine „default“ Fall auszulösen, wenn keiner der Fälle angepasst wurden

Das häufigste Refactoring vorgeschlagen zur Beseitigung von if-else oder switch-Anweisungen ist die Einführung Polymorphismus (siehe http : //www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html ). Die Beseitigung solcher conditionals ist am wichtigsten, wenn sie dupliziert werden. Im Fall von XML-Analyse wie Ihre Probe Sie sind im Wesentlichen die Daten in eine natürlichere Struktur zu bewegen, so dass Sie nicht an anderer Stelle die bedingte duplizieren müssen. In diesem Fall wird die if-else oder switch-Anweisung ist wahrscheinlich gut genug.

In diesem Fall bin ich nicht sicher, ob Sie bequem die Klasse Polymorphismus einzuführen Refactoring kann als Bradley schlägt vor, da es sich um eine Cocoa-native Klasse ist. Stattdessen ist der Objective-C-Weg, es zu tun, um eine Klassenkategorie zu verwenden, um eine elementNameCode Methode NSSting hinzuzufügen:

   typedef enum { 
       companyName = 0,
       companyID,  
       ...,
       Unknown
    } ElementCode;

    @interface NSString (ElementNameCodeAdditions)
    - (ElementCode)elementNameCode; 
    @end

    @implementation NSString (ElementNameCodeAdditions)
    - (ElementCode)elementNameCode {
        if([self compare:@"companyName"]==0) {
            return companyName;
        } else if([self compare:@"companyID"]==0) {
            return companyID;
        } ... {

        }

        return Unknown;
    }
    @end

In Ihrem Code, können Sie jetzt einen Schalter auf [elementName elementNameCode] verwenden (und gewinnen die zugehörigen Compiler-Warnungen, wenn Sie vergessen, etc für eine der Enum-Mitglieder zu testen.).

Wie Bradley darauf hinweist, kann dies nicht wert sein, wenn die Logik nur an einem Ort verwendet wird.

Was wir in unseren Projekten gemacht, wo wir so diese Art der Sache zu müssen immer und immer wieder, ist eine statische CFDictionary Zuordnung der Strings / Objekte einzurichten, gilt das auf einen einfachen Integer-Wert. Es führt zu Code, der wie folgt aussieht:

static CFDictionaryRef  map = NULL;
int count = 3;
const void *keys[count] = { @"key1", @"key2", @"key3" };
const void *values[count] = { (uintptr_t)1, (uintptr_t)2, (uintptr_t)3 };

if (map == NULL)
    map = CFDictionaryCreate(NULL,keys,values,count,&kCFTypeDictionaryKeyCallBacks,NULL);


switch((uintptr_t)CFDictionaryGetValue(map,[node name]))
{
    case 1:
        // do something
        break;
    case 2:
        // do something else
        break;
    case 3:
        // this other thing too
        break;
}

Wenn Sie Leopard Targeting sind nur, Sie könnten einen NSMapTable anstelle eines CFDictionary verwenden.

ähnlich Lvsti I Blöcke sind mit einem Schaltmuster auf Objekte auszuführen.

Ich habe eine sehr einfache Filterblock basiert Kette schrieb, dass n Filterblöcke und führt jeden Filter auf das Objekt erfolgt.
Jeder Filter kann das Objekt ändern, sondern muss es zurück. Egal was passiert.

NSObject + Functional.h

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

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

NSObject + Functional.m

@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

Jetzt können wir n FilterBlocks einrichten für die verschiedenen Fälle zu testen.

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

FilterBlock caseNO  = ^id(id element, NSUInteger idx, BOOL *breakAfter){ 
    if ([element isEqualToString:@"NO"] ) { 
        NSLog(@"Nope");
        *breakAfter = YES;
    }
    return element;
};

Jetzt halten wir diese blockieren wollen wir als Filterkette in einem Array testen:

NSArray *filters = @[caseYES, caseNO];

und es auf einem Objekt durchführen kann

id obj1 = @"YES";
id obj2 = @"NO";
[obj1 processByPerformingFilterBlocks:filters];
[obj2 processByPerformingFilterBlocks:filters];

Dieser Ansatz kann zum Schalten, sondern auch für jede (bedingte) Filterkette Anwendung verwendet werden, da die Blöcke, die das Element bearbeiten und weitergeben.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top