Typed Throws v Swift 6.2: Kompletný sprievodca novým error handlingom

Sprievodca typed throws v Swift 6.2 — od základov syntaxe cez generickú propagáciu chýb až po migráciu sieťovej vrstvy a integráciu so SwiftUI. S funkčnými ukážkami pre Xcode 26.

Typed Throws Swift 6.2: Error Handling Guide

Swift 6 priniesol jednu z najvýznamnejších zmien v error handlingu, akú sme za posledné roky videli — typed throws. Klasický throws v podstate hovorí len „táto funkcia môže hodiť čokoľvek", čo, povedzme si úprimne, nikdy nebolo veľmi uspokojujúce. Typed throws to mení: presne deklaruje, ktoré chybové typy môžu opustiť funkciu. A v Swift 6.2 sa táto funkcia konečne stabilizovala.

V kombinácii so striktnou súbežnosťou a generikami sa otvárajú dvere k čistejším, bezpečnejším API. Tak poďme na to.

V tomto sprievodcovi prejdeme od základov syntaxe cez generické propagácie chýb, porovnanie s Result, až po praktické vzory pre SwiftUI a sieťovú vrstvu — všetko s funkčnými ukážkami kódu pre Xcode 26 a Swift 6.2.

Čo sú typed throws a prečo na nich záleží

V klasickom Swift modeli každá throws funkcia môže hodiť any Error. To znamená, že volajúci nikdy presne nevie, ktorý typ chyby má očakávať — musí použiť všeobecný catch alebo pretypovanie. Trochu ako otvárať dvere, za ktorými môže byť čokoľvek.

Typed throws to obracia naopak. Typ chyby je súčasťou podpisu funkcie a kompilátor vie staticky overiť, ktoré chyby môžu nastať.

// Klasický throws — môže hodiť čokoľvek
func fetchUser(id: String) throws -> User {
    // ...
}

// Typed throws — môže hodiť iba NetworkError
func fetchUser(id: String) throws(NetworkError) -> User {
    // ...
}

Vďaka tomu sa pri do/catch bloku odvodí presný typ chyby. A čo je najlepšie, nemusíte písať default catch klauzulu len preto, že kompilátor o tom nemá tušenia.

Tri ekvivalentné podoby

Swift definuje tri kategórie throwing funkcií a pochopenie ich vzájomného vzťahu je naozaj kľúčové:

  • func f() throws — ekvivalentné throws(any Error)
  • func f() throws(MyError) — typed throws s konkrétnym typom
  • func f() — ekvivalentné throws(Never)

To posledné je obzvlášť zaujímavé (a ja osobne to považujem za najelegantnejší kúsok celého návrhu): throws(Never) znamená „túto funkciu volajte bez try". Vďaka tomuto modelu môžu generické konštrukcie pracovať s throwing aj non-throwing kódom úplne jednotne.

Definovanie vlastných chybových typov

Najlepšou praxou je vytvoriť doménovo špecifický enum implementujúci Error. Pre typed throws je výhodné, ak je tiež Equatable (kvôli testovaniu) a LocalizedError (pre používateľské hlásenia).

enum NetworkError: Error, Equatable, LocalizedError {
    case invalidURL(String)
    case timeout(seconds: Int)
    case serverError(statusCode: Int)
    case decoding(field: String)
    case unauthorized

    var errorDescription: String? {
        switch self {
        case .invalidURL(let url):
            return "Neplatná URL adresa: \(url)"
        case .timeout(let seconds):
            return "Vypršal časový limit (\(seconds) s)"
        case .serverError(let code):
            return "Chyba servera: HTTP \(code)"
        case .decoding(let field):
            return "Chyba pri dekódovaní poľa: \(field)"
        case .unauthorized:
            return "Neautorizovaný prístup"
        }
    }
}

Vyčerpávajúce spracovanie chýb

Pri typed throws sa pri do/catch bloku odvodí typ chyby z funkcie a kompilátor vás upozorní, ak nepokryjete všetky prípady. To zásadne mení dynamiku error handlingu — zrazu ho neignorujete, lebo nemôžete.

