Jakie są różnice między rzutami i ponownymi rzutami w Swift?

83

Po wyszukaniu pewnych odniesień, aby to rozgryźć, -Niestety- nie mogłem znaleźć przydatnego - i prostego - opisu zrozumienia różnic między throwsi rethrows. To trochę zagmatwane, gdy próbujemy zrozumieć, jak powinniśmy ich używać.

Chciałbym wspomnieć, że jestem trochę zaznajomiony z -default- throwsz jego najprostszą formą propagowania błędu, jak następuje:

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")
    }
}

Jak na razie dobrze, ale problem pojawia się, gdy:

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
}

do tej pory wiem, kiedy wywołując funkcję, throwsmusi być obsługiwana przez a try, w przeciwieństwie do rethrows. Więc co?! Jaka jest logika, którą powinniśmy się kierować, decydując się na użycie throwslub rethrows?

Ahmad F.
źródło

Odpowiedzi:

186

Z „Deklaracji” w książce Swift:

Funkcje i metody odtwarzania

Funkcję lub metodę można zadeklarować za pomocą rethrowssłowa kluczowego, aby wskazać, że zgłasza błąd tylko wtedy, gdy jeden z jej parametrów funkcji zgłasza błąd. Te funkcje i metody są znane jako funkcje ponownego wrzucania i metody ponownego wrzucania . Funkcje i metody ponownego generowania muszą mieć co najmniej jeden parametr funkcji rzucającej.

Typowym przykładem jest mapmetoda:

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

Jeśli mapjest wywoływana z niewyrzucającą transformacją, sama nie zgłasza błędu i można ją wywołać bez try:

// Example 1:

let a = [1, 2, 3]

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

let a1 = a.map(f1)

Ale jeśli mapzostanie sprawdzony z zamknięciem rzucanym, sam może rzucić i musi zostać sprawdzony za pomocą 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 {
    // ...
}
  • Gdyby mapzostały zadeklarowane jako throwszamiast rethrowsto, musiałbyś to wywołać trynawet w przykładzie 1, co jest "niewygodne" i niepotrzebnie nadyma kod.
  • Gdyby mapzostało zadeklarowane bez throws/rethrows, nie można by go wywołać z zamknięciem rzucanym, jak w przykładzie 2.

To samo odnosi się do innych metod z Swift Standard Library, które biorą parametry funkcji: filter(), index(where:), forEach()i wiele wiele więcej.

W Twoim przypadku,

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

oznacza funkcję, która może zgłosić błąd, nawet jeśli jest wywoływana z niezrzucającym argumentem, podczas gdy

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

oznacza funkcję, która zgłasza błąd tylko wtedy, gdy jest wywoływana z rzucającym argumentem.

Z grubsza rzecz ujmując, rethrowsdotyczy funkcji, które nie generują błędów „na własną rękę”, a jedynie błędy „do przodu” z parametrów funkcji.

Martin R.
źródło
37
Ostatnie zdanie jest złote!
Klaas
1
@Honey: Ostatnie zdanie odpowiedzi brzmi, jak bym to podsumował.
Martin R
Tak, to wydaje się lepsze. Czy poprawne byłoby stwierdzenie, że rethrows są używane tylko z zamknięciami, poza tym, że nie są potrzebne?
Honey
3
@ Kochanie: nie do końca rozumiem, o co ci chodzi. rethrowsjest używane tylko z funkcjami, które pobierają parametry funkcji, które mogą być generowane.
Martin R
Jak wyglądałaby składnia, gdyby spełniała obie te funkcje? 🤔🤔 Czy byłyby to „rzuty ponownie” ??
Kautsya Kanu
14

Dodam tylko coś wraz z odpowiedzią Martina. Funkcja nie rzucająca z taką samą sygnaturą jak funkcja rzucająca jest uważana za sub-typefunkcję rzucającą. Dlatego powtórki mogą określić, który to jest i wymagają tylko trywtedy, gdy parametr func również jest zgłaszany , ale nadal akceptuje tę samą sygnaturę funkcji, która nie jest zgłaszana . Jest to wygodny sposób, aby używać bloku do try tylko wtedy, gdy rzuca parametr func, ale drugi kod funkcji nie zgłasza błędu.

JustinM
źródło