0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

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文のような使い方、特定の型の値を取り出す方法など様々な使い方がある。
  • 両者の型が違うが値がマッチする場合、~=というオペレータが使える。

最後に

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

0
2
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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?