2008-09-19 6 views
14

Я занимаюсь программированием Objective-C, который включает в себя синтаксический анализ NSXmlDocument и заполнение свойств объектов из результата.Переключатель Objective-C с использованием объектов?

Первая версия выглядела так:

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

Но я не люблю if-else-if-else шаблон это производит. Глядя на инструкцию switch, я вижу, что я могу обрабатывать только ints, chars и т. Д., А не объекты ... так что есть лучший образец реализации, о котором я не знаю?

КСТАТИ Я на самом деле придумал лучшим решением для установки свойств объекта, но я хочу, чтобы конкретно знать о if - else против switch модели в Objective-C

+0

Это вообще не помогает, но в VB.Net вы можете использовать операторы switch (select in vb) для любого типа значения, а компилятор будет выполнять преобразование в шаблон if-elseif-else при компиляции , если это невозможно сделать с помощью прямого скачка. Лично я считаю, что все языки должны иметь эту функцию. – Kibbee 2008-11-10 14:04:24

+0

Связанный ответ: http://stackoverflow.com/questions/8161737/can-objective-c-switch-on-nsstring/10177956#10177956 – 2013-01-03 16:22:49

ответ

12

Я надеюсь, что вы все простите меня за выход на конечности здесь, но я хотел бы затронуть более общий вопрос о разборе XML документов в какао без необходимости операторов if-else. Вопрос, как изначально указано, присваивает текущему элементу текст переменной экземпляра объекта символа. Как отметил jmah, это можно решить, используя кодирование с ключом. Однако в более сложном документе XML это может оказаться невозможным. Рассмотрим, например, следующее.

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

Существует несколько подходов к решению этой проблемы. В верхней части головы я могу думать о двух, используя NSXMLDocument. Первый использует NSXMLElement. Это довольно просто и не затрагивает проблему if-else. Вы просто получаете корневой элемент и один за другим просматриваете его именованные элементы.

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

Следующий использует более общий NSXMLNode, прогулки по дереву, так и непосредственно использует если-то структуру.

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

Это является хорошим кандидатом для устранения если-иначе структура, но, как и исходной задачи, мы не можем просто использовать переключатель-случай здесь. Однако мы все же можем устранить if-else с помощью функции performSelector. Первый шаг - определить метод для каждого элемента.

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

Магия происходит в методе invokeMethodForNode: prefix:. Мы создаем селектор, основанный на имени элемента, и выполняем этот селектор с помощью aNode в качестве единственного параметра. Presto bango, мы устранили необходимость в выражении if-else. Вот код для этого метода.

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

Теперь, вместо того, чтобы наш больше, если-иначе заявления (тот, что различие между COMPANYNAME и corporationID), мы можем просто написать одну строку кода

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

Теперь я прошу прощения, если я получил какой-либо из этого неправильно, прошло некоторое время с тех пор, как я написал что-нибудь с NSXMLDocument, поздно ночью, и я действительно не тестировал этот код. Поэтому, если вы видите что-то не так, оставьте комментарий или отредактируйте этот ответ.

Однако, я считаю, что я только что показал, как правильно названные селектора могут использоваться в Cocoa для полного устранения операторов if-else в таких случаях. Есть несколько чешских и угловых случаев. Семейство методов performSelector: принимает только 0, 1 или 2 аргумента, аргументы и типы возвращаемых объектов - это объекты, поэтому, если типы аргументов и тип возвращаемого значения не являются объектами или если существует более двух аргументов, должны использовать NSInvocation для его вызова. Вы должны убедиться, что имена методов, которые вы генерируете, не будут вызывать другие методы, особенно если целью вызова является другой объект, и эта схема именования конкретного метода не будет работать с элементами с не буквенно-цифровыми символами. Вы можете обойти это, как-то избегая имен элементов XML в именах методов или создавая NSDictionary, используя имена методов в качестве ключей и селекторов в качестве значений. Это может привести к интенсивной памяти и в конечном итоге увеличить время. performSelector отправка, как описано выше, довольно быстро. Для очень больших операторов if-else этот метод может быть даже быстрее, чем оператор if-else.

+0

