caseはswitch-case文の他にも、enumのassociated valueを取り出す際にも使われます。
typealias Price = Int
enum Fruits {
case banana(Price)
case apple(Price)
}
let someFruit = Fruits.banana(100)
if case .banana(let bananaPrice) = someFruit {
print("banana is \(bananaPrice) yen")
}
ところが、case文はこの2通りの使い方だけでなく、以下のようにif &&
のような、かつ
を使う条件の場合にも使うことができます。
let akashi = (long: 135, lat: 34)
let greenwich = (long: 0, lat: 51)
func isAkashi(coordinates: (long: Int, lat: Int)) -> String {
// if coordinates.long == 135 && coordinates.lat == 34 {
if case (135, 34) = coordinates {
return "yes"
}
return "no"
}
print(isAkashi(coordinates: akashi)) // yes
print(isAkashi(coordinates: greenwich)) // no
ここでは、覚えておくと便利なcaseの使い方について、具体的な例を交えて紹介していきます。
if文の代わりに使う
ワイルドカードを含む場合
_
で置換すると、case
のパターンをワイルドカードにすることができます。
...
let equador = (long: -78, lat: 0)
...
func isUnderEquator(coordinates: (long: Int, lat: Int)) -> String {
// if coordinates.lat == 34 {
if case (_, 0) = coordinates {
return "yes"
}
return "no"
}
...
print(isUnderEquator(coordinates: equador)) // yes
print(isUnderEquator(coordinates: akashi)) // no
値が二つのみのtupleであればif
を使った場合と大差ありませんが、これが3要素以上になり、一致していて欲しい要素が2要素以上になると、一行で書けるこちらの書き方はかなり簡潔に書けると言えます。
特定の範囲内かを判定する場合
何か特定の値でなく、以上、未満のような範囲で判定したい場合、以下のような書き方ができます。
...
let seoul = (long: 127, lat: 37)
...
func distanceToJapan(coordinates: (long: Int, lat: Int)) -> String {
// range型で指定ができる。
let closeLongRange = 125...145
let closeLatRange = 30...40
let sameHemisphereLongRange = 0...90
let sameHemisphereLatRange = 0...180
switch coordinates {
case (135, 34):
return "at Japan"
// if coordinates.long >= 125 && coordinates.long <= 145 && coordinates.lat >= 30 && coordinates.lat <= 40 {
case (closeLongRange, closeLatRange):
return "close to Japan"
case (sameHemisphereLongRange, sameHemisphereLatRange):
return "the same side as Japan"
default:
return "very far from Japan"
}
}
...
print("distance to Japan: \(distanceToJapan(coordinates: akashi))") // distance to Japan: at Japan
print("distance to Japan: \(distanceToJapan(coordinates: greenwich))") // distance to Japan: the same side as Japan
print("distance to Japan: \(distanceToJapan(coordinates: seoul))") // distance to Japan: close to Japan
print("distance to Japan: \(distanceToJapan(coordinates: equador))") // distance to Japan: very far from Japan
ここで、複数のcase
にマッチングする場合、一番最初にマッチしたcaseがその値におけるcaseになる、という点に注意です(実質if-else ifのような書き方になる)。
余談
評価対象が一つの場合、以下のような書き方ができます。
func isNorthern(lat: Int) -> Bool {
let northernLange = 0...90
// if case northernRange = lat {
// return true
// }
// return false
// 両者の型が違うが、値がマッチする場合、`~=`が使える。
return northernLange ~= lat
}
両方の値を独立に評価したい場合
where
を使うと、片方のみの条件を詳細に評価していくことができます。
...
func locationDescription(coordinates: (long: Int, lat: Int)) -> String {
switch coordinates {
// if coordinates.lat > 0 {
case _ where coordinates.lat > 0:
return "northern hemisphere"
// else if coordinates.long > 0 {
case _ where coordinates.long > 0:
return "Oceania"
// else {
default:
return "Far from Japan, anyway"
}
}
...
print("place: \(locationDescription(coordinates: seoul))") // northern hemisphere
print("place: \(locationDescription(coordinates: equador))") // Far from Japan, anyway
ここで、先述の通り、二つ目のcase
は、一つ目の条件のelse if
になっている(つまり、coordinates.lat < 0
という条件が付加されている)ことに注意です。
このように、caseとtupleを併せることで複数の値をswitch-case
のような書き方で書くことができるのはSwiftの大きな特徴かと思います。
実用的なcase
の使い方
例えば、jsonが送られてきて、そこからInt型の値のみを抽出したい場合などは、as
と組み合わせて使うことにより、特定の型の値を抽出できます。
let importedJson = [
"name": "tarou",
"age": 24,
"hobby": "game"
] as [String: Any]
func extractAge(from json: [String: Any]) -> String {
var ageData = ""
for data in json.values {
switch data {
case let age as Int:
ageData = "\(age)"
default:
break
}
}
return ageData
}
print(extractAge(from: importedJson)) // 24
取得したjsonから、特定の型に合わせて値を取得したい場合は多々あると思うので、そうした場合にもcase
文が有用です。
まとめ
-
case
文には、switch-case
やassociated valueを取り出すif case(let xxx)
の使い方のほかに、複数の値に対するif
文のような使い方、特定の型の値を取り出す方法など様々な使い方がある。 - 両者の型が違うが値がマッチする場合、
~=
というオペレータが使える。
最後に
こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。