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/await | Manuálna | Natívna |
| Stratégia rozšírenia | Pridanie case do enum-u | Pridanie case do enum-u |
| Boilerplate pri reťazení | flatMap reťazce | Lineárny kód s try |
| Vhodné pre | Hodnotová sémantika, ukladanie | Synchró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.
- Identifikujte API hranice. Knižnice, framework moduly a sieťové vrstvy sú ideálni kandidáti.
- Vytvorte chybové enum-y. Premeňte voľne hádzané chyby na konkrétne typy s prípadnou prílohou kontextu.
- Zmeňte podpis.
throws → throws(MyError). Kompilátor vám ukáže, kde sa hádže iný typ (a verte mi, ukáže ich dosť).
- Pretypujte vnútorné chyby. Tam, kde voláte iné API, prevoďte ich chyby na váš doménový typ vo vlastnom
catch.
- 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.