Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Swift

Swift from Scratch: Inicialização e inicializador por delegação

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Swift From Scratch.
Swift From Scratch: Access Control and Property Observers

Portuguese (Português) translation by David Batista (you can also view the original English article)

Nos capítulos anteriores de Swift from Scratch, criamos um aplicativo gerenciador de tarefas funcional. Entretanto, o modelo de dados poderia se um pouco mais bem estuturado. Neste tutorial, vamos refactorar nosso modelo de dados implementado uma classe modal personalisada. 

1. Modelo de Dados

O modelo de dados que vamos implementar inclui duas classes, uma classe Task e uma classe ToDo que herda da classe Task. Enquanto criamos e implementamos as classes modelo, vamos continuar nossa exploração em programação orientada a objetos em Swift. Neste tutorial, vamos verificar a inicialização de instâncias de classes e qual o papel a herança desempenha durante a inicialização.

Classe Task

Vamos começar com a implementação da classe Task. Criar um novo arquivo Swift selecionando New > File... no menu File do Xcode. Escolher Swift File na seção iOS > Source. Nomear o arquivo de Task.swift e clicar Create.

A implementação básica é curta e simples. A classe Task herda de NSObject, definido no framework Foundation, e tem a propriedade variável name do tipo String. A classe define dois inicializadores, init e init(name:). Existem alguns detalhes que podem confundi-los, então deixem-me explicar o que está acontecendo.

Como o método init também é definido na classe NSObject, precisamos usar o prefixo override no inicializador. Trabalhamos com substituição (overriding) de métodos mais cedo nesta serie. No método init, chamamos o método init(name:), passando "New Task" como valor para o parâmetro name.

O método init(name:) é outro inicializador, que aceita um único parâmetro name do tipo String. Neste inicializador, o valor do parâmetro name é atribuído para propriedade name. Isto é fácil de entender. Certo?

Inicializadores Designados e de Conveniência

O que é aquela palavra-chave convenience prefixando o método init? Classes podem ter dois tipos de inicializadores, inicializadores designados e inicializadores de conveniência. Inicializadores de conveniencia são prefixados com a palavra-chave conveniente, o que indica que init(name:) é um inicializador designado. O que é isso? Qual é a diferença entre inicializador designado e inicializador de conveniência?

Inicializadores designados inicializam totalmente uma instância de uma classe, significa que todas as propriedades da instância tem um valor após a inicialização. Olhando para a classe Task, como exemplo, vimos que a propriedade name é definida com o valor do parâmetro name do inicializador init(name:). O resultado após a inicialização é uma instância Task totalmente inicializada.

Inicializadores de conveniência, entretanto, conta com um inicializador designado para criar uma instanciada totalmente inicializada de uma classe. É por isso que o inicializador init da classe Task chama o inicializador init(name:) em sua implementação. Isto é referido como delegação de inicializador. O inicializador init delaga a inicialização para o inicializador designado para criar uma instância da classe Task totalmente inicializada.

Inicializadores de conveniência são opcionais. Nem todas as classes tem um inicializador de conveniência. Inicializadores designados são obrigatórios e uma classe precisa ter ao menos um inicializador designado para criar uma instância completa de si.

Protocolo NSCoding

A implementação da classe Task ainda não está completa. Mais a frente neste artigo, vamos escrever um array da instâncias de ToDo para armazena-lo. Isto é possível apenas se a instância da classe ToDo puder ser codificada e decodificada.

Mas não se preocupe, isto não é coisa da NSA. Precisamos apenas criar as classes Task e ToDo conforme o protocolo NSCoding. É por isso que a classe Task herda da classe NSObject, pois o protocolo NSCoding só pode ser implementado por classes herdando — diretamente ou indiretamente — do NSObject. Como a classe NSObject, o protocolo NSCoding é definido no framework Foundation.

Adotar um protocolo é algo que nós já discutimos nesta série, mas existem alguns macetes que eu quero ressaltar. Vamos começar informando ao compilador que a classe Task está em conformidade com o protocolo NSCoding.

