web-dev-qa-db-ja.com

switchステートメントでNSStringを使用する

NSStringステートメントでswitchを使用することはできますか?

または、単にif/else if

52
Thomas Clayson

switchステートメントはその場合に整数定数を必要とするため、NSStringをここで使用することはできないため、if/elseオプションを選択する必要があるようです。

もう1つのポイントは、isEqualToString:またはcompare:メソッドを使用してNSStringsを比較する必要があるため、スイッチケースにポインター値が許可されていても使用できない

51
Vladimir

これらのマクロをアプリで使用します。

#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

これに対応し、@Cœurの回答をサポートします。これは同じものですが、Xcode 4.4+/clang /Whateverで記述されています。 「リテラル構文」は単純なif, else比較にさらに近い(それがポイントです、そうではありません。..)

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

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

BlockA!

または、ボタンのタイトルに基づいてセレクターを実行する場合...

- (IBAction) multiButtonTarget:button { 

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

Quit! _ exit -1

w.string = kIvar == 0 ? @"StringA" : @"StringB";のような簡潔な、はるかに便利な、恐ろしい(そして限定的で複雑な)@selectorのことを考えずにブロックを押し込むことができるので!

編集:これはより明確にそのように構築されています:

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

認めざるを得ない、私は恥ずかしいほど[〜#〜] into [〜#〜]この種の統治的な愚かさ。もう1つのオプションは、文字列が何であるかを忘れることです。

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

または、UIのWordをそのまま使用します。

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

SwitchステートメントはNSStringでは機能しません。intでのみ機能します。

If/Elseステートメントはコードが多すぎるため、多くの場合最適ではありません。

最適なソリューションは、NSString(または他のオブジェクト)の可能性によってインデックス付けされたNSDictionaryを使用することです。次に、適切な値/機能に直接アクセスします。

例1、@ "A"または@ "B"をテストし、methodAまたはmethodBを実行する場合:

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

例2、@ "A"または@ "B"をテストし、blockAまたはblockBを実行する場合:

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

アレックスグレーに触発され、チェーンメソッドをオブジェクトに適用するカテゴリメソッドを作成しました。

.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

次のように使用できます

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

しかし、aplied chianed計算のような、より複雑なこともできます。

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

私はパーティーに少し遅れていることを知っていますが、ここにObjective-Cスイッチステートメントの提出があります。少し複雑なので、いマクロに耐えてください。

機能:

  • looksswitchステートメントのように
  • スレッドの原子性が組み込まれています
  • NSStringだけでなく、すべてのObjective-Cオブジェクトで機能します(-isEqual:セレクターを使用)
  • structs(==演算子がないため)を除いて、Cタイプでも動作するように拡張できます。
  • Switchステートメントごとに評価できるcaseラベルは1つだけです(breakは不要です)
  • ケースが選択されていない場合、無限ループの可能性はありません(breakは不要です)
  • 1つの変数名____dontuse_switch_varのみを予約します(その他はすべて静的スコープ内にあり、ローカルスコープで上書きできます)
  • 奇妙な保持問題は発生しません(__weak参照を使用)

欠点:

  • 非常にソースを理解するのが難しい
  • すべてのcaseステートメントは、選択される前に評価されます(したがって、最も頻繁に使用されるステートメントを先頭に配置します)
  • スレッドアトミック性はパフォーマンスを犠牲にします。ロックを必要としませんが、NSThreadを非常に広範囲に使用します。
  • ブラケット{または}を使用しないと、Xcodeはステートメントを適切にフォーマットすることを好みません(これは暗黙的なgotoラベルが原因です。
  • ヘッダーだけではありません(NSValue weak refの場合、1つの.mファイルが必要です)

例:

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

さらに苦労せずに、(ugい)コードを次に示します。

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

これはまったく自明であるため、このドキュメントは提供しません。本当に理解しにくい場合は、コメントを残し、コードを文書化します。

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

他のみんなが述べたように、おそらくif/elseを使用するのが最も簡単ですが、canはswitchステートメントによく似たものを作成します。私はGitHubで WSLObjectSwitch とまったく同じプロジェクトを作成しました。これはかなり単純な実装であり、ハッシュなどを使用して最適化しませんが、機能します。

1

これは通常、enumのようなものを使用する場所です。その数の値を管理する必要がある場合は、他の方法で渡した文字列と同じ名前の列挙型を作成し、そこに渡します。次に例を示します。

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

タグを使用して異なるアクションのボタンを簡単に切り替えることができます。

例:

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