Tratamento de Erros em Swift: try/catch e muito mais
No desenvolvimento moderno para iOS e macOS, a confiabilidade do aplicativo é um dos principais indicadores de qualidade. Um tratamento adequado de erros em Swift não apenas evita falhas inesperadas, mas também torna o código mais legível e previsível. Neste artigo, abordaremos não apenas o mecanismo clássico try/catch, mas também abordagens alternativas, incluindo o tipo Result, o operador defer e padrões avançados.
Fundamentos: o protocolo Error e a palavra-chave throws
Em Swift, os erros são representados por tipos que estão em conformidade com o protocolo Error. Na maioria das vezes, usamos enumerações (enum) para isso. Para que uma função possa lançar um erro, a palavra-chave throws é usada em sua declaração.
enum NetworkError: Error { case noInternet case serverError(code: Int) case timeout}
func fetchData(from url: String) throws -> Data { guard url.hasPrefix("https") else { throw NetworkError.serverError(code: 400) } // ... simulação de carregamento return Data()}Observe: se uma função for marcada com throws, o código chamador é obrigado a tratar o possível erro usando try, try? ou try!.
Construção do-catch: a abordagem clássica
A maneira mais comum de tratamento é o bloco do-catch. Você coloca o código potencialmente perigoso dentro do do e, no catch, descreve o que fazer com cada erro específico.
do { let data = try fetchData(from: "http://example.com") print("Dados recebidos: \\(data)")} catch NetworkError.noInternet { print("Sem conexão com a internet")} catch NetworkError.serverError(let code) { print("Erro do servidor com código: \\(code)")} catch { print("Erro desconhecido: \\(error.localizedDescription)")}Importante: o último bloco catch sem padrão captura todos os erros restantes. Esta é uma boa prática para não perder exceções inesperadas.
try? e try!: alternativas rápidas
Se o tipo do erro não for importante para você e você só precisar saber se a função foi executada com sucesso, use try?. Em caso de erro, o resultado será nil.
if let data = try? fetchData(from: "https://valid.com") { print("Sucesso: \\(data)")} else { print("Erro ao carregar")}O operador try! é uma "bomba-relógio". Ele suprime o tratamento de erros e causa uma falha no aplicativo se um erro ocorrer. Use-o apenas nos casos em que você tem 100% de certeza de que não há erro (por exemplo, ao carregar um recurso embutido).
let data = try! fetchData(from: "https://trusted-source.com")// Se houver erro — o aplicativo irá travarTipo Result: abordagem funcional
A partir do Swift 5, o tipo Result<Success, Failure> foi adicionado à biblioteca padrão. É um closure que pode conter um valor de sucesso ou um erro. Essa abordagem é especialmente conveniente em código assíncrono e em funções de callback (completion handlers).
func fetchDataResult(from url: String) -> Result<Data, NetworkError> { guard url.hasPrefix("https") else { return .failure(.serverError(code: 400)) } return .success(Data())}
// Usolet result = fetchDataResult(from: "http://bad.com")switch result {case .success(let data): print("Dados: \\(data)")case .failure(let error): print("Erro: \\(error)")}A vantagem do Result é que você não se esquece de tratar o erro, pois o compilador força você a