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 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)に移行します。
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の思想を見て取れますね!
以上、最後までご覧いただきありがとうございました。