【Swift】caseを使いこなして可読性を上げよう

caseはswitch-case文の他にも、enumのassociated valueを取り出す際にも使われます。
```swift
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 &&`のような、`かつ`を使う条件の場合にも使うことができます。
```swift
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`のパターンをワイルドカードにすることができます。
```swift
...
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要素以上になると、一行で書けるこちらの書き方はかなり簡潔に書けると言えます。
### 特定の範囲内かを判定する場合
何か特定の値でなく、以上、未満のような範囲で判定したい場合、以下のような書き方ができます。
```swift
...
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のような書き方になる)。
#### 余談
評価対象が一つの場合、以下のような書き方ができます。
```swift
func isNorthern(lat: Int) -> Bool {
    let northernLange = 0...90
    // if case northernRange = lat {
    //   return true
    // }
    // return false
    // 両者の型が違うが、値がマッチする場合、`~=`が使える。
    return northernLange ~= lat
}
```

### 両方の値を独立に評価したい場合
`where`を使うと、片方のみの条件を詳細に評価していくことができます。
```swift
...
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`と組み合わせて使うことにより、特定の型の値を抽出できます。
```swift
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`文のような使い方、特定の型の値を取り出す方法など様々な使い方がある。
- 両者の型が違うが値がマッチする場合、`~=`というオペレータが使える。

### 最後に
こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。