Swift ifとguardのエトセトラ - Re.Ra.Ku アドベントカレンダー day 11

Re.Ra.Ku アドベントカレンダー 11日目です。

こんにちは、神場です。 前回の記事 に引き続き、今回もSwift関連の記事となります。

概要

今回はif文とguard文に関するtipsです。 if文は一般の条件分岐に、guard文は想定外の値が来た場合などの早期リターンに使われるという違いはありますが、使い方が似ているためこの記事ではまとめて紹介したいと思います。

ifとguard両方で使えるもの

今回紹介するのはこの3つです。いずれもifguardの両方で使えます。

  • if let, guard letによるOptional Binding
  • if A, B, ..., guard A, ,B ...のような複数の条件
  • if case, guard caseによるパターンマッチ

順番に見て行きましょう。

if let, guard letによるOptional Binding

まずは基本的なところですが、if let, guard letでオプショナルな変数のバインディングを行うことが出来ます。

if let a = a {
    print(a)
} else {
    print("a is nil")
}

これはオプショナルな変数のUnwrapに使う基本的な方法なので、特に疑問はないかと思います。

if A, B, ..., guard A, ,B ...のような複数の条件

Swiftをやり始めの頃だと(自分も含めて)よくやってしまっていたと思うのですが、

if let a = a {
    if let b = b {
        ...
    }
}

のような文はまとめて

if let a = a, let b = b {
    ...
}

と書くことが出来ます。上記の例のように二つぐらいの条件であればまだ良いのですが、条件が多い時は基本的にカンマ区切りで書いたほうがコードがすっきりします。guard文を使う例を以下に挙げます。

indexが存在し、かつステータスがactiveなユーザーの名前を取得するサンプルコード

enum Status {
  case active, inactive
}
struct User {
  let name: String
  let status: Status
}

func getActiveUserName(users: [User], index: Int) -> String? {
    guard
        index < users.count,
        users[index].status == .active
        else { return nil }
    return users[index].name
}

if case, guard caseによるパターンマッチ

上記二つに比べて日本語ではあまり情報がない印象がありますが、実はifguardでもswitchで使うようなパターンマッチを使うことが出来ます。

enumのAssociated Valueの中身をパターンマッチで取り出すサンプルコード

enum Result<T> {
    case success(value: T)
    case failure(error: Error)
}

func getValue<T>(result: Result<T>) -> T? {
    guard case let .success(value) = result else {
        return nil
    }
    return value
}

上記のように、switchに書くcaseがほぼそのままguardの中に 入ります。

なおこれを応用(?)することによって、Optionalでない変数のバインドを行うことも出来ます。

indexが存在し、かつステータスがactiveなユーザーの名前を取得するサンプルコード(userを一旦変数に入れておくバージョン)

func getActiveUserName2(users: [User], index: Int) -> String? {
    guard
        index < users.count,
        case let user = users[index],
        user.status == .active
        else { return nil }
    return user.name
}

if case, guard caseのさらに詳しい情報については、こちらの方の記事 が非常に参考になるかと思います。

より実践的なサンプル

以上のいくつかを組み合わせて、より具体的なサンプルを見てみましょう。

guard文を使ったAPIレスポンスのハンドリングのサンプル

func parseResponse(result: Result<Any>) -> DTO? {
    guard case let .success(value) = result else {
        print("通信に失敗したよ")
        return nil
    }
    
    guard let json = data as? [String: Any] else {
        print("データがJSONじゃないみたいだよ")
        return nil
    }
    
    guard (200..<300) ~= statusCode else {
        print("ステータスコードが200番台以外だよ")
        return nil
    }
    
    guard let dto = parseJson(json) else {
        print("JSONが期待しているものと違うよ")
        return nil
    }
    
    print("正常なレスポンスが返ってきたよ")
    return dto
}

ただしエラー内容も取得したい場合はsuccessfailureの2つにパターンマッチを使いたいため、成功か失敗かの分岐部分をswitchで書くことになるかと思います。

まとめ

いかがでしょうか。以上の3つ程度があればifguardだけでも比較的読みやすい書き方が出来るのではないでしょうか。若干まとまりのない感じになってしまいましたが、もしご参考になれば幸いです。

参考