【Swift】条件分岐文について〜switch〜

#switch文 とは

switch文とは、**パターンを利用して制御式の値に応じて実行文を変える制御構文**になります。

switch文の各パターンはcaseキーワードで定義し、
どのパターンともマッチしなかった場合の処理はdafaultキーワードで定義します。

```swift

switch 制御式 {
case パターン１:
   制御式がパターン１にマッチした時に実行される箇所
case パターン２：
   制御式がパターン２にマッチした時に実行される箇所
default:
   制御式がいずれのパターンにもマッチしなかった場合に実行される箇所
}
```

switch文では、制御式がパターンとマッチするか上から順に確認していき、
マッチしたパターンの実行文が読み込まれます。

swiftにおけるswitch文は一度実行するとマッチを終了し、
**それ以降のパターンは全て無視します。**

つまり、複数のパターンにマッチする制御式でも、
先頭のパターン以外は意味をなしません。

is文やguard文は成立するか否かの２ケースへの分岐でしたが、
switch文はさらに多くの分岐ができます。

つまり、switch文は複数のケースを持つ条件分岐に向いています。

また、if文やguard文がBool型の値しか指定できないのに対し、
**switch文はどんな型でも指定できます。**

Int型の定数aの値が、負・0・正の場合のいずれかによって３つに分岐しています。

```swift

let a = 10

switch a {
case Int.min..<0:
    print("aは負の値です。")
case 1..<Int.max:
    print("aは正の値です。")
default:
    print("aは0です")
}

実行結果
aは正の値です。
```


###ケースの網羅性
swiftのswitch文ではコンパイラによってケースの網羅性のチェックが行われ、
網羅されていない場合はコンパイルエラーになります。

siwtch文を網羅的にさせるには、
制御式が取り得る全ての値をいずれかのケースにマッチさせる必要があります。

`.abc`、`.def`、`.ghi`の3つのケースを持つSomeEnum型の値を返す制御式を例にします。
(列挙型のenumについては別の記事で詳しく説明します。)

```swift

enum SomeEnum {
   case abc
   case def
   case ghi
}

let a = SomeEnum.abc

switch a {
case .abc:
    print("abc")
case .def:
    print("def")
case .ghi:
    print("ghi")
}

実行結果
abc
```

制御式aはSomeEnum型なので、取り得る値は`.abc`、`.def`、`.ghi`です。

先ほどのswitch文では、いずれの値に対してもケースが記述されているため、
網羅的となっています。

先ほどの内容から`case .ghi:`を削除してしまうとコンパイルエラーになります。

```swift


enum SomeEnum {
   case abc
   case def
   case ghi
}

let a = SomeEnum.abc

switch a {
case .abc:
    print("abc")
case .def:
    print("def")
}
// .ghiのケースが想定されていないため網羅的ではなくコンパイルエラー
```

switch文の網羅性を満たすためには、
**制御式の型が取り得る全ての値をケースで記述する必要があります。**

Bool型の場合にはtrueとfalseの両方を記述する必要があります。

```swift


let a = true

switch a {
case true:
   print("true")
case false:
   print("false")
}

実行結果
true
```

制御式が取り得る値がわからない場合や、
ケースを分ける必要がない場合などはdefaultを使用することも可能です。
**defaultは網羅性を保証する役割を担っております。**

先ほどのSomeEnum型を例にすると下記のようになります。

```swift


enum SomeEnum {
   case abc
   case def
   case ghi
}

let a = SomeEnum.ghi

switch a {
case .abc:
    print("abc")
case .def:
    print("def")
default:
    print("Default")
}

実行結果
Default
```

ただし、列挙型の制御式に対して
デフォルトケースを用いることは**極力避けた方がいい**らしいです。
というのもきちんと理由があります。

switch文がdefaultケースを持っている場合、
列挙型に新しいケースが追加されたとしても自動的にdefaultケースにマッチします。

この場合、網羅性には問題がないためコンパイルエラーにはなりません。

一見良いことのように思えますが、
新たに追加されたケースは至る所でdefaultケースとして扱われます。

**つまり、意図していない動作になる確率が大幅に上がります。**

defaultケースの使用は、楽ですが変更に弱いプログラムを招きます。


defaultケースを使っていない場合は、
変更後は網羅的ではなくなるためコンパイルエラーが発生します。
結果として**追加されたケースに対しての実装をどこに追加すべきか分かります。**

###whereキーワード
whereキーワードは、ケースにマッチする条件を追加する機能を持っています。

```swift


switch 制御式 {
case パターン where 条件式:
   制御式のパターンにマッチし、かつ、条件式を満たす時に実行
default:
   制御式がいずれのパターンにもマッチしなかった場合に実行
}
```

パターンがマッチしても条件式を満たしていなかった場合は、
そのケースの内容を実行することはできません。

```swift


let a: Int? = 1

switch a {
case .some(let a) where a > 10:
    print("10よりも大きい値\(a)が存在します。")
default:
    print("値が存在しない、もしくは10以下です。")
}

実行結果
値が存在しない、もしくは10以下です。
```
`case .some(let a)`の部分はマッチしています。
ですが、`where a > 10`で条件を満たしていないのでdefaultケースが実行されます。

`.some(let a)`についてですが、appleから標準で提供されている機能です。
値が入っている時にマッチするケースだと認識しています・・・。

```swift

一部抜粋

@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {

    /// The absence of a value.
    ///
    /// In code, the absence of a value is typically written using the `nil`
    /// literal rather than the explicit `.none` enumeration case.
    case none

    /// The presence of a value, stored as `Wrapped`.
    case some(Wrapped)

    /// Creates an instance that stores the given value.
```

###break文
break文は、switch文のケースの実行を中断する文になります。
**breakキーワードのみか、breakキーワードと後述するラベルの組み合わせで構成されます。**

break文を使用した時点で処理が中断されるため、
下記の場合は１つ目のprint()関数しか実行されません。

```swift

let a = 1

switch a {
case 1:
   print("実行：1回目")
   break
   print("実行：2回目")
default:
   break
}

実行結果
実行：1回目
```

**ケース内には少なくとも一つの文が必要**なため、
defaultケースを使用しているものの特に処理を行う必要がない場合などに
defaultケース内にbreak文を記述したりする場合もあります。

###ラベル
ラベルは、break文の制御対象を指定するための仕組みになります。

どの様な時に使うのかというと、
switch文が入れ子(ネスト)構造になっている場合などの、
**break文の対象となるswitch文を明示する必要があるケースで使用します。**

無理やり作ったコードですが・・・。
0~10の間の値だと、１つ目のケースの中に入り、それ以外だと測定不可能と表示される処理です。
また、0~10の間で、奇数の場合は「値は奇数です」と表示され、
値が偶数の場合は「値は偶数です」と表示されます。

なお、値が0の場合は「0はダメです」と表示されます。(無理やり)

```swift

let a = 0

outer: switch a {
case 0...10:
    let string: String
    inner: switch a {
    case 1, 3, 5, 7, 9:
        string = "奇数"
    case 2, 4, 6, 8, 10:
        string = "偶数"
    default:
        print("0はダメです。")
        break outer
    }
    print("値は\(string)です。")
default:
    print("10より大きい数字なので測定不能")
}

実行結果
0はダメです。
```

今回は定数aに0を代入しています。
`outer: switch a`や`inner: switch a`は、switch文にラベル名をつけています。
`break outer`でどこのswitch文をbreakするかを指定しています。

今回は外側のswitch文を抜けるようにしていますが、
`break inner`に変えれば内側のswitch文を抜けて
`print("値は\(string)です。")`を実行します。

ただ`break inner`にしてしまうと、
`let string: String`が初期化されておらず、
中に何も値がない状態なのでコンパイルエラーになります。

break outerの場合にコンパイルエラーが起きないのは、
外側のswitch文が終了するため`print("値は\(string)です。")`が実行されないからです。


###fallthrough文

fallthrough文は、switch文のケースの実行を終了し、
次のケースを実行させる制御構文になります。

`case 1`内にfallthroughを記述すると、
fallthrough以降の処理は行わず一つしたのケース(case 2)に移行します。

```swift

let a = 1

switch a {
case 1:
    print("case 1")
    fallthrough
    print("case 1-2")
case 2:
    print("case 2")
default:
    print("defaule")
}

実行結果
case 1
case 2
```

JavaやC言語などでは、
ケースを実行した後に次のケースに移行するのはデフォルトの挙動になります。

しかし、swiftではfallthrough文によって明示しない限り次のケースに移行することはありません。

暗黙的な使用をできる限り排除しようというswiftの思想を見て取れますね！

以上、最後までご覧いただきありがとうございました。