func loadProfile() async {
    do {
        let user = try await fetchUser(id: "abc-123")
        print("Načítaný používateľ: \(user.name)")
    } catch .invalidURL(let url) {
        // Swift vie, že error je NetworkError
        log("Zlá URL: \(url)")
    } catch .timeout(let seconds) {
        log("Timeout po \(seconds) s")
    } catch .serverError(let code) where code >= 500 {
        log("Server padol: \(code)")
    } catch {
        // Pokrýva zvyšok NetworkError prípadov
        log("Iná sieťová chyba: \(error)")
    }
}

Všimnite si, že nikde nemusíme písať error as NetworkError — typ je odvodený automaticky. To je naozaj zásadný rozdiel oproti klasickému throws, na ktorý si rýchlo zvyknete.

Typed throws v generických funkciách

A teraz to najlepšie. Tu vyniká skutočná sila funkcie. Pred Swift 6 ste museli používať rethrows, ktorý síce fungoval pre throwing closures, ale nedokázal presne propagovať chybový typ. S typed throws viete chytiť typ chyby ako generický parameter:

extension Sequence {
    func mapTyped<T, E: Error>(
        _ transform: (Element) throws(E) -> T
    ) throws(E) -> [T] {
        var result: [T] = []
        for element in self {
            try result.append(transform(element))
        }
        return result
    }
}

// Použitie s typed closure
let names = ["alice", "bob", "charlie"]
let upper = try names.mapTyped { (name) throws(NetworkError) -> String in
    guard !name.isEmpty else { throw .invalidURL(name) }
    return name.uppercased()
}

Keď je closure non-throwing, generický parameter E sa odvodí na Never a celá funkcia sa stane non-throwing — nemusíte písať try. Tento elegantný mechanizmus postupne nahrádza staršie rethrows v štandardnej knižnici.

Typed throws verzus Result

Pred uvedením async/await bol Result<Success, Failure> štandardným spôsobom, ako vyjadriť „vrátim hodnotu alebo presnú chybu". Typed throws túto sémantiku prináša priamo do throwing syntaxe.

VlastnosťResult<T, E>throws(E)
Typ chyby v podpiseÁnoÁno
Kompozícia s async/awaitManuálnaNatívna
Stratégia rozšíreniaPridanie case do enum-uPridanie case do enum-u
Boilerplate pri reťazeníflatMap reťazceLineárny kód s try
Vhodné preHodnotová sémantika, ukladanieSynchrónne aj async API

Praktické pravidlo, ktoré sa mi osvedčilo: ak chcete chybu uložiť, poslať cez hranicu aktora alebo serializovať, použite Result. Pre klasický control flow je typed throws čitateľnejší.

Praktický príklad: sieťový klient s typed throws

Postavme si malý, ale úplný HTTP klient. Stavia na URLSession a vracia presný chybový typ — presne to, čo by ste chceli mať v produkčnom kóde.

actor APIClient {
    private let session: URLSession
    private let decoder = JSONDecoder()

    init(session: URLSession = .shared) {
        self.session = session
        decoder.dateDecodingStrategy = .iso8601
    }

    func get<T: Decodable>(
        _ type: T.Type,
        from urlString: String
    ) async throws(NetworkError) -> T {
        guard let url = URL(string: urlString) else {
            throw .invalidURL(urlString)
        }

        let data: Data
        let response: URLResponse
        do {
            (data, response) = try await session.data(from: url)
        } catch let error as URLError where error.code == .timedOut {
            throw .timeout(seconds: 30)
        } catch {
            throw .serverError(statusCode: -1)
        }

        guard let http = response as? HTTPURLResponse else {
            throw .serverError(statusCode: -1)
        }

        switch http.statusCode {
        case 200..<300:
            break
        case 401:
            throw .unauthorized
        default:
            throw .serverError(statusCode: http.statusCode)
        }

        do {
            return try decoder.decode(T.self, from: data)
        } catch let DecodingError.keyNotFound(key, _) {
            throw .decoding(field: key.stringValue)
        } catch {
            throw .decoding(field: "unknown")
        }
    }
}