Em seguida, vamos implementar os dois métodos declarados no protocolo NSCoding, init(code:) e encodeWithCoder(_:). A implementação é simples se você está familiarizado com o protocolo NSCoding.

O inicializador init(coder:) é um inicializador designado que inicializa uma instância Task. Embora implementamos o método init(coder:) em conformidade com o protocolo NSCoding, você não precisa chamar o método diretamente. O mesmo serve para encodeWithCoder(_:), que codifica uma instância da classe Task.

A palavra-chave required prefixando o método init(coder:) indica que toda subclasse da classe Task precisa implementar este método. A palavra-chave required apenas se aplica aos inicializadores, por isso não precisamos adiciona-la ao método encodeWithCoder(_:).

Antes de seguir em frente, precisamos falar sobre o atributo @objc. Como o protocolo NSCoding é um protocolo Objective-C, a conformidade do protocolo só pode ser verificada adicionando o atributo @objc. Em Swift, não existe essas coisas de conformidade de protocolo ou métodos opcional de protocolo. Em outras palavras, se a classe adiciona um determinado protocolo, o compilador verifica e espera que todos os métodos do protocolo sejam implementados.

Classe ToDo

Com a classe Task implementada, é hora de implementar a classe ToDo. Criar um novo arquivo Swift com o nome ToDo.swift. Vamos ver como é a implementação da classe ToDo.

A classe ToDo herda da classe Task e declara uma propriedade variável done do tipo Bool. Além dos dois métodos exigidos do protocolo de NSCoding, que herda da classe de Task, ele também declara um inicializador designado, init(name:done:).

Como em Objective-C, a palavra chave super refere-se a superclasse, a classe Task no exemplo. Este é um detalhe importante que merece atenção. Antes de você chamar o método init(name:) na superclasse, cada propriedade declarada pela classe ToDo precisa ser inicializado. Em outras palavras, antes da classe ToDo delegar a inicialização para sua superclasse, cada propriedade declarada da classe ToDo, precisa ter um valor inicial válido. Você pode verificar isso trocando a order da instrução e verificar o erro que aparece.

O mesmo se aplica ao método init(coder:). Primeiro vamos inicializar a propriedade done antes de chamar init(coder:) na superclasse. Note também o downcast e o desempacotamento forçado do resultado de decodeObjectForKey(_:) para um Bool usando as!.

Inicializadores e Herança

Quando lidamos com herança e inicialização, há algumas regras que devemos ter em mente.  A regra para inicializadores designados é simples.

  • Um inicializador designado precise chamar um inicializador designado de sua superclasse. Na classe ToDo, por exemplo, o método init(coder:) chama o método init(coder:) de sua superclasse. Isto também é referido como delegating up.

As regras para inicializadores por conveniência são um pouco mais complexas. Há duas regras para se ter em mente.

  • Um inicializador por conveniência sempre precisa chamar outro inicializador que é definido na própria classe. Na classe Task, por exemplo, o método init é um inicializador por conveniência e delega a inicialização para outro inicializador, init(name:) por exemplo. Isso é conhecido como delegating across.
  • Mesmo que um inicializador de conveniência não precise delegar a inicialização a um inicializador designado, um inicializador de conveniência precisa chamar um inicializador designado em algum momento. Isto é necessário para inicializar completamente a instância que está sendo inicializada.

Com ambos os modelos de classes implementados, é hora de retaforar as classes ViewController e AddItemViewController. Vamos começar com o último.

2. Refatoração da AddItemViewController

Passo 1: Atualizando o Protocolo AddItemViewControllerDelegate

A unica alteração que precisamos fazer na classe AddItemViewController é relacionado ao protocolo AddItemViewControllerDelegate. Na declaração do protocolo, mudaremos o tipo do didAddItem de String para ToDo, a classe modelo que implementamos anteriormente.

Passo 1: Atualizar a Ação create

Isto significa que nós também precisamos atualizar a ação create em que chamamos o método delegate. Na atualização da implementarção, criamos uma instância ToDo, passando ela ao método delegate.