Мне нравится компактность этого решения. Всего несколько замечаний: NSXMLNode не NSNode. NSXMLNode * ret - я не могу придумать для этого никакого использования, поскольку aNode уже задано nextNode. Использование функции performSelector, как это, даст предупреждение: «performSelector может вызвать утечку, потому что его селектор неизвестен». Самый короткий путь для этого - использовать objc_msgSend, но затем вы должны отключить «Включить строгую проверку вызовов objc_msgSend». Сделайте это в [self responsesToSelector: selector], чтобы облегчить некоторые из ошибок objc_msgSend. – 2016-06-06 17:18:54

+0

Эй, спасибо за комментарий. Этот ответ является вики-сообществом, поэтому, пожалуйста, не стесняйтесь редактировать любые ошибки, которые могут возникнуть. Однако я хотел бы отметить, что этот ответ был скорее упражнением в изучении реализации с произвольным ограничением, а не предназначенным для консультирования о том, как на самом деле писать производственный код. – 2016-06-27 15:59:17

2

Наиболее распространенный рефакторинг предложил для устранения если -sese или switch вводит полиморфизм (см. http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html). Устранение таких условностей является наиболее важным, когда они дублируются. В случае синтаксического анализа XML, как и ваш образец, вы по существу перемещаете данные в более естественную структуру, чтобы вам не пришлось дублировать условие в другом месте. В этом случае оператор if-else или switch, вероятно, достаточно хорош.

3

Реализация if-else у вас есть правильный способ сделать это, так как switch не будет работать с объектами. Помимо того, что, возможно, немного сложнее читать (что является субъективным), нет реального недостатка в использовании заявлений if-else.

1

В этом случае я не уверен, что вы можете легко реорганизовать класс для введения полиморфизма, как предлагает Брэдли, поскольку он является родным классом Cocoa. Вместо этого, Objective-C способ сделать это состоит в использовании категории класса, чтобы добавить elementNameCode метод NSSting:

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 

В вашем коде, вы можете теперь использовать переключатель на [elementName elementNameCode] (и получить связанные предупреждения компилятора если вы забыли проверить один из членов перечисления и т. д.).

Как указывает Брэдли, это может не стоить того, если логика используется только в одном месте.

3

Хотя не обязательно лучший способ сделать что-то подобное для использования в одно время, зачем использовать «сравнивать», когда вы можете использовать «isEqualToString»? Казалось бы, это было бы более результативным, поскольку сравнение остановилось бы при первом несогласованном символе, вместо того чтобы проходить через все это, чтобы вычислить достоверный результат сравнения (хотя подумать, что сравнение может быть ясно в той же точке) - также, хотя это выглядело бы немного чище, потому что этот вызов возвращает BOOL.

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

Как было указано в исходном вопросе, я уже нашел лучший способ сделать настройку свойств , Я просто искал советы по любому лучшему шаблону для работы с шаблоном if-else. – craigb 2008-09-21 19:10:03

+0

Nice downvote. Это по-прежнему полезная информация для людей, которые предпочитают более чистые способы сравнения строк. Вы злоупотребляете downvotes, единственным утешением является то, что вам стоит репутация, чтобы отбросить отличную информацию. Я не ошибусь, помогая тебе снова. – 2008-09-21 20:44:28

13

Вы должны воспользоваться Coding Key-Value:

[character setValue:currentElementText forKey:elementName]; 

Если данные сомнителен, вы можете проверить, что ключ действителен:

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

Как уже говорилось, я уже нашел лучший способ настройки этих свойств и просто искал совет по шаблону if-else, который мне не понравился. – craigb 2008-09-21 19:11:18

+0

Но это то, что нужно сделать, чтобы избежать многократной отправки, и позволить аспектам языка (или фреймворка) обрабатывать его.Следует избегать шаблона if-else-else-else или switch на объекте, когда вы можете делать такие вещи, как поиск словарей. – jmah 2008-09-22 03:23:15

4

Один из способов Я сделал это с помощью NSStrings, используя NSDictionary и перечисления. Возможно, он не самый элегантный, но я думаю, что он делает код более читаемым.Следующий псевдокод извлекается из one of my projects:

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

Dare Я предлагаю использовать макрос?

#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 
7

