web-dev-qa-db-ja.com

Swiftのスローとリスローの違いは何ですか?

それを理解するためにいくつかの参照を検索した後、残念ながら、throwsrethrowsの違いを理解するのに役立つ-簡単な説明が見つかりませんでした。それらをどのように使用すべきかを理解しようとすると、一種の混乱を招きます。

次のように、エラーを伝播するための最も単純な形式の-default- throwsにある程度精通していることに言及します。

enum CustomError: Error {
    case potato
    case tomato
}

func throwCustomError(_ string: String) throws {
    if string.lowercased().trimmingCharacters(in: .whitespaces) == "potato" {
        throw CustomError.potato
    }

    if string.lowercased().trimmingCharacters(in: .whitespaces) == "tomato" {
        throw CustomError.tomato
    }
}

do {
    try throwCustomError("potato")
} catch let error as CustomError {
    switch error {
    case .potato:
        print("potatos catched") // potatos catched
    case .tomato:
        print("tomato catched")
    }
}

これまでのところは良好ですが、問題は次の場合に発生します。

func throwCustomError(function:(String) throws -> ()) throws {
    try function("throws string")
}

func rethrowCustomError(function:(String) throws -> ()) rethrows {
    try function("rethrows string")
}

rethrowCustomError { string in
    print(string) // rethrows string
}

try throwCustomError { string in
    print(string) // throws string
}

私がこれまでに知っているのは、throwsとは異なり、tryで処理する必要があるrethrows関数を呼び出すときです。だから何?! throwsまたはrethrowsの使用を決定する際に従うべきロジックは何ですか?

61
Ahmad F

Swift本の "Declarations" から:

関数とメソッドの再スロー

関数またはメソッドをrethrowsキーワードで宣言して、関数のパラメーターの1つがエラーをスローした場合にのみエラーをスローすることを示すことができます。これらの関数とメソッドは、再スロー関数および再スローメソッドとして知られています。再スローする関数とメソッドには、少なくとも1つのスロー関数パラメーターが必要です。

典型的な例は map メソッドです:

public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

mapが非スロー変換で呼び出された場合、エラー自体はスローされず、tryなしで呼び出すことができます。

// Example 1:

let a = [1, 2, 3]

func f1(n: Int) -> Int {
    return n * n
}

let a1 = a.map(f1)

ただし、mapがスローするクロージャーで呼び出される場合、それ自体がスローでき、tryで呼び出す必要があります。

// Example 2:

let a = [1, 2, 3]
enum CustomError: Error {
    case illegalArgument
}

func f2(n: Int) throws -> Int {
    guard n >= 0 else {
        throw CustomError.illegalArgument
    }
    return n*n
}


do {
    let a2 = try a.map(f2)
} catch {
    // ...
}
  • mapthrowsではなくrethrowsとして宣言されている場合は、例1でもtryを使用して呼び出す必要があります。
  • mapthrows/rethrowsなしで宣言された場合、例2のようにスローするクロージャーで呼び出すことはできません。

同じことが、関数パラメーターを受け取るSwift標準ライブラリの他のメソッドにも当てはまります:filter()index(where:)forEach()など。

あなたの場合、

func throwCustomError(function:(String) throws -> ()) throws

は、非スロー引数で呼び出された場合でもエラーをスローできる関数を示しますが、

func rethrowCustomError(function:(String) throws -> ()) rethrows

throwing引数を指定して呼び出された場合にのみエラーをスローする関数を示します。

大まかに言うと、rethrowsは、「独自に」エラーをスローするのではなく、関数パラメーターから「フォワード」エラーのみをスローする関数用です。

140
Martin R

マーティンの答えと一緒に何かを追加するだけです。スロー機能と同じシグネチャを持つ非スロー機能は、スロー機能のsub-typeと見なされます。これが、再スローがそれがどれであるかを決定し、func paramもスローするときにtryのみを必要とするが、スローしない同じ関数シグネチャを受け入れることができる理由です。 func paramがスローされたときにのみdo tryブロックを使用する必要がある便利な方法ですが、関数内の他のコードはエラーをスローしません。

13
JustinM