Skip to content

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 um throw lançando o erro real ou um try para executar o método que lança. O método que contém o try também requer a palavra-chave throws:
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()
}