Если вы хотите использовать в качестве небольшой код, как это возможно, и ваши имена элементов и сеттеры все названные так, что если ELEMENTNAME это @ «Foo», то сеттер setFoo :, вы могли бы сделать что-то вроде:

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

[character performSelector:selector withObject:currentElementText]; 

или, возможно, даже:

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

Хотя это, конечно, будет немного медленнее, чем кучу если заявления.

[Изменить: второй вариант уже упоминался кем-то; oops!]

3

На самом деле существует довольно простой способ справиться с каскадированием инструкций if-else на языке Objective-C. Да, вы можете использовать подклассирование и переопределение, создавая группу подклассов, которые реализуют один и тот же метод по-разному, вызывая правильную реализацию во время выполнения с использованием общего сообщения. Это хорошо работает, если вы хотите выбрать одну из нескольких реализаций, но это может привести к ненужному распространению подклассов, если у вас много небольших, немного разных реализаций, как вы, как правило, в операторах long if-else или switch.

Вместо этого вычтите тело каждого предложения if/else-if в свой собственный метод, все в одном классе. Назовите сообщения, вызывающие их аналогичным образом. Теперь создайте NSArray, содержащий селектор этих сообщений (полученный с помощью @selector()). Поверните строку, которую вы тестировали в условных выражениях, в селектор, используя NSSelectorFromString() (вам может понадобиться сначала связать с ним дополнительные слова или двоеточия в зависимости от того, как вы назвали эти сообщения, и не принимают ли они аргументы). Теперь вы можете выполнить селектор с помощью функции performSelector :.

Этот подход имеет недостаток, что он может загромождать класс множеством новых сообщений, но, вероятно, лучше загромождать один класс, чем вся иерархия классов с новыми подклассами.

1

То, что мы сделали в наших проектах, где нам нужно так много раз, заключается в создании статического CFDictionary, сопоставляющего строки/объекты для проверки против простого целочисленного значения. Это приводит к коду, который выглядит следующим образом:

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

Если вы ориентируетесь только Leopard, вы могли бы использовать NSMapTable вместо CFDictionary.

3

Проводка это как ответ на ответ Wevah в выше - я бы отредактирован, но у меня нет достаточно высокую репутацию еще:

, к сожалению, первые разрывы методов для полей с более чем одним словом в них - как xPosition. capitalizedString преобразует это в Xposition, которое в сочетании с форматом дает setXposition:. Определенно не то, что здесь нужно. Вот что я использую в своем коде:

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

Не так красиво, как первый метод, но он работает.

3

У меня появилось решение, которое использует блоки для создания подобной коммутатору структуры для объектов. Там он идет:

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

Как вы можете видеть, это функция oldschool C с переменным списком аргументов. Я передаю объект для тестирования в первом аргументе, за которым следуют пары case_value-case_block.(Напомним, что блоки Objective-C - это просто объекты.) Цикл while продолжает извлекать эти пары до тех пор, пока не будет сопоставлено значение объекта или нет случаев (см. Примечания ниже).

Использование:

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

// will print "stuff" 

Примечание:

  • это первое приближение без проверки ошибок
  • того факта, что обработчики случае являются блоками, требует дополнительного ухода, когда речь идет о видимости , объем и управление памятью переменных, на которые ссылаются в пределах
  • , если вы забудете дозорного, вы обречены: P
  • вы можете использовать логическое значение, возвращаемое для запуска «по умолчанию» случай, когда ни один из случаев не было подкреплено
0

Подобно Lvsti я использую блоки для выполнения конфигурации переключения на объекты.

Я написал очень простую цепочку на основе блока фильтров, которая принимает n блоков фильтров и выполняет каждый фильтр на объекте.
Каждый фильтр может изменять объект, но должен возвращать его. Не важно что.

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 

Теперь мы можем установить n FilterBlocks для проверки различных случаев.

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

Теперь мы вставляем эти блоки мы хотим проверить в качестве фильтрующей цепи в массиве:

NSArray *filters = @[caseYES, caseNO]; 

и можем выполнять его на объекте

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

Этот подход может быть использован для переключения, но также и для любого (условного) применения цепочки фильтров, поскольку блоки могут редактировать элемент и передавать его.