Volajúci teraz dostane garantovaný typ chyby a môže ho v UI vrstve presne mapovať na hlásenia. Žiadne „something went wrong" obrazovky.

Integrácia so SwiftUI

V SwiftUI view modeloch ozdobených @Observable môžete chybu uložiť do typed property a UI bude reagovať na konkrétny prípad:

@Observable
@MainActor
final class ProfileViewModel {
    var user: User?
    var lastError: NetworkError?
    var isLoading = false

    private let client = APIClient()

    func load(id: String) async {
        isLoading = true
        defer { isLoading = false }

        do {
            user = try await client.get(
                User.self,
                from: "https://api.example.com/users/\(id)"
            )
            lastError = nil
        } catch {
            // error je staticky typu NetworkError
            lastError = error
        }
    }
}

Migrácia existujúceho kódu

Dobrá správa: nemusíte prepisovať celý projekt naraz. Apple aj komunita odporúčajú postupný prístup, a úprimne, je to jediná rozumná cesta. Začnite na úrovni modulov alebo SDK, kde sú chybové typy stabilné a klienti benefitujú z vyčerpávajúceho spracovania.

  1. Identifikujte API hranice. Knižnice, framework moduly a sieťové vrstvy sú ideálni kandidáti.
  2. Vytvorte chybové enum-y. Premeňte voľne hádzané chyby na konkrétne typy s prípadnou prílohou kontextu.
  3. Zmeňte podpis. throwsthrows(MyError). Kompilátor vám ukáže, kde sa hádže iný typ (a verte mi, ukáže ich dosť).
  4. Pretypujte vnútorné chyby. Tam, kde voláte iné API, prevoďte ich chyby na váš doménový typ vo vlastnom catch.
  5. Nechajte interný kód untyped. Privátne pomocné funkcie a app-level kód obyčajne profitujú z flexibility throws.

Kedy typed throws NEpoužívať

Aj keď je featura mocná, nie je vhodná všade. Tu je niekoľko prípadov, kedy by som siahol skôr po klasickom throws:

  • Aplikačná vrstva. Kde sa chyby väčšinou len logujú alebo zobrazujú, untyped throws je flexibilnejší.
  • Rýchlo sa vyvíjajúce API. Každé pridanie nového chybového prípadu je breaking change pre volajúcich.
  • Kompozícia mnohých zdrojov chýb. Ak funkcia volá päť knižníc, museli by ste mať obrovský zjednotený enum — a to nie je zábava.

Spolupráca s typed throws v koncepcii súbežnosti

Swift 6.2 zmenil predvolené umiestnenie async funkcií — bežia v aktorovom kontexte volajúceho, ak nepoužijete @concurrent. Typed throws sa do tohto modelu zapája bez výnimky. Task a TaskGroup majú generický Failure parameter, ktorý môžete využiť rovnako ako pri funkciách.

let task = Task { () async throws(NetworkError) -> User in
    try await APIClient().get(User.self, from: "https://api.example.com/me")
}

do {
    let user = try await task.value
    print(user.name)
} catch .unauthorized {
    // presne typovaný catch
}

Pre paralelné spracovanie viacerých požiadaviek funguje withThrowingTaskGroup rovnako, no s typed verziou withThrowingTaskGroup(of:returning:) môžete chybový typ priamo špecifikovať.

Testovanie typed throws kódu

Swift Testing framework, ktorý sa v roku 2026 stal oficiálnou voľbou, plne podporuje typed throws cez makro #expect(throws:).

import Testing

@Test func invalidURLReportsCorrectly() async {
    let client = APIClient()
    await #expect(throws: NetworkError.invalidURL("not a url")) {
        _ = try await client.get(User.self, from: "not a url")
    }
}

@Test func unauthorizedMapping() async {
    // mock session vracajúci 401
    let client = APIClient(session: MockSession(status: 401))
    await #expect(throws: NetworkError.unauthorized) {
        _ = try await client.get(User.self, from: "https://x")
    }
}