3. Refatoração da ViewController

Passo 1: Atualizando Propriedade Items

A classe ViewController requer um pouco mais de trabalho. Primeiro precisamos alterar o tipo da propriedade items para [ToDo], um array de instâncias ToDo.

Passo 2: Métodos do Table View Data Source

Isto também significa que é preciso refatorar alguns outros métodos, tais como o método cellForRowAtIndexPath(_:) mostrado abaixo. Como a array items agora contém instâncias de ToDo, verificar se um item foi marcado como finalizado é muito mais simples. Nós usamos o operador condicional ternário do Swift para atualizar o tipo de acessório da table view cell.

Quando o usuário deletar um item, precisamos apenas atualizar a propriedade items removendo um correspondente da instância ToDo. Isso reflete na implementação do método tableView(_:commitEditingStyle:forRowAtIndexPath:) mostrado a seguir.

Passo 3: Métodos do Table View Delegate

A atualização do estado de um item quando o usuário seleciona uma linha é manipulada no método tableView(_:didSelectRowAtIndexPath:). A implementação deste método UITableViewDelegate ficou muito simples, graças a classe ToDo.

A instância correspondente do ToDo é atualizada e essa alteração é refletida pelo table view. Para salvar o estado, chamaremos saveItems ao invés do saveCheckeItems.

Passo 4: Métodos da Add Item View Controller Delegate

Já que atualizamos o protocolo AddItemViewControllerDelegate, também precisamos atualizar a implementação da ViewController deste método.  A alteração, entretanto, é simples. Precisamos apenas atualizar a assinatura do método.

Passo 5: Salvando Itens

pathForItems

Em vez de armazenar os itens no banco de dados padrão do usuário, nós vamos armazená-los no diretório de documentos do aplicativo. Antes de atualizar os métodos loadItems e saveItems, vamos implementar um método auxiliar chamado pathForItems. O método é privado e retorna um caminho, a localização do item no diretório de documentos.

Nós primeiro buscaremos o caminho para o diretório de documentos na área de segurança do aplicativo chamando NSSearchPathForDirectoriesInDomains(_:_:_:). Como este método retorna um array de strings, vamos pegar o primeiro item, forçar o desempacotamento, e fazer o downcast para um String. O valor retornado do pathForItems é composto do caminho para o diretório de documentos com a string "items" anexada a ele.

loadItems

O método loadItems muda um pouco. Primeiro armazenamos o resultado do pathForItems em uma constante chamada path. Então, desarquivamos o objeto arquivado no caminho e fazemos o downcast para um array opcional da instância ToDo. Usamos optional binding para desempacotar o opcional e atribui-lo à contante chamada items. Na clausula if, atribuímos o valor armazenado em items para a propriedade items.

saveItems

O método saveItems é pequeno e simples. Armazenamos o resultado da pathForItems em uma constante chamava path e chamamos archiveRootObject(_:toFile:) em NSKeyedArchiver, passando a propriedade items e path. Imprimimos o resultado da operação no console.

Passo 6: Limpando

Vamos finalizar com a parte divertida, deletando código. Começaremos removendo a propriedade checkedItems no topo, já que não precisamos mais dela. Como resultado, podemos remover também os métodos loadCheckedItems e saveCheckedItems, e qualquer referência a esses métodos na classe ViewController.

Compile e execute o aplicativo para ver se ainda está tudo funcionando. O modelo de dados torna o código do aplicativo muito mais simples e confiável. Graças a classe ToDo, gerenciar os itens em nossa lista é muito mais fácil agora e menos propenso a erros.

Conclusão

Neste tutorial, refatoramos o modelo de dados do nosso aplicativo. Aprendemos um pouco mais sobre programação orientada a objetos e herança. Inicialização de instância é um conceito importante em Swift, por isso certifique-se de entender o que foi explicado neste tutorial. Você pode ler sobre inicialização e delegação de inicializador em The Swift Programming Language.

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

Advertisement
Advertisement
Advertisement
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.