0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-07

switch文 とは

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

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


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

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

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

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

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

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

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

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


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については別の記事で詳しく説明します。)


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:を削除してしまうとコンパイルエラーになります。



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の両方を記述する必要があります。



let a = true

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

実行結果
true

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

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



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キーワードは、ケースにマッチする条件を追加する機能を持っています。



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

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



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から標準で提供されている機能です。
値が入っている時にマッチするケースだと認識しています・・・。


一部抜粋

@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文を使用した時点で処理が中断されるため、
下記の場合は1つ目のprint()関数しか実行されません。


let a = 1

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

実行結果
実行1回目

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

ラベル

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

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

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

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


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 ainner: 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)に移行します。


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の思想を見て取れますね!

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?