Tratamento de erros em Swift: try/catch e muito mais (nível avançado)
\n\nO tratamento de erros é um dos tópicos principais no desenvolvimento em Swift. Se você já conhece o básico de do-catch e try?, é hora de avançar para o próximo nível. Neste artigo, abordaremos técnicas avançadas: trabalho com o protocolo Error, tipo Result, palavra-chave rethrows, além de como projetar corretamente uma hierarquia de erros em projetos reais iOS/macOS.
Mergulho profundo no protocolo Error e erros personalizados
\n\nEm Swift, qualquer erro deve estar em conformidade com o protocolo Error. No entanto, para tornar o código mais legível e sustentável, vale a pena criar suas próprias enumerações (enum) com casos aninhados e valores associados.
enum NetworkError: Error {\n case invalidURL\n case noData\n case decodingFailed(description: String)\n case serverError(statusCode: Int)\n case timeout(seconds: Double)\n}\n\n// Exemplo de uso\nfunc fetchUser(id: Int) throws -> User {\n guard id > 0 else {\n throw NetworkError.invalidURL\n }\n // ... lógica da requisição\n if someCondition {\n throw NetworkError.serverError(statusCode: 500)\n }\n return User()\n}\n\nEssa abordagem permite transmitir contexto adicional: código de erro, descrição ou tempo de espera. Isso é especialmente útil para registro de logs e depuração.
\n\nResult: alternativa ao do-catch para código assíncrono
\n\nO tipo Result<Success, Failure> é uma ferramenta poderosa que veio da programação funcional. Ele é ideal para APIs baseadas em callbacks, requisições de rede e operações assíncronas.
enum DataError: Error {\n case corrupted\n case notFound\n}\n\nfunc loadImage(from url: URL, completion: @escaping (Result<UIImage, DataError>) -> Void) {\n DispatchQueue.global().async {\n // Simulação de carregamento\n if arc4random_uniform(2) == 0 {\n completion(.success(UIImage()))\n } else {\n completion(.failure(.notFound))\n }\n }\n}\n\n// Uso\nloadImage(from: someURL) { result in\n switch result {\n case .success(let image):\n print("Imagem carregada: \\(image)")\n case .failure(let error):\n print("Erro: \\(error.localizedDescription)")\n }\n}\n\nResult torna o código mais previsível e elimina a necessidade de lidar com valores opcionais com nil. Além disso, você pode usar .map e .flatMap para cadeias de transformações.
Rethrows: quando a função "passa adiante" os erros
\n\nA palavra-chave rethrows é um dos recursos mais subestimados do Swift. Ela permite escrever uma função que não lança erros por si só, mas os "propaga" se o closure passado for throws.
func performOperation<T>(on array: [T], operation: (T) throws -> Bool) rethrows -> [T] {\n var result: [T] = []\n for element in array {\n if try operation(element) {\n result.append(element)\n }\n }\n return result\n}\n\n// Sem erros (não precisa de try)\nlet numbers = [1, 2, 3, 4, 5]\nlet evenNumbers = performOperation(on: numbers) { $0.isMultiple(of: 2) }\nprint(evenNumbers) // [2, 4]\n\n// Com erro (precisa de try)\nenum ValidationError: Error { case negative }\nlet filtered = try performOperation(on: numbers) { value in\n guard value > 0 else { throw ValidationError.negative }\n return value % 2 == 0\n}\n\nrethrows torna a API flexível: o usuário pode passar tanto um closure comum (sem try) quanto um closure com throws (neste caso, será necessário try). Isso é frequentemente usado na biblioteca padrão do Swift — por exemplo, em map, filter e for