Časté chyby a ako sa im vyhnúť

1. Kvalifikujte enum prípady plne

V Swift 6.2 stále existuje známy problém s odvodzovaním typu pri generických typed throws a autoclosures. Ak narazíte na chybu „type has no member" (a narazíte, skôr či neskôr), napíšte celý kvalifikovaný typ:

// ❌ Niekedy nefunguje
#expect(throws: .noAccount) { try login() }

// ✅ Vždy funguje
#expect(throws: AuthError.noAccount) { try login() }

2. Nepretierajte enum prípady stavom

Lákavé je do enum-u dať veľa kontextu, ale potom sa Equatable rozbije a porovnávanie v testoch je zrazu peklo. Držte sa minimálneho kontextu, zvyšok logujte oddelene.

3. Pozor na Error protokol

Typed throws vyžaduje, aby váš typ vyhovoval Error protokolu. Pri generických ohraničeniach to musíte explicitne deklarovať: throws(E) where E: Error.

FAQ — najčastejšie otázky

V akej verzii Swiftu sú typed throws dostupné?

Typed throws sú oficiálne dostupné od Swift 6.0 (Xcode 16) a v Swift 6.2 (Xcode 26) sú stabilné s vylepšenou inferenciou pre generiká a async kontext.

Sú typed throws spätne kompatibilné?

Áno. Funkcia s throws(MyError) sa správa ako podtyp throws, takže ju môžete použiť všade tam, kde sa očakáva untyped throwing funkcia. Opačne to ale neplatí — funkciu s untyped throws nemôžete použiť tam, kde sa očakáva konkrétny typ.

Mám nahradiť všetky Result typy typed throws?

Nie, určite nie. Result je naďalej užitočný, keď potrebujete uložiť chybu ako hodnotu, prenášať ju cez hranicu aktora, serializovať alebo kombinovať s reaktívnymi pipeline. Pre klasický control flow je typed throws čitateľnejší.

Funguje typed throws s rethrows?

Typed throws nahrádza väčšinu prípadov, kedy ste predtým používali rethrows. Štandardná knižnica postupne migruje API ako map, filter a reduce na typed throws variant, ktorý je presnejší a dovoľuje vyčerpávajúce zachytenie chyby.

Ako vplývajú typed throws na výkon?

V bežnom kóde je rozdiel zanedbateľný. V Embedded Swift, kde nie je k dispozícii alokovaný any Error existential, sú typed throws v podstate nutnosťou — kompilátor vie presne, akú veľkosť má chyba mať na zásobníku.

Záver

Typed throws sú jedným z mála jazykových rozšírení, ktoré zlepšujú čitateľnosť aj typovú bezpečnosť bez výrazného boilerplate-u. Kombinácia s prísnou súbežnosťou Swift 6.2 a observation frameworkom v SwiftUI dáva moderným iOS aplikáciám lepší základ než kedykoľvek predtým.

Začnite na hraniciach knižníc, postupne migrujte sieťovú vrstvu, a interný kód nechajte na klasickom throws. Tak získate prehľadnosť tam, kde je dôležitá, a flexibilitu tam, kde sa hodí. A o pár mesiacov sa už k untyped throws nebudete chcieť vrátiť — to vám zaručujem.

O Autorovi Mei-Lin Chen

Mei-Lin joined Robinhood in 2020 as an iOS engineer on the Crypto team and stayed through the SwiftUI rewrite of the order-entry flow before leaving in 2025. She also did a two-year stint at Asana earlier in her career working on the iPad app and the Mac Catalyst port. She writes about the parts of Apple's frameworks that the WWDC talks gloss over - what Observable actually does to your view-update graph, why @Bindable bindings tear in some animation contexts, and the surprisingly deep rabbit hole of Swift macros for boilerplate elimination. She has shipped two indie apps to the App Store, one of which hit #4 in the Health & Fitness category for a week in 2023. Mei-Lin is based in Seattle and has been writing Swift for 8 years.