Advertisement
  1. Code
  2. Swift

Tratamento de erro em Swift 2

Scroll to top
Read Time: 10 min

() translation by (you can also view the original English article)

Ainda não encontrei um programador que goste do tratamento de erro. Entretanto você gostando ou não, um aplicativo robusto precisa tratar erros de tal maneira que o aplicativo continue funcional e informe ao usuário quando necessário. Como o teste, isto faz parte do trabalho.

1. Objetive-C

Em Objective-C, era muito fácil ignorar o tratamento de erro. De uma olhada no exemplo a seguir, em que eu ignorei qualquer erro que pode resultar da execução de um pedido de busca. 

1
// Execute Fetch Request

2
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
3
4
if (results) {
5
    // Process Results

6
    ...
7
}

O exemplo acima demonstra que o tratamento de erro em Objetive-C é algo que o desenvolvedor precisar optar. Se você quiser saber o que deu errado, caso algo de errado, então você informa isso á API entregando a ela um ponteiro de NSError. O exemplo abaixo ilustra como isto funciona em Objetive-C.

1
NSError *error = nil;
2
3
// Execute Fetch Request

4
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
5
6
if (error) {
7
    // Handle Error

8
    ...
9
10
} else {
11
    // Process Results

12
    ...
13
}

Enquanto a versão anterior do Swift não tinha uma boa solução para o tratamento de erro, o Swift 2 nós deu o que foi pedido e valeu a pena esperar. Em Swift 2, o tratamento de erro é habilitado por padrão. Ao contrario do Objetive-C, os desenvolvedores precisam explicitamente informar ao compilador que desejam ignorar o tratamento de erro. Apesar disso não forçar os desenvolvedores a abraçar o tratamento de erros, isto torna a decisão explícita.

Se nós traduzirmos o exemplo acima para Swift, acabariamos com a mesma quantidade de linhas. Apesar de a quantidade de linhas de código que você precisa escrever permaneça inalterada, a sintaxe torna muito explícito o que você está tentando fazer.

1
do {
2
    // Execute Fetch Request

3
    let results = try managedObjectContext.executeFetchRequest(fetchRequest)
4
5
    // Process Results

6
    ...
7
8
} catch {
9
    let fetchError = error as NSError
10
11
    // Handle Error

12
    ...
13
}

Até o final deste tutorial, você irá entender o código acima e saber tudo que você precisa saber sobre tratamento de erros em Swift 2.

2. Funções de lançamento

throws

A foundation de tratamento de erro em Swift é a habilidade para funções e métodos de lançar erros. No jargão do Swift, uma função que pode lançar erros é referenciada como uma função de lançamento. A definição de uma função de lançamento é muito clara desta habilidade, conforme ilustrado no exemplo a seguir.

1
init(contentsOfURL url: NSURL, options readOptionsMask: NSDataReadingOptions) throws

A palavra-chave throws indica que init(contentsOfURL:options:) pode lançar um erro se algo der errado. Se você chamar uma função de lançamento, o compilador irá lançar um erro, soa irônico. Por que isso?

1
let data = NSData(contentsOfURL: URL, options: [])

try

Os criadores do Swift deram muita atenção em criar a linguagem expressiva e o tratamento de erro é exatamente isso, expressivo. Se você tentar chamar uma função que pode lançar um erro, a chamada da função precisa ser iniciada com a palavra-chave try. A palavra-chave try não é mágica. Tudo que ela faz, é conscientizar o programador da habilidade de lançamento da função.

Error Handling Without tryError Handling Without tryError Handling Without try

Espere um segundo. O compilador continua reclamando mesmo depois de nós iniciarmos a chamada da função com a palavra-chave try. O que nós esquecemos?

Error Handling With tryError Handling With tryError Handling With try

O compilador vê que estamos usando a palavra-chave try, mas aposta corretamente que nós não temos um lugar para capturar os erros que possam ser lançado. Para capturar erros, nós usamos a nova instrução do-catch do Swift.

do-catch

Se uma função de lançamento lançar um erro, o erro automaticamente irá propagar para fora do escopo atual até que seja capturado. Isto é similar a exceções em Objective-C e outras linguagens. A idéia é que um erro deve ser capturado e tratado em algum ponto. Mais especificamente, um erro se propaga até ser capturado por uma clausura catch da instrução do-catch.

No exemplo atualizado abaixo, nós chamamos o método init(contentsOfURL:options:) em uma instrução do-catch. Na clausura do, nós chamamos a função, usando a palavra palavra-chave try. Na instrução catch, nós tratamos os erros que forem lançados ao executar a função. Este é um padrão que será muito comum em Swift 2.

1
do {
2
    let data = try NSData(contentsOfURL: URL, options: [])
3
} catch {
4
    print("\(error)")
5
}

Na clausura catch, você terá acesso ao erro que foi lançado através da constante local error. A clausura catch é muito mais poderosa do que foi mostrado no exemplo acima. Vamos dar uma olhada em um exemplo mais interessante um pouco mais tarde.

3. Lançando Erros

Em Objective-C, você normalmente usa NSError, definido no framework Foundation, para tratar erro. Como a linguagem não define como o tratamento de erro deve ser implementada, você está livre para definir sua própria classe ou estrutura para criação de erros.

Isto não funciona em Swift. Embora qualquer classe ou estrutura possa atuar como um erro, elas precisam estar em conformidade com o protocolo ErrorType. O protocolo, entretanto, é muito fácil de implementar já que ele não declara nenhum método ou propriedade.

Enumerações são poderosas em Swift e elas são uma boa adaptação para o tratamento de erro. Enums são ótimos para o padrão de funcionalidades correspondentes da cláusula catch da instrução do-catch. É fácil de ilustrar isto com um exemplo. Vamos começar definindo um enum que esteja em conformidade com o protocolo ErrorType.

1
enum PrinterError: ErrorType {
2
    case NoToner
3
    case NoPaper
4
    case NotResponding
5
    case MaintenanceRequired
6
}

Nós definimos um enum, PrinterError, que está em conformidade com o protocolo ErrorType. O enum tem quatro variáveis membro. Agora nós podemos definir uma função para imprimir um documento. Nós passamos à função uma instância NSData e falamos ao compilador que ele pode lançar erros usando a palavra-chave throws

1
func printDocumentWithData(data: NSData) throws {
2
    ...
3
}

Para imprimir um documento, nós chamamos printDocumentWithData(_:). Como nós vimos anteriormente, nós precisamos usar a palavra-chave try e envolver a função chamada em uma instrução do-catch. No exemplo abaixo, nós tratamos os erros na cláusula catch.

1
do {
2
    try printDocumentWithData(data)
3
} catch {
4
    // Handle Errors

5
}

Podemos melhorar o exemplo inspecionando o erro lançado. Uma cláusula catch é similar a uma instrução switch em que permite verificar correspondência de valor. De uma olhada no exemplo atualizado abaixo.

1
do {
2
    try printDocumentWithData(data)
3
    
4
} catch PrinterError.NoToner {
5
    // Notify User

6
    
7
} catch PrinterError.NoPaper {
8
    // Notify User

9
    
10
} catch PrinterError.NotResponding {
11
    // Schedule New Attempt

12
    
13
}

Assim fica muito melhor. Mas temos um problema. O compilador nós notificará que nós não estamos tratando todos os erros possíveis que o método printDocumentWithData(_:) poderá lançar.

Error Handling Needs to Be ExhaustiveError Handling Needs to Be ExhaustiveError Handling Needs to Be Exhaustive

E o compilador tem razão. Uma clausura catch é similar a uma instrução switch que precisa ser exaustiva, ela precisa tratar todos os casos possíveis. Nós podemos adicionar outra clausura catch, PrinterError.MaintenanceRequired, ou nós podemos adicionar um clausura de captura geral no final. Adicionando uma clausura catch padrão, o erro de compilação irá desaparecer.

1
do {
2
    try printDocumentWithData(data)
3
    
4
} catch PrinterError.NoToner {
5
    // Notify User

6
    
7
} catch PrinterError.NoPaper {
8
    // Notify User

9
    
10
} catch PrinterError.NotResponding {
11
    // Schedule New Attempt

12
    
13
} catch {
14
    // Handle Any Other Errors

15
    
16
}

4. Limpando antes de ir

Quanto mais eu aprendo sobre a linguagem Swift, mais eu aprecio ela. A instrução defer é outra adição maravilhosa para a linguagem. O nome resume muito bem, mas deixe-me mostrar um exemplo para explicar o conceito.

1
func printDocumentWithData(data: NSData) throws {
2
    if canPrintData(data) {
3
        powerOnPrinter()
4
        
5
        try printData(data)
6
        
7
        defer {
8
            powerOffPrinter()
9
        }
10
    }
11
}

O exemplo é um pouco forçado, mas ele ilustra o uso do defer. O bloco da instrução defer é executado antes da execução sair do escopo no qual a instrução defer aparece. Você talvez queira ler essa frase novamente.

Isso significa que a função powerOffPrinter() é chamada mesmo se a função printData(_:) lançar um erro. Eu tenho certeza que você pode ver que isto realmente funciona muito bem com o tratamento de erro do Swift.

A posição da instrução defer dentro da instrução if não é importante. O exemplo atualizado abaixo é idêntico no que diz respeito ao compilador.

1
func printDocumentWithData(data: NSData) throws {
2
    if canPrintData(data) {
3
        defer {
4
            powerOffPrinter()
5
        }
6
        
7
        powerOnPrinter()
8
        
9
        try printData(data)
10
    }
11
}

Você pode ter múltiplas declarações defer, contanto que você lembre-se que elas seram executadas na ordem inversa em que aparecem.

5. Propagação

É possível que você não queira tratar um erro, mas sim deixá-lo borbulhar até um objeto que é capaz de ou responsável pelo tratamento o erro. Tudo bem. Nem sempre precisamos da expressão try para ser empacotado em uma instrução do-catch. Há uma condição, porém, a função que chama a função de lançamento precisa ser uma função de lançamento de si. De uma olhada nos dois exemplos a seguir.

1
func printTestDocument() {
2
    // Load Document Data

3
    let dataForDocument = NSData(contentsOfFile: "pathtodocument")
4
    
5
    if let data = dataForDocument {
6
        try printDocumentWithData(data)
7
    }
8
}
1
func printTestDocument() throws {
2
    // Load Document Data

3
    let dataForDocument = NSData(contentsOfFile: "pathtodocument")
4
    
5
    if let data = dataForDocument {
6
        try printDocumentWithData(data)
7
    }
8
}

O primeiro exemplo resulta em um erro de compilação, pois nós não tratamos os erros que a printDocumentWithData(_:) pode lançar. Nós resolvemos este problema no segundo exemplo tornando a função printTestDocument() uma função de lançamento. Se a printDocumentWithData(_:) lançar um erro, então o erro passará a chamar a função printTestDocument().

6. Ignorando o tratamento de erro

No inicio deste artigo, eu escrevi que o Swift quer que você abrace o tratamento de erro tornando isto fácil e intuitiva. Pode haver momentos em que você não quer ou não precise lidar com os erros que são lançados. Você decide parar a propagação de erros. Isto é possível usando a variante da palavra-chave try, try!.

Em Swift, uma marca de exclamação serve sempre como um aviso. Uma exclamação basicamente informa ao desenvolvedor que o Swift não é responsável se algo der errado. E é isso que a palavra-chave try! diz à você Se você iniciar a chamada de uma função de lançamento com a palavra-chave try!, também conhecida como uma expressão de tentativa-forçada, a propagação de erro será desabilitada.

Apesar de isso soar de forma fantástica para alguns de vocês, eu devo adverti-lo que isso não é o que você pensa ser. Se uma função de lançamento lançar um erro e você tiver desabilitado a propagação de erro, então você terá um erro em tempo de execução. Isto significa que seu aplicativo irá travar. Você foi avisado.

7.  APIs Objective-C

A equipe do Swift da Apple colocou muito esforço em tornar o tratamento de erro o mais transparente possível para APIs Objective-C. Por exemplo, você percebeu que o primeiro exemplo Swift deste tutorial é uma API Objective-C. Apesar da API ser escrita em Objective-C, o método não aceita um ponteiro NSError como seu último argumento. Para o compilador, ele é um método de lançamento normal. Esta é a definição do método em Objective-C.

1
- (NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error;

E isto é como a definição do método fica em Swift.

1
public func executeFetchRequest(request: NSFetchRequest) throws -> [AnyObject]

Os erros que executeFetchRequest(request:NSFetchRequest) lança são instâncias NSError. Isto só é possível, porque NSError está em conformidade com o protocolo ErrorType como discutido anteriormente. De uma olhada na coluna Conforms To abaixo.

NSError Class ReferenceNSError Class ReferenceNSError Class Reference

Aprenda mais em nosso Curso de Programação Swift 2

O Swift 2 tem varias novas funções e possibilidades. Faça nosso curso de desenvolvimento em Swift 2 para aprender com mais velocidade. A manipulação de erro é apenas uma pequena fatia dessas possibilidades do Swift 2.

Conclusão

A mensagem deste artigo é que o tratamento de erro é poderoso em Swift. Se você prestou atenção, então você também pegou que será necessário adotar o tratamento de erros, se você optar por desenvolver em Swift. Usar a palavra-chave try!, não vai evitar de fazer o tratamento de erro. É o oposto, usá-lo muitas vezes vai te trazer problemas. Experimente e tenho certeza que você vai adorar, ja que você gastou algum tempo com ele.

Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.