MASTG-KNOW-0065: Tratamento de Exceções
As exceções geralmente ocorrem após um aplicativo entrar em um estado anormal ou com erro.
Testar o tratamento de exceções consiste em garantir que o aplicativo irá lidar com a exceção e entrar em um estado seguro sem expor nenhuma informação sensível por meio de seus mecanismos de logging ou pela interface do usuário (UI).
Tenha em mente que o tratamento de exceções em Objective-C é bastante diferente do tratamento de exceções em Swift. Unir as duas abordagens em um aplicativo que é escrito tanto em código legado Objective-C quanto em Swift pode ser problemático.
Tratamento de Exceções em Objective-C¶
Objective-C possui dois tipos de erros:
NSException:
NSException é usado para tratar erros de programação e de baixo nível (por exemplo, divisão por zero e acesso a array fora dos limites).
Uma NSException pode ser levantada com raise ou lançada com @throw. A menos que seja capturada, essa exceção invocará o manipulador de exceções não tratadas, com o qual você pode registrar a instrução (o logging interromperá o programa). @catch permite que você se recupere da exceção se estiver usando um bloco @try-@catch:
@try {
//fazer o trabalho aqui
}
@catch (NSException *e) {
//recuperar da exceção
}
@finally {
//limpeza
Lembre-se de que usar NSException traz armadilhas de gerenciamento de memória: você precisa limpar as alocações do bloco try que estão no bloco finally. Observe que você pode promover objetos NSException para NSError instanciando um NSError no bloco @catch.
NSError:
NSError é usado para todos os outros tipos de erros. Algumas APIs do framework Cocoa fornecem erros como objetos em seu callback de falha caso algo dê errado; aquelas que não os fornecem passam um ponteiro para um objeto NSError por referência. É uma boa prática fornecer um tipo de retorno BOOL para o método que recebe um ponteiro para um objeto NSError para indicar sucesso ou falha. Se houver um tipo de retorno, certifique-se de retornar nil para erros. Se NO ou nil for retornado, isso permite que você inspecione o erro/motivo da falha.
Tratamento de Exceções em Swift¶
O tratamento de exceções em Swift (versões 2 a 5) é bastante diferente. O bloco try-catch não existe para lidar com NSException. O bloco é usado para tratar erros que estão em conformidade com o protocolo Error (Swift 3) ou ErrorType (Swift 2). Isso pode ser desafiador quando códigos Objective-C e Swift são combinados em um aplicativo. Portanto, NSError é preferível a NSException para programas escritos em ambas as linguagens. Além disso, o tratamento de erros é opcional em Objective-C, mas throws deve ser explicitamente tratado em Swift. Para converter o lançamento de erros, consulte a documentação da Apple.
Métodos que podem lançar erros usam a palavra-chave throws. O tipo Result representa um sucesso ou falha; consulte Result, Como usar Result no Swift 5 e O poder dos tipos Result no Swift. Existem quatro maneiras de tratar erros em Swift:
- Propagar o erro de uma função para o código que chama essa função. Nessa situação, não há
do-catch; há apenas umthrowlançando o erro real ou umtrypara executar o método que lança. O método que contém otrytambém requer a palavra-chavethrows:
func dosomething(argumentx:TypeX) throws {
try functionThatThrows(argumentx: argumentx)
}
- Tratar o erro com uma instrução
do-catch. Você pode usar o seguinte padrão:
func doTryExample() {
do {
try functionThatThrows(number: 203)
} catch NumberError.lessThanZero {
// Tratar número menor que zero
} catch let NumberError.tooLarge(delta) {
// Tratar número muito grande (com valor delta)
} catch {
// Tratar quaisquer outros erros
}
}
enum NumberError: Error {
case lessThanZero
case tooLarge(Int)
case tooSmall(Int)
}
func functionThatThrows(number: Int) throws -> Bool {
if number < 0 {
throw NumberError.lessThanZero
} else if number < 10 {
throw NumberError.tooSmall(10 - number)
} else if number > 100 {
throw NumberError.tooLarge(100 - number)
} else {
return true
}
}
- Tratar o erro como um valor opcional:
let x = try? functionThatThrows()
// Neste caso, o valor de x é nil em caso de erro.
- Usar a expressão
try!para afirmar que o erro não ocorrerá. - Tratar o erro genérico como um retorno do tipo
Result:
enum ErrorType: Error {
case typeOne
case typeTwo
}
func functionWithResult(param: String?) -> Result<String, ErrorType> {
guard let value = param else {
return .failure(.typeOne)
}
return .success(value)
}
func callResultFunction() {
let result = functionWithResult(param: "OWASP")
switch result {
case let .success(value):
// Tratar sucesso
case let .failure(error):
// Tratar falha (com erro)
}
}
- Tratar erros de rede e decodificação JSON com um tipo
Result:
struct MSTG: Codable {
var root: String
var plugins: [String]
var structure: MSTGStructure
var title: String
var language: String
var description: String
}
struct MSTGStructure: Codable {
var readme: String
}
enum RequestError: Error {
case requestError(Error)
case noData
case jsonError
}
func getMSTGInfo() {
guard let url = URL(string: "https://raw.githubusercontent.com/OWASP/mastg/master/book.json") else {
return
}
request(url: url) { result in
switch result {
case let .success(data):
// Tratar sucesso com dados MSTG
let mstgTitle = data.title
let mstgDescription = data.description
case let .failure(error):
// Tratar falha
switch error {
case let .requestError(error):
// Tratar erro de requisição (com erro)
case .noData:
// Tratar nenhum dado recebido na resposta
case .jsonError:
// Tratar erro ao analisar JSON
}
}
}
}
func request(url: URL, completion: @escaping (Result<MSTG, RequestError>) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
return completion(.failure(.requestError(error)))
} else {
if let data = data {
let decoder = JSONDecoder()
guard let response = try? decoder.decode(MSTG.self, from: data) else {
return completion(.failure(.jsonError))
}
return completion(.success(response))
}
}
}
task.resume()
}