Edited at

[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と組み合わせると最強です。

個人の感想です


ご注意

カスタムオペレータは諸刃の剣です。

多用すると意味不明になることがままあるので注意しましょう。