Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

@masakihori

[Swift] 例外を投げるためのカスタムオペレータ

これはなに?

例外を投げる関数を作る時、単純な条件で例外を投げるためにguard文を書くのが煩わしいことがありませんか?
その煩わしさを軽減させるカスタムオペレータを作りました。

使用する場面

JSONが入っているであろうDataから特定の値を取り出す場面を考えましょう。
普通ならこのようになるでしょう。

仕様

  1. Dataを受け取る
  2. JSONは { "identifier": "hogehoge"; }
  3. 戻り値として"hogehoge"を返す
  4. 問題があった場合は例外を投げる

例外

enum MyError: Error {
  case couldNotParseJSON
  case jsonNotDictionay
  case jsonHasNotIdentifier
}

通常のコード

func getIdentifier(_ data: Data) throws -> String {

  guard let json = try? JSONSerialization.jsonObject(with: data) else {
    throw MyError.couldNotParseJSON
  }

  guard let dict = json as? [String: Any] else {
    throw MyError.jsonNotDictionay
  }

  guard let identifier = dict["identifier"] as? String else {
    throw MyError.jsonHasNotIdentifier
  }

  return identifier
}

やっていることは単純なのですが、すごく長いです。

カスタムオペレータを使ってみる

上の関数をカスタムオペレータを導入して簡略化します。

func getIdentifier(_ data: Data) throws -> String {

  let json = try JSONSerialization.jsonObject(with: data) !!! MyError.couldNotParseJSON
  let dict = try json as? [String: Any] ??! MyError.jsonNotDictionay

  return try dict["identifier"] as? String ??! MyError.jsonHasNotIdentifier
}

3行になりました。

カスタムオペレータ

カスタムオペレータは以下の通りです。

precedencegroup ThrowPrecedence {
    associativity: left
    higherThan: NilCoalescingPrecedence
}
infix operator !!! : ThrowPrecedence
infix operator ?!! : ThrowPrecedence
infix operator ??! : ThrowPrecedence


/// 左辺値が例外を投げた場合に右辺値の例外を投げる
///
/// - Parameters:
///   - value: 例外が発生しうる値
///   - throwsError: 投げられる例外
/// - Returns: 例外が発生しなければ左辺値
/// - Throws: 右辺値の例外が投げられる
func !!! <T, E: Error> (_ value: @autoclosure () throws -> T, _ throwsError: E) throws -> T {

    do {

        return try value()
    }
    catch {

        throw throwsError
    }
}


/// 左辺値がnilまたは例外を投げた場合に右辺値の例外を投げる
///
/// - Parameters:
///   - optionalValue: nilまたは例外が発生しうる値
///   - throwsError: 投げられる例外
/// - Returns: 例外が発生しなく、かつnilでなければ左辺値
/// - Throws: 右辺値の例外が投げられる
func ?!! <T, E: Error> (_ optionalValue: @autoclosure () throws -> T?, _ throwsError: E) throws -> T {

    guard let returnValue = try optionalValue() !!! throwsError else {

        throw throwsError
    }

    return returnValue
}


/// 左辺値がnilならば右辺値の例外を投げる
///
/// - Parameters:
///   - optionalValue: nilになりうる値
///   - throwsError: 投げられる例外
/// - Returns: nilでなければ左辺値
/// - Throws: 右辺値の例外が投げられる
func ??! <T, E: Error> (_ optionalValue: @autoclosure () -> T?, _ throwsError: E) throws -> T {

    return try optionalValue() ?!! throwsError
}

Errorをジェネリクスを使って書いてますが、今のSwiftだと意味ないです。
投げる例外の型を指定できるようになるといいですね。

使いどころ

Errorの型を指定するResultやErrorの型を指定するFutureと組み合わせると最強です。
個人の感想です

ご注意

カスタムオペレータは諸刃の剣です。
多用すると意味不明になることがままあるので注意しましょう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
1
Help us understand the problem. What are the problem?