Olá, amigos! Esta é a terceira lição do curso de criação de apps para iPhone com Swift. Na verdade, não é só para iPhone, mas para qualquer dispositivo iOS. E a linguagem Swift pode ser usada para criar apps para computadores Mac também.
Acompanhe o material do curso e veja o resto das lições!
Fala sério! Aprender a programar é um saco! Você tem que ficar criando projetos falsos, criar arquivos, codificar, compilar, só para descobrir como funciona um comando, ou para testar uma ideia. Aposto que seu computador é cheio de projetos “teste-alguma-coisa”.
O Xcode, na versão 6, criou o conceito de “Playground”, uma área na qual você pode digitar comandos e ver o resultado imediatamente, sem ter que montar projeto ou mesmo compilar.
Cara, eu sei que este capítulo é grande e, talvez, demore mais de 4 horas para estudar. Mas eu recomendo que você o estude de uma só vez, e tente fazer pelo menos 2 exercícios.
Crie seu playground
Inicie o Xcode e clique em “Get started with a playground”, na Janela de “Welcome” do Xcode, ou então, abra o menu “File / New / Playground...”. Dê um nome ao seu Playground e selecione a plataforma (IOS ou OSX), e selecione onde salvar. Você verá uma janela como essa:
O que temos aqui? Uma tela de editor, só que, ao digitarmos um comando, ele é executado imediatamente, e o resultado aparece na parte direita. Por exemplo, ao lado do comando: “var str = "Hello, playground"”, vemos a frase “Hello, playground" aparecendo!
Vamos lá... Digite isso:
let x = 3 * 3
Você verá o resultado 9 (nove) aparecer na parte direita.
Com um Playground é assim: Você testa o comando e vê o resultado imediatamente, sem delongas.
O seu Playground é salvo como um diretório, cujo conteúdo é:
enterprise:PrimeiroPlayground.playground cleutonsampaio$ ls -la
total 24
drwxr-xr-x 5 cleutonsampaio staff 170 15 Mar 14:25 .
drwxr-xr-x 11 cleutonsampaio staff 374 15 Mar 14:25 ..
-rw-r--r-- 1 cleutonsampaio staff 247 15 Mar 14:25 contents.xcplayground
-rw-r--r-- 1 cleutonsampaio staff 114 15 Mar 14:25 section-1.swift
-rw-r--r-- 1 cleutonsampaio staff 120 15 Mar 14:25 timeline.xctimeline
O arquivo “section-1.swift” contém todo o código que você digitou no Playground.
Aprendendo a linguagem Swift
Antes de mais nada, vamos alinhar as expectativas. Não dá para aprender tudo sobre uma linguagem complexa como a Swift, em um só livro. Vamos ver o essencial.
Bom, vamos lá. A linguagem Swift é uma linguagem de programação moderna, que combina muitos elementos de Javascript, C e C++. Ao contrário da linguagem Java, o Swift não usa Garbage Collector, mas usa um mecanismo conhecido como ARC - Automatic Reference Counting, para desalocar memória ocupada por objetos.
Ao contrário de Javascript, Swift é uma linguagem fortemente tipada, ou seja, ao declarar uma variável de um determinado tipo, você não pode atribuir valores de tipos diferentes. Digite a seguintes linhas:
var a = 10
a = "Teste"
A segunda linha apresentará um erro:
Como a variável “a” foi definida como um inteiro (Int), você não pode atribuir um texto (String) a ela.
Em Swift, podemos declarar:
- Variáveis;
- Funções;
- Classes;
- Estruturas.
Por exemplo, digite isso no seu Playground e observe os valores na coluna direita:
func ePar(valor: Int) -> Bool {
if (valor % 2 == 0) {
return true;
}
return false;
}
ePar(2)
ePar(3)
var x = 10
ePar(x)
Os comentários e identificadores são iguais aos usados em C. Só que em Swift, não terminamos as linhas com ponto-e-vírgula, como no C, C++ ou Java.
Não existem aquelas restrições sem sentido da linguagem Java.
Declaração de variáveis, constantes e atribuição
Em Swift, podemos declarar variáveis e constantes. Variáveis são declaradas com o comando “var”, como no Javascript, e constantes com o comando “let”:
- var fx = 10
- let constante1 = 5
Para saber o tipo de dados de uma variável, podemos usar a função: “_stdlib_getDemangledTypeName()”:
let const1 = 10
_stdlib_getDemangledTypeName(const1)
→ Resultado: “Swift.Int”
var v10 = 5.3
_stdlib_getDemangledTypeName(v10)
→ Resultado: “Swift.Double”
var binario = true
_stdlib_getDemangledTypeName(binario)
→ Resultado: “Swift.Bool”
Ao declarar uma variável ou constante, podemos também informar o tipo de dados explicitamente:
- var preco : Float = 15.50
- var codigo : Int = 1029
- var nome : String = "Serrote"
- var disponivel : Bool = true
Em Swift, temos tipos de dados “named” (nomeados), como: Classes, Int, Double Bool e “compound” (compostos), como tuplas e funções.
Como os tipos básicos (Int, Float, Double, Bool e String) são tipos nomeados, eles possuem métodos. Por exemplo:
- let preco : Float = 50.00
- let precoTexto : String = preco.description
O segundo comando cria uma variável String que contém: “50.00”, ou seja, o valor real convertido em texto.
Como já vimos, o comando para mudar o conteúdo de uma variável é a atribuição (“=”).
Expressões aritméticas
Digite cada um dos elementos abaixo no seu playground:
var valor : Double = 0.0
Operação | Exemplo | Resultado |
Soma | valor = valor + 1 | 1.0 |
Subtração | valor = valor - 1 | 0.0 |
Multiplicação | valor = 10 * 5 | 50.0 |
Divisão | valor = valor / 5 | 10.0 |
Resto de uma divisão | valor = valor % 2 | 0 |
Também podemos usar operadores aritiméticos compostos:
Operação | Sinônimo de | Resultado |
valor = 10 | valor = 10 | 10 |
valor += 1 | valor = valor + 1 | 11 |
valor *= 2 | valor = valor * 2 | 22 |
valor -= 12 | valor = valor - 12 | 10 |
valor /= 2 | valor = valor / 2 | 5 |
E os operadores unários “--” e “++” também funcionam, e com a mesma semântica. Antes da variável, incrementam o valor ANTES de usá-lo. Depois da variável, incrementam o valor DEPOIS de usá-lo. Exemplo:
valor = 5
→ Resultado: valor recebe 5
var valor2 = valor++
→ Resultado: valor2 recebe 5 e valor fica com 6
var valor3 = ++valor
→ Resultado: valor3 recebe 7 e valor fica com 7
Funções matemáticas
Podemos usar funções matemáticas diretamente:
Função | Exemplo | Resultado |
Potenciação (Quadrado de 9) | valor = pow(9,2) | 81.0 |
Raiz quadrada (de 81) | valor = sqrt(81) | 9 |
Seno (de 30 = 0.5) | // Swift só trabalha com radianos var trintagraus = 30 * 0.0174532925 valor = sin(trintagraus) | 0.4999999 |
Coseno (de 60 = 0,5) | var sessentagraus = 60 * 0.0174532925 valor = cos(sessentagraus) | 0.500000 |
Tangente (de 45 = 1) | var qarentaecinco = 45 * 0.0174532925 valor = tan(qarentaecinco) | 0.999999 |
O valor de PI | valor = M_PI | 3.1415926 |
Basicamente, você tem todas as funções de Math.h do sistema operacional Darwin, que é a base do iOS. Para saber a lista: https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/math.3.html
Notou que os valores estão carecendo de arredondamento? Podemos arredondar em 1 casa decimal assim:
var sessentagraus = 60 * 0.0174532925
var arrcos = round(10 * cos(sessentagraus)) / 10
→ Resulado: 0,5
Nós usamos a função “round()”, do Math.h, que arredonda o valor para um inteiro. Ao multiplicar por 10, preservamos uma casa decimal. Depois, dividimos tudo por 10. Se quisermos arredondar com duas casas, usamos o valor 100, com 3, usamos o valor 1000.
Conversão e “cast” de tipos de dados
Existem situações em que queremos converter um valor de um tipo de dados para outro. Podemos fazer isso com Swift, dependendo do tipo de dados:Conversões numéricas
Basta fazer um “cast” do valor para o tipo desejado. Por exemplo:
- Double para Int:
var valor : Double = 570.50
var vi : Int = Int(valor)
- Float para Int:
var valor : Float = 570.50
var vi : Int = Int(valor)
- Int para Double:
var vi : Int = 570
var valor : Double = Double(vi)
- Int para Float:
var vi : Int = 570
var valor : Float = Float(vi)
- Double para Float:
var valor : Double = 570.05
var vi : Float = Float(valor)
- Float para Double:
var valor : Float = 570.05
var vi : Double = Double(valor)
Conversão para String:
Podemos converter números para String facilmente:
var valor : Float = 570.05
var texto1 = "\(valor)"
var texto2 = String(format: "%0.2f", valor)
Na variável “texto1” usamos um “atalho” do Swift, para inserir um valor numérico em um String (“\(valor)”). Na variável “texto2”, usamos o construtor de String, passando um formato numérico:
%0,2f
Especificamos que queremos um valor de ponto flutuante, com 2 casas decimais.
E para converter inteiros é basicamente a mesma coisa:
var valint = 566
var texto3 = "\(valint)"
var texto4 = String(valint)
Vetores e dicionários
Podemos declarar vetores de maneira bem simples. Digite essas duas linhas no Playground:
var vetor1 : [Int];
vetor1
A segunda linha apresentará um erro, o que significa que você tentou usar um vetor antes de inicializá-lo. Podemos consertar isso alterando um pouco a primeira linha:
var vetor1 : [Int] = [10,5,3,7,12,-1,4]
Note que podemos ver o conteúdo do vetor em detalhes, bastando clicar no ícone de olho, na coluna direita:
A declaração de um vetor segue os mesmos princípios que a declaração de variáveis, apenas temos que “embrulhar” o tipo de dados com colchetes.
Acessando elementos e modificando um vetor
Podemos acessar elementos de maneira semelhante às outras linguagens. Vamos trocar a posição dos dois primeiros elementos de um vetor:
var vetor1 : [Int] = [10,5,3,7,12,-1,4]
var x = vetor1[0]
vetor1[0] = vetor1[1]
vetor1[1] = x
vetor1
Após a última linha, você notará na barra direita que as posições dos dois primeiros elementos foram trocadas.
Agora, vamos fazer uma coisa legal: Adicionar um novo elemento ao vetor. Isso pode ser feito de duas maneiras diferentes:
- vetor1 += [5]
- vetor1.append(5)
Só que a primeira versão é mais flexível. Teste essas linhas:
var frutas = ["banana", "laranja", "pêra"]
println(frutas)
frutas += ["maçã", "uva"]
println(frutas)
Após o último “println”, você verá isso:
[banana, laranja, pêra, maçã, uva]
E podemos intercalar itens! Por exemplo, vamos inserir “melancia” após “laranja”:
frutas.insert("melancia", atIndex: 2)
println(frutas)
Agora, o elemento de índice 2 (o terceiro elemento) é “melancia”.
Vamos ver outras propriedades de um vetor:
- Contagem de elementos: “.count”;
- Índice do próximo elemento a ser acrescentado: “.endIndex”;
Assim como podemos acrescentar elementos, também podemos removê-los. Por exemplo, vamos remover a “melancia”:
println(frutas)
frutas.removeAtIndex(2)
println(frutas)
E podemos remover o último com: “removeLast”:
frutas.removeLast()
println(frutas)
Como pode ver, é fácil implementar uma pilha. Toda estrutura de pilha tem os métodos:
- “push()” : Empurra um novo elemento para o topo da pilha;
- “pop()” : Retira um elemento do topo da pilha;
- “peek()” : Retorna o elemento do topo da pilha, sem retirá-lo dela.
Podemos simular uma pilha usando os métodos: “append()”, “removeLast()” e “last()” de um vetor:
var pilha : [String] = [];
// Empilhando elementos
pilha.append("primeiro")
pilha.append("segundo")
//Vendo qual é o elemento do topo:
println(pilha.last)
//Desempilhando elementos
var elemento = pilha.removeLast()
println(pilha)
println(elemento)
Iterando sobre um vetor
Iterar é percorrer todos os elementos de um vetor. Podemos fazer isso com o comando “for”. Primeiramente, vamos ver a iteração mais simples, na qual não precisamos saber a posição do elemento, apenas o seu valor:
println(frutas)
for fruta in frutas {
println(fruta)
}
Após rodar esse “for-in”, você verá escrito “(4 times)”, na barra direita. Se quisermos ver o resultado, podemos clicar no ícone “Value history”, que fica ao lado do “olho”, na barra direita:
Então, veremos um novo painel surgir na direita, e ele tem os resultados das iterações, no painel “println(fruta)”:
Agora, como saber o índice dos elementos, enquanto iteramos um vetor? Podemos usar outra variante do “for”:
for (posicao, conteudo) in enumerate(frutas) {
println("Fruta: \(conteudo) na posição: \(posicao)")
}
A função global “enumerate()” retorna um valor do tipo “tupla” (ainda não falamos sobre ele), para cada elemento de um vetor. Essa tupla tem o índice e o valor do elemento. Então, usamos a sintaxe de formatação de string para mostrar isso para cada elemento. Eis o resultado no “Value history”:
Fruta: banana na posição: 0
Fruta: laranja na posição: 1
Fruta: pêra na posição: 2
Fruta: maçã na posição: 3
Ok. Hora de um exercício
Sem tentar “colar” a resposta (que está mais adiante), faça um pequeno código que inverta as palavras de um texto fornecido. Por exemplo:
“minha terra tem palmeiras onde canta o sabiá”
→ “sabiá o canta onde palmeiras tem terra minha”
Mas, como vamos dividir o texto original em palavras? Todo String tem o método “componentsSeparatedByString()”, que retorna um vetor com as palavras. Por exemplo:
var texto = "Minha terra tem palmeiras onde canta o sabiá"
var palavras = texto.componentsSeparatedByString(" ")
E como vamos pegar do último para o primeiro? Podemos navegar ao “contrário” em um vetor! É só usar a função “reverse”:
for x in reverse(vetor) ...
A resposta está no final desse capítulo.
Dicionários
Um Dicionário é uma coleção de elementos no formato: (chave, valor). Podemos criar dicionários de maneira bem simples, especificando o tipo de dados da chave e do valor:
var capitais : [String : String] = ["Brasil" : "Brasília", "França" : "Paris",
"EUA" : "Washington DC"]
println(capitais["Brasil"]!)
É como um vetor associativo. Definimos que o tipo de dados da chave será String (o primeiro tipo), e o do valor será também String. Então, podemos inicializar o dicionário com a sintaxe: <chave> : <valor>.
Notou o ponto de exclamação no “println”? É uma expressão opcional. Tenha calma que já falaremos disso.
Podemos adicionar elementos de maneira muito simples:
capitais["Italia"] = "Roma"
println(capitais)
E podemos remover elementos também de maneira fácil:
capitais.removeValueForKey("França")
println(capitais)
Acessando caracteres em um String
Uma das últimas coisas que vamos ver é como acessar os caracteres individuais de um String, e depois, faremos mais um exercício.
Podemos iterar sobre os caracteres em um String, da mesma forma que iteramos sobre os elementos de um vetor. Veja dois exemplos:
var texto = "Minha terra tem palmeiras onde canta o sabiá"
for letra in texto {
println(letra)
}
for (posicao, letra) in enumerate(texto) {
println("Letra: \(letra) na posição: \(posicao)")
}
E podemos adicionar letras com o operador “+=”:
texto += “!”
Controle de fluxo
Para fazermos algo interessante com nosso código, é hora de ver como funciona o controle de fluxo em Swift. Temos os comandos:
- “if” / “else”;
- “switch”;
- “do” / “while”;
- “while”.
if
Você vai precisar conhecer um comando importante: “if”. Eis a sintaxe do comando:
if <argumento> <operador> <outro argumento> {
}
else {
}
Os operadores são iguais aos do Java e C:
- Igual: “==”;
- Diferente: “!=”;
- Maior, menor: “>” e “<”.
E temos conectores lógicos:
- Conjunção (E) : “&&”;
- Disjunção (OU) : “||”;
Exemplo de “if”:
if guardado == "(" ||
guardado == "+" ||
guardado == "-" {
break
}
switch
O “switch” tem semelhanças e diferenças para a linguagem Java:
switch <variável> {
case <valor 1> :
<comandos>
case <valor 2>:
<outros comandos>
default:
<outros comandos>
}
Diferentemente do Java e C, o “switch” não entra por dentro das outras opções, logo, não é necessário usar o “break” em cada “case”. Devido a essa característica, você é obrigado a informar pelo menos um comando a cada “case”, senão, tomará erro. Quando existir a situação em que dois “cases” tem o mesmo comando, você tem que criar um “case” com dois valores. Por exemplo:
Java:
switch(x) {
case 1:
case 2:
System.out.println(“OK”);
break;
default:
System.out.println(“Erro”);
}
Swift:
switch x {
case 1,2:
println(“OK”);
default:
println(“Erro”);
}
Loop com execução obrigatória “do / while”
Se quisermos executar um grupo de comandos pelo menos uma única vez, podemos usar essa sintaxe:
var vx = 10;
do {
println(vx--);
} while vx > 0
Após a execução, a condição será avaliada e, se for verdadeira, o grupo será executado novamente.
O comando “break” interrompe a execução de um “loop” (“for” ou “while”).
Loop condicional com “while”
Se você quiser executar (e possivelmente repetir) um grupo de comandos dependendo de uma determinada condição, use o “while”:
var vx = 10;
while vx > 0 {
println(vx--)
}
Hora de um exercício!
A notação polonesa foi criada para auxiliar a resolução de expressões. Ela é utilizada até hoje nas calculadoras HP (Hewlet Packard).
Vamos analisar uma expressão aritmética, convertendo-a para notação polonesa. Por exemplo, suponha a expressão:
((3 + 5) * 4) / 2
A ordem para resolução é:
- Somar 3 e 5;
- Multiplicar o resultado por 4;
- Dividir o resultado por 2.
35+4*2/
Muito mais fácil de ser resolvida por um algoritmo. Como fazemos essa transformação? Usando pilha!
Eis um algoritmo bem simples:
Criamos um String para o resultado.
Criamos uma “pilha” intermediária.
Para cada caracter na expressão original:
- Se for um “(“:
- Empilhamos;
- Se for um “)”:
- Desempilhamos tudo o que estiver na pilha. Vamos do mais recente até encontrarmos um “(”, que também devem ser desempilhado, ou até a pilha ficar vazia. Todos os caracteres da pilha, exceto o “(”, deverão ser acrescentados ao resultado.
- Se for um “+” ou “-”:
- Se tiver alguma coisa na pilha, desempilhamos até encontrar um “(“ e acrescentamos ao resultado, com o caracter do sinal. Mantemos o “(“ na pilha;
- Se for um “*” ou “/”:
- Se tiver alguma coisa na pilha, desempilhamos até encontrarmos um dos três caracteres: “(“, ou “+” ou “-”, mantendo-os na pilha, e acrescentamos os caracteres desempilhados no resultado, acrescentando o caracter que acabamos de ler na experssão de origem.
- Caso contrário:
- Acrescentamos o caracter ao resultado.
Eis uma tabela de iterações:
((3 + 5) * 4) / 2
Caracter | Pilha | Resultado |
( | ( | |
( | (( | |
3 | (( | 3 |
+ | ((+ | 3 |
5 | ((+ | 35 |
) | ( | 35+ |
* | (* | 35+ |
4 | (* | 35+4 |
) | ( | 35+4* |
2 | ( | 35+4*2 |
/ | (/ | 35+4*2 |
) | 35+4*2/ |
E aí? Já sabe criar isso? Para simplificar, assuma que os números são inteiros e sempre menores que 10, ou seja, cada número é um algarismo de 0 a 9. Tente. Depois, confira no capítulo de respostas.
Variáveis opcionais e tuplas
O Swift tem algumas coisas bem legais, com as quais não estamos acostumados. Para começar, temos as variáveis opcionais.
Variáveis opcionais podem ou não conter algum valor. Já vimos um exemplo disso quando acrescentamos um ponto de exclamação nas conexões “outlet” de nossos exemplos.
Vamos ver um exemplo diferente. Apague seu Playground (ou crie outro), e digite:
var x : Int
x = "300".toInt()
Você verá um erro dizendo: “value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?”
Isso quer dizer que o resultado do método “toInt()” poderá retornar um inteiro ou não. Logo, é uma expressão que retorna um tipo “Int?” e não “Int”. Para evitar esse erro, você pode declarar a variável “x” como “Int?”:
var x : Int?
Ou então, se tiver certeza que o método “toInt()” retornou um valor, pode usar o ponto de exclamação. Podemos testar se o resultado da conversão foi válido, comparando-o com “nil” (nulo):
var x : Int
if "300".toInt() != nil {
x = "300".toInt()!
}
Usando o ponto de exclamação, você está dizendo: “Ok, eu tenho certeza que tem um valor, então, use logo”.
Tuplas
As vezes, queremos criar uma variável composta, o que é muito útil em certas funções. Por exemplo, vamos supor que queiramos retornar um erro em nossa função, e esse erro é composto por “código” e “mensagem”:
let erro = (100, "Arquivo não encontrado")
println(erro)
println(erro.0)
println(erro.1)
var codigo : (Int, String) = erro
println(codigo.0)
Podemos acessar os componentes de uma tupla através do seu índice (0,1,...). Mas podemos dar nomes a eles também. Por exemplo, vamos criar um novo tipo de tupla:
var funcionario : (matricula : Int, nome : String)
funcionario = (100, "Fulano de tal")
println(funcionario)
println(funcionario.nome)
println(funcionario.matricula)
Swift também tem um tipo chamado “structure”, que serve a um propósito semelhante.
Funções
Funções em Swift são um tipo de dados, assim como as tuplas, são tipos compostos. Uma função é um bloco de comandos que podemos executar repetidamente. Vamos ver um exemplo:
func calcular(valor: Double) -> Double {
return valor * valor
}
var numero = calcular(30)
O nome de uma função é dado pelo seu nome e pelos tipos de dados dos parâmetros. A “seta” indica o tipo de dados que a função retorna.
As funções podem ou não retornar valor. Uma função que nada retorna, não tem a “seta” (“->”) em sua declaração.
As funções podem ter mais de um parâmetro. Vamos criar uma função para retornar o valor do Delta de uma equação do segundo grau, segundo a fórmula de Bhaskara, e vamos invocá-la com os coeficientes da equação: x2 - 2x - 3:
Funções
Funções em Swift são um tipo de dados, assim como as tuplas, são tipos compostos. Uma função é um bloco de comandos que podemos executar repetidamente. Vamos ver um exemplo:
func calcular(valor: Double) -> Double {
return valor * valor
}
var numero = calcular(30)
O nome de uma função é dado pelo seu nome e pelos tipos de dados dos parâmetros. A “seta” indica o tipo de dados que a função retorna.
As funções podem ou não retornar valor. Uma função que nada retorna, não tem a “seta” (“->”) em sua declaração.
As funções podem ter mais de um parâmetro. Vamos criar uma função para retornar o valor do Delta de uma equação do segundo grau, segundo a fórmula de Bhaskara, e vamos invocá-la com os coeficientes da equação: x2 - 2x - 3:
func calcularDelta(a : Double, b : Double, c : Double) -> Double {
return pow(b,2) - 4 * a * c
}
println(calcularDelta(1, -2, -3))
Funções podem retornar tuplas. Vamos criar uma função que retorna as raízes de uma equação do segundo grau:
func calcularDelta(a : Double, b : Double, c : Double) -> Double {
return pow(b,2) - 4 * a * c
}
func raizes(a : Double, b : Double, c : Double) -> (delta : Double, x1 : Double, x2 : Double) {
var d = calcularDelta(a,b,c)
var resultado : (delta : Double, x1 : Double, x2 : Double) = (d,0,0)
if d > 0 {
resultado.x1 = ((-1) * b + sqrt(d)) / 2 * a
resultado.x2 = ((-1) * b - sqrt(d)) / 2 * a
}
else if d == 0 {
resultado.x1 = ((-1) * b) / 2 * a
resultado.x2 = resultado.x1
}
else {
resultado.x1 = Double.infinity
resultado.x2 = Double.infinity
}
return resultado
}
println(calcularDelta(1, -2, -3))
println(raizes(1,-2,-3))
→ Resultado: (16.0, 3.0, -1.0)
println(raizes(1,8,16))
→ Resultado: (0.0, -4.0, -4.0)
println(raizes(10,6,10))
→ Restulado: (-364.0, inf, inf)
Nossa função retorna um tipo de dados tupla: (delta : Double, x1 : Double, x2 : Double).
Parâmetros nomeados
Em certos casos, para aumentar a clareza do código, talvez você queira que o usuário de sua função saiba exatamente quais parâmetros está passando para ela. Neste caso, você pode forçar o uso de nomes externos de parâmetros. Por exemplo, vamos renomear nossa função original, que calcula raízes, para:
func raizes(CoeficienteA a : Double,
CoeficienteB b : Double,
CoeficienteC c : Double)
-> (delta : Double, x1 : Double, x2 : Double) {
println(raizes(CoeficienteA: 1,CoeficienteB: -2,CoeficienteC: -3))
println(raizes(CoeficienteA: 1,CoeficienteB: 8,CoeficienteC: 16))
println(raizes(CoeficienteA: 10,CoeficienteB: 6,CoeficienteC: 10))
func calcular(valor: Double, expoente exp: Double) -> Double {
return pow(valor, exp)
}
func calcular(valor: Double, radical r: Double) -> Double {
return pow(valor, 1 / r)
}
println(calcular(9, expoente: 2))
println(calcular(81, radical: 2))
Struct, Classes, Objetos e ARC
A linguagem Swift também nos permite criar classes, e, a partir dessas, instanciar objetos. E esses objetos são alocados dinamicamente na memória. Para evitar “memory leak”, o Swift usa um mecanismo chamado “Automatic Reference Counting” ou ARC.
Classes e estruturas
Em Swift, podemos criar elementos compostos usando tuplas, como já vimos. Porém, Classes e Estruturas nos permitem fazer mais coisas. Vamos ver um exemplo de cada:
class veiculo {
var ligado: Bool = false
var placa: String = ""
func ligar() {
self.ligado = true
}
func desligar() {
self.ligado = false
}
}
struct sveiculo {
var ligado: Bool = false
var placa: String = ""
mutating func ligar() {
self.ligado = true
}
mutating func desligar() {
self.ligado = false
}
}
var meucarro : veiculo = veiculo()
var segundo : sveiculo = sveiculo()
meucarro.ligar()
meucarro.ligado
segundo.ligar()
segundo.ligado
Uma Classe (“Class”) e uma Struct são muito semelhantes. Em ambas podemos criar propriedades e métodos, ambas podem possuir inicializadores (construtures) e podemos usar ambas da mesma forma.
Pelo exemplo acima, a única diferença notável é que os métodos de uma Struct, caso precisem modificar o valor de suas propriedades, precisam ser marcados com “mutating”.
E aí? Struct ou Class?
Ambas servem para criarmos novos tipos de dados, e ambas podem conter métodos e propriedades, mas, na minha opinião, Struct não vale a pena.
Struct é um “value type”, ou seja, quando você atribui uma instância de uma Struct a outra variável, todos os valores são copiados para ela. Class, por outro lado, é um “reference type”, o que significa que, ao atribuir uma instância de uma Class a uma variável, estamos criando uma nova referência para a mesma instância. Isso fica melhor com um exemplo:
class veiculo {
var ligado: Bool = false
var placa: String = ""
func ligar() {
self.ligado = true
}
func desligar() {
self.ligado = false
}
}
struct sveiculo {
var ligado: Bool = false
var placa: String = ""
mutating func ligar() {
self.ligado = true
}
mutating func desligar() {
self.ligado = false
}
}
//Primeiro bloco: Usando a Classe
var primeiroCarro : veiculo = veiculo()
var segundoCarro : veiculo = primeiroCarro
primeiroCarro.placa = "XPTO9999"
println(segundoCarro.placa)
→ Resultado: “XPTO9999” o valor da mesma instância.
//Segundo bloco: Usando a Struct
var terceiroCarro : sveiculo = sveiculo()
var quartoCarro : sveiculo = terceiroCarro
terceiroCarro.placa = "ABCD1234"
println(quartoCarro.placa)
→ Resultado: “”, o valor da nova instância.
No primeiro bloco de atribuições, criamos uma instância da classe “veiculo”, e criamos uma segunda referência para ela, colocada na variável “segundoCarro”. Na verdade, as duas variáveis apontam para a mesma instância de “veiculo”, o que fica comprovado ao alterar uma propriedade em uma referência, ver que o valor foi alterado na outra.
No segundo bloco, vemos que, ao atribuir a instância da Struct a outra variável (“quartoCarro”), na verdade criamos uma cópia, ou seja, uma nova instância da Struct.
A recomendação oficial é usar Struct quando você quiser sempre copiar instâncias e valores, ao invés de criar referências. Outra recomendação é que Struct não usa herança, logo, se você precisa passar comportamento ou propriedades para outros tipos, é melhor usar Class. Mais uma vez, na minha opinião, Struct não vale a pena.
Propriedades
Podemos ter constantes ou variáveis, declaradas como propriedades das instâncias de uma classe, conforme já vimos. Para acessar o valor de uma propriedade de instância, é necessário ter uma instância da classe:
var v : veiculo = veiculo()
println(v.placa)
Propriedades derivadas
Podemos ter outros tipos de propriedades, por exemplo as propriedades derivadas (“Computed Properties”). Uma propriedade derivada não é armazenada na instância, mas calculada sob demanda, com base em outras propriedades. Vamos supor uma classe que armazene o percuso feito por um carro, e retorne o valor em quilômetros ou milhas:
class Percurso {
var metros : Double = 0.0
var quilometros : Double {
return metros / 1000
}
var milhas : Double {
return metros * 0.000621371192
}
}
var percurso : Percurso = Percurso()
percurso.metros = 2000
println(percurso.quilometros)
println(percurso.milhas)
Neste exemplo, as propriedades “quilometros” e “milhas” não são armazenadas na instância da classe “Percurso”, mas são calculadas cada vez em que são solicitadas. E se quiséssemos alterar o valor da distância do percurso, que é em metros, usando quilômetros ou milhas? Podemos tornar as propriedades derivadas “read / write”, informado uma função “get”, para obter o valor, e uma função “set”, para alterar o valor:
class Percurso {
var metros : Double = 0.0
var quilometros : Double {
get {
return metros / 1000
}
set(nq) {
self.metros = nq * 1000
}
}
var milhas : Double {
get {
return metros * 0.000621371192
}
set(nm) {
self.metros = nm * 1609.344
}
}
}
var percurso : Percurso = Percurso()
percurso.metros = 2000
println(percurso.quilometros)
println(percurso.milhas)
percurso.quilometros = 5
println(percurso.metros)
percurso.milhas = 2
println(percurso.metros)
Propriedades “Lazy”
Uma propriedade “lazy” tem a sua inicialização adiada até que seu valor seja requisitado. Geralmente é declarada quando sua inicialização é uma operação de alto custo, por exemplo, é necessário ler um arquivo, ou acessar um Web Service, ou mesmo exige o instanciamento de uma classe complexa. Por exemplo, uma classe que retorne a cotação de moeda estrangeira pode necessitar de um serviço externo:
class ServicoCotacoes {
var dolar : Double?
var euro : Double?
init() {
// abre conexão com banco de dados
// conecta a vários web services
// inicializa as variáveis
}
}
class conta {
lazy var servCotacoes : ServicoCotacoes = ServicoCotacoes()
func outraCoisa() {
// Qualquer outra função
}
}
Neste exemplo, a classe “conta” usa os serviços da classe “ServicoCotacoes”, que é muito custosa para instanciar, pois precisa ir em um banco de dados e em Web Services. Porém, a classe “conta” possui outras funções (o método “outraCoisa”) que podem ser utilizadas sem a propriedade “servCotacoes” ser inicializada. Ao declarar essa propriedade como “lazy”, estamos adiando a inicialização até que seu valor seja requisitado pela primeira vez.
Propriedades do Tipo (ou Classe)
Assim como outras linguagens, por exemplo: Java, podemos necessitar declarar propriedades que pertencem à Classe, e não a uma instância específica. Com Swift, infelizmente, só é possível criar propriedades de Classe (“Type Properties”) que sejam calculadas (“Computed Properties”). Não é possível criar propriedades comuns. Por exemplo, este código daria erro:
class ServicoCotacoes {
class var qtdClientes : Int = 0
var dolar : Double?
var euro : Double?
...
Construtores e destrutores de instâncias
As propriedades de uma classe devem ser sempre inicializadas com algum valor. Por exemplo, pegue a classe “veiculo” que criamos anteriormente, e remova a inicialização da propriedade “placa”. Você vai tomar um erro. Podemos consertar isso dizendo que a propriedade é opcional:
class veiculo {
var ligado: Bool = false
var placa: String!
func ligar() {
self.ligado = true
}
func desligar() {
self.ligado = false
}
}
Mas e se quiséssemos fazer uma inicialização mais complicada? Para isso, temos o equivalente a um método construtor: O “initializer”. Veja esse novo exemplo:
class veiculo {
var ligado: Bool
var placa: String
init() {
self.ligado = false
self.placa = ""
}...
O método “init()” é o inicializador da classe, e, se declarado, dispensa inicialização na declaração das propriedades. Note o uso do prefixo “self.” para nos referirmos às propriedades da classe. Isso é semelhante ao “this.”, da linguagem Java. Podemos ter parâmetros no inicializador, por exemplo:
class veiculo {
var ligado: Bool
var placa: String
init() {
self.ligado = false
self.placa = ""
}
init(placa: String) {
self.ligado = false
self.placa = placa
}
func ligar() {
self.ligado = true
}
func desligar() {
self.ligado = false
}
}
var carro1 : veiculo = veiculo()
var carro2 : veiculo = veiculo(placa: "XPTO9999")
println(carro2.placa)
Neste exemplo, vimos dois inicializadores diferentes: um sem parâmetros e outro com um parâmetro “placa”. Quando criamos instâncias da classe, usamos o nome da classe para invocar o inicializador, e podemos passar parâmetros também.Notou uma coisa estranha? Sim! Veja como instanciamos o veículo com um parâmetro:
var carro2 : veiculo = veiculo(placa: "XPTO9999")
Tivemos que passar o nome do parâmetro, mesmo não tendo criado um nome externo para ele (lembra? Funções podem ter nomes externos). O “init()” é um método da classe, e, para métodos, o Swift cria um nome externo para todos os seus parâmetros, independente de você querer isso ou não.
Isso é para documentar melhor os vários construtores de uma classe, pois, como eles não possuem nome, se você tiver vários “init()”, como identificar qual deles você está chamando? Você pode alterar esse comportamento passando um outro nome externo para o seu parâmetro, ou então passando um caracter sublinha (“_”), como nome externo. Nesse caso, você está removendo esse comportamento. Vamos ver de novo:
class veiculo {
var ligado: Bool
var placa: String
...
init(_ placa: String) {
self.ligado = false
self.placa = placa
}
...
}
var carro1 : veiculo = veiculo()
var carro2 : veiculo = veiculo("XPTO9999")
println(carro2.placa)
Depois de dar o nome externo de “_”, não será mais necessário fornecer o nome do parâmetro ao instanciar a classe.
O que fazer em um construtor?
Tudo o que você precisar para inicializar sua instância. Por exemplo, você pode carregar dados (do Core Data) em uma estrutura interna, ou abrir uma conexão HTTP.
De qualquer forma, é o momento que você tem para inicializar tudo o que sua classe precisa para funcionar.
Destrutor?
Você deve estar pensando: “Se eu aloquei alguma coisa, algum recurso ou abri alguma conexão, deve ter um momento para fechar isso tudo”. E tem! Chama-se “deinit()”:
class xpto {
…
init() { … }
deinit {
// Faça sua desalocação aqui!
}
}
Não se preocupe em liberar a memória alocada pelos seus objetos, pois o ARC tomará conta disso.
Métodos
Além de propriedades, as classes podem conter métodos. Nós já vimos isso na classe “veiculo”:
class veiculo {
var ligado: Bool = false
var placa: String = ""
func ligar() {
self.ligado = true
}
func desligar() {
self.ligado = false
}
}
Um método é uma função, logo, tudo o que falamos sobre funções, incluindo os parâmetros nomeados, se aplicam aos métodos.
Tem um porém! Da mesma forma que o “init()”, os métodos com mais de um parâmetro também recebem nomes externos automaticamente. Por exemplo, vamos acrescentar um método à classe “veiculo”:
func mover(direcao: Int, velocidade: Double) -> Bool {
return true
}
Agora, vamos invocá-lo:
let carro: veiculo = veiculo()
carro.mover(1,5.0)
No momento de invocar o método “mover”, você tomará um erro: “missing argument label 'velocidade:' ”. Para invocar o método, é necessário fornecer o nome do segundo parâmetro:
carro.mover(1, velocidade: 5.0)
Quando um método (que não seja o “init()”) tem mais de um parâmetro, o Swift dá nomes externos automaticamente para os outros parâmetros, exceto o primeiro. Logo, você é obrigado a fornecer seus nomes ao invocar o método. Se você quiser mudar esse comportamento, pode dar outros nomes externos, ou mesmo substituir por “_”:
func mover(direcao: Int, _ velocidade: Double) -> Bool {
return true
}
...
let carro: veiculo = veiculo()
carro.mover(1, 5.0)
Assim como Java, Swift permite criar métodos que são associados à Classe, e não a uma instância específica. Para isso, basta acrescentar “class” à sua declaração:
class Veiculo {
…
class func selecionar() {
}
}
Veiculo.selecionar()
Herança, protocolos, visibilidade e escopo
Eu sei, é muito blá-blá-blá, mas temos que falar sobre essas coisas de qualquer forma. Então, respire fundo e vamos lá.
Neste livro, eu assumo que você já sabe programar, portanto, já conhece orientação a Objetos, certo? Ok. Então, quais são os 3 pilares da OOP?
- Herança: Comportamento e propriedades podem ser passadas de uma classe para outra;
- Encapsulamento: Os detalhes de implementação de uma classe devem ser ocultos de seus usuários;
- Polimorfismo: Um método invocado em uma instância, sempre será chamado a partir do tipo runtime da mesma.
Herança em Swift é quando uma classe é criada com base em outra. Funciona de maneira semelhante às outras linguagens orientadas a objeto.
Por exemplo, vamos supor uma hierarquia de classes, tendo “Veículo” como classe básica e “Carro” como subclasse:
class Veiculo {
var ligado: Bool = false;
func ligar() {
ligado = false
}
func desligar() {
ligado = true
}
}
class Carro : Veiculo {
var placa : String = ""
var marca : String = ""
init(marca: String, placa: String) {
super.init()
self.placa = placa
self.marca = marca
}
private func virarChave(direita: Bool) -> Bool {
var resultado = false
if direita {
// virar a chave para direita e ligar o carro
resultado = true
}
else {
// virar a chave para a esquerda e desligar o carro
resultado = false
}
return resultado
}
override func ligar() {
if virarChave(true) {
ligado = true
}
}
override func desligar() {
if !virarChave(false) {
ligado = false
}
}
}
A classe “Carro” declara que estende a classe “Veiculo”, logo, ela herda todos os métodos e propriedades, exceto os inicializadores (“init(...)”) da classe ancestral. Após o nome da classe “Carro”, vem o tipo no qual ela se baseia, que é “Veiculo”.
A nova classe, além de herdar a propriedade “ligado”, acrescentou mais duas propriedades: “placa” e “marca”. E, como não herdou o “init()” da classe ancestral, ela especificou um “init()” próprio. Note que o primeiro comando é invocar o “init()” da superclasse (“super.init()”), que é uma boa praxe quando criamos classes derivadas.
Ela tem um membro privado, o método: “virarChave”. Ele foi declarado como “private”, logo, só pode ser acessado por classes que estejam no mesmo código-fonte da classe “Carro”.
Finalmente, temos dois métodos sobrescritos: “ligar()” e “desligar()”, o que significa que nós não aceitamos o comportamento default herdado pela classe.
Encapsulamento é a característica de ocultar a implementação de uma classe, do código que a utiliza. No Swift, como em qualquer outra classe, você só tem acesso às propriedades e métodos públicos. E, mesmo assim, não precisa ter acesso ao código-fonte dos métodos. Pode usar uma classe que existe apenas em formato binário.
Outra característica importante do encapsulamento é a aderência ao princípio de segregação de interfaces, segundo o qual, a dependência de uma classe para outra, tem que ser baseada na menor interface possível. Bem, em Java, por exemplo, existe o conceito de “interface”, que pode declarar comportamento (e propriedades) que uma classe deve oferecer, de modo a implementá-la.
No exemplo anterior, da classe Veículo, temos o seu funcionamento interno preservado, e podemos até restringir a visibilidade de seus membros, o que fizemos ao declarar um método “private”. O bom encapsulamento diz que até mesmo o acesso às propriedades deveria ser feito através de métodos, e não diretamente. Bem, podemos fazer isso com propriedades calculadas, usando “get” e “set”, se quisermos.
O polimorfismo é garantido sem a necessidade de anotações especiais (por exemplo: “virtual”). No exemplo anterior, a classe Veículo tinha métodos diferentes para “ligar()” e “desligar()”. O polimorfismo do Swift garante que, mesmo que invoquemos esses métodos a partir da classe ancestral, serão invocados os métodos da instância. Por exemplo, digite essas duas linhas no Playground:
var veiculo : Veiculo = Carro(marca: "Ford", placa: "XPTO1111")
veiculo.ligar()
Protocolo é a maneira de segregar interfaces em Swit. Um Protocolo é um comportamento que uma classe deve oferecer, composto por métodos sem implementação. Por exemplo, vamos supor o protocolo “Salvavel”:
protocol Salvavel {
func salvar(arquivo: String) -> Bool
}
Uma classe que declare seguir esse protocolo, tem que fornecer um método “salvar”, exatamente como declarado. Vamos colocar isso na nossa classe “Carro”:
class Carro : Veiculo, Salvavel {
var placa : String = ""
var marca : String = ""
...
Se uma classe vai seguir um protocolo, deve declará-lo como se fosse uma “herança adicional”, sempre depois do nome da sua superclasse. Ao modificar a classe, você vai tomar um erro, informando que a classe não implementa o protocolo “Salvavel”. Basta acrescentar o método “salvar”:
class Carro : Veiculo, Salvavel {
...
func salvar(arquivo: String) -> Bool {
// código para salvar o Carro em um arquivo
return true
}
…
Visibilidade e escopo estão relacionados com o controle de acesso e a existência das variáveis. Vamos começar pelo controle de acesso. Em Swift, temos 3 tipos de acesso:
- “public” : Visível para qualquer código-fonte, mesmo em “módulos” diferentes;
- “internal” : Visível apenas para código-fonte declarado dentro do mesmo “módulo”;
- “private” : Visível apenas dentro do arquivo fonte atual.
O que é um “módulo”? É uma unidade de construção no Xcode. Um framework ou uma app. Se declararmos um elemento como “public”, ele estará disponível até para outros módulos, que importem o nosso módulo.
Atenção: Se você tiver duas classes declaradas no mesmo arquivo-fonte, uma acessará os membros privados da outra! Isso é estranho! Digite essas duas linhas no seu Playground, logo abaixo da classe “Carro”:
var carro : Carro = Carro(marca: "ford", placa: "xpto1234")
println(carro.virarChave(true))
Ao declararmos uma classe, como fizemos com as classes “Veiculo” e “Carro”, sem especificar o controle de acesso, elas serão “internal”, assim como todos os seus membros (exceto quando for explicitamente especificado).
Se declararmos uma classe como “public” ela poderá ser vista fora do nosso “módulo”, mas, por default, seus membros serão “internal”. Então, você poderá dar acesso “public” a cada membro específico.
Quando ao escopo de variáveis, temos as coisas básicas:
- Variáveis definidas dentro de um bloco de código (entre “{“ e “}”) só existem dentro desse bloco, sendo destruídas fora dele;
- Variáveis globais, são definidas fora de um bloco de código e existem fora dele.
Uma variável global em Swift é declarada fora de qualquer escopo (Classe, Struct, Função, Método ou bloco de comandos):
import UIKit
public var minhaGlobal : Int = 1
A variável “minhaGlobal” é global e tem acesso público. Ela existe fora de qualquer contexto do arquivo-fonte.
Uma variável local simples pode ser declarada dentro de um bloco de comandos, por exemplo:
while pilha.count > 0 {
var guardado = pilha.last!
if guardado == "(" ||
guardado == "+" ||
guardado == "-" {
break
}
guardado = pilha.removeLast()
resultado += guardado
}
A variável “guardado” só existe dentro do bloco do “while”. Ao terminar o “while”, ela será destruída, e qualquer objeto que ela referencie, terá sua contagem de referências decrementada.
Automatic Reference Counting
Como alocamos objetos em C++? Se você não sabe, eis um exemplo:
Veiculo * carro = new Veiculo();
A partir desse momento, o C++ alocou dinamicamente um espaço na memória, para acomodar a instância que você criou. Se você não gerenciar isso com cuidado, poderá incorrer no erro conhecido como: “Memory Leak”.
Vamos supor que você tenha uma classe C++, digamos “Motorista”, que chame o comando acima dentro do seu Construtor. O que acontece? A cada instância criada, um bloco de memória é alocado dinamicamente para acomodar uma nova instância de “Veiculo”. Se você instanciar várias vezes a sua classe, haverá várias instâncias de veículo ocupando a memória. Até aí, tudo bem.
Porém, ao destruir uma instância de “Motorista”, você não liberou a memória do “Veículo” instanciado plor ela. Isso significa que haverá várias instâncias “zumbis” de veículos, sem uso algum, só ocupando memória.
Com o passar do tempo, a memória disponível para o seu programa vai diminuindo até dar uma exceção. Isso é “Memory Leak”, ou “vazamento de memória”.
A linguagem Java resolve isso através de um método chamado de “Garbage Collector”. Por exemplo:
public class Motorista {
public Veiculo veiculo = new Veiculo();
…
Esse código, que causaria arrepios em desenvolvedores C++, está alocando uma nova instância de “Veiculo” sempre. Quando a instância de Motorista for destruída, o objeto referenciado por ela será marcado para exclusão pelo “Garbage Collector”, que rodará futuramente.
O Swift usa um mecanismo diferente, chamado de ARC - Automatic Reference Counting, para reclamar a memória ocupada por objetos que não são mais referenciados. O princípio é o seguinte: Enquanto um objeto tiver pelo menos uma referência no programa, ele não será deletado. Ao acabarem todas as referências, ele será deletado e a memória, liberada.
O exemplo a seguir não funcionará no Playground, apenas em uma app rodando no Simulator ou em um dispositivo real:
class veiculo {
var ligado: Bool
var placa: String
init() {
self.ligado = false
self.placa = ""
println("instânca criada")
}
deinit {
println("Instância deletada")
}
func ligar() {
self.ligado = true
}
func desligar() {
self.ligado = false
}
}
var carro1 : veiculo!
var carro2 : veiculo!
carro1 = veiculo()
carro1.placa = "XPTO9999"
carro2 = carro1
println(carro2.placa)
carro2 = nil
carro1 = nil
O objeto “veiculo” instanciado somente será deletado quando TODAS as variáveis que o referenciam, forem atribuídas a “nil” (nulo), o que acontece quando nulamos a variável “carro1”, depois de haver nulado a variável “carro2”. Para poder atribuir uma variável a “nil”, é necessário que ela seja declarada como opcional (com o ponto de exclamação). Isso pode ser comprovado com a mensagem “Instância deletada”, no Log.
Por que isso não funciona no Playground? Porque o objeto nunca será deletado realmente. O Playground não é um ambiente Swift real, logo, testar “deinit” nele não é uma boa ideia.
Quando instanciamos uma variável ou copiamos sua referência, criamos o que é conhecido como “Strong Reference” (referência forte), ou seja, nós somos proprietários daquele objeto. Então, o ARC considera as nossas referências ANTES de deletar o objeto. Evitando deletar um objeto que ainda estamos utilizando.
No exemplo, é exatamente o que nossas variáveis “carro1” e “carro2” são: “Strong references”.
Referências Strong e Weak
Antes de continuar estudando o ARC, precisamos rever o conceito de variáveis opcionais. O que é uma variável opcional? Digite essas duas linhas no Playground e veja o resultado:
var sNumero : String = "203"
var numero : Int = sNumero.toInt()
Deve haver um erro na segunda linha, dizendo: “value of optional type 'Int?' not unwrapped; did you mean to use '!' or '?'?” Isso significa que o resultado do método “toInt()” é “wapped”, ou seja, embrulhado. Em outras palavras, ele pode retornar “nil”. O método “toInt()”, da classe String, é declarado dessa forma:
func toInt() -> Int?
Isso significa que ele pode retornar “nil”. O Swift força você a se proteger contra variáveis não inicializadas. Quando uma variável é declarada com “?”, significa que é um tipo opcional, que admite o valor “nil”.
O que podemos fazer para resolver? Podemos verificar se o valor retornado é “nil” e, caso não seja, podemos forçar que ele seja “desembrulhado”, acrescentando um ponto de exclamação:
var sNumero : String = "203"
var numero : Int = 0
if sNumero.toInt() != nil {
numero = sNumero.toInt()!
}
Estamos informando ao Swift: “Ok, eu já verifiquei e quero desembrulhar o valor”.
Outra maneira, mais arriscada, seria declarar a variável que recebe o valor opcional, como sendo também opcional:
var sNumero : String = "203"
var numero : Int? = sNumero.toInt()
Nesse caso, a variável “numero” é também opcional, e não haverá erro. Porém, se você tentar atribuir seu valor a uma outra variável, que não seja opcional, tomará o mesmo erro.
Quando declaramos uma variável usando o ponto de exclamação, estamos querendo dizer que é para desembrulhá-la sempre, pois sabemos que não poderá ser “nil”. Isto se chama: “Implicitly Unwrapped Optionals”. Vamos rever nosso exemplo:
var sNumero : String = "203"
var numero : Int! = sNumero.toInt()
var outro : Int = numero
Normalmente, usamos variáveis opcionais (declaradas com “?”), quando temos instâncias de classes, não inicializadas imediatamente.
class Veiculo {
var motorista : Motorista?
}
Neste exemplo, temos uma classe que possui uma propriedade não inicializada “motorista”, e não temos um “init()” que inicialize seu valor, logo, o Swift nos força a declará-la como opcional, com uma interrogação.
As referências no Swift são sempre “Strong”, ou seja, se você criou uma variável que referencia uma instância do Objeto, ele não poderá ser deletado pelo ARC, até que essa referência seja nulada (com “nil”).
Porém, há casos em que queremos uma referência “fraca”. Vamos demonstrar isso.
Vamos ver um exemplo que explica o “Strong Reference Cycle” ou “Referência cíclica”. Note que, se tentar rodar no Playground, o “deinit” jamais será chamado de qualquer forma. Suponha as duas classes abaixo:
class Veiculo {
var ligado: Bool = false
var placa: String = ""
var motorista : Motorista?
deinit {
println("Veiculo sendo deletado")
}
}
class Motorista {
var nome: String = ""
var carro : Veiculo?
deinit {
println("Motorista sendo deletado")
}
}
Cada uma tem uma propriedade, que é do tipo da outra. Note que declaramos como propriedades opcionais usando o “?”, pois não as inicializamos.
Agora, precisamos criar, temporariamente, uma instância de cada uma delas. Ao final, vamos nular as referências.
var veiculo : Veiculo? = Veiculo()
veiculo!.placa = "XPTO9999"
var motorista : Motorista? = Motorista()
motorista!.nome = "Fulano de Tal"
Por que declaramos as referências como opcionais? Porque vamos nulá-las depois. O Swift não deixa atribuir “nil” a uma variável que não seja opcional. Note também que tive que colocar uma exclamação após o nome de cada variável, antes de acessar suas propriedades. Isto é porque necessito “desembrulhar” o valor das referências opcionais, conforme já vimos. Nada disso seria necessário, se eu não fosse nular o valor das duas variáveis (“veiculo” e “motorista”).
Entendeu? Ok. Então, vamos “embaralhar” as coisas... Vamos criar uma referência circular entre “motorista” e “veiculo”:
motorista!.carro = veiculo
veiculo!.motorista = motorista
Beleza! Nosso Veículo tem Motorista e nosso Motorista tem um Veículo! Quando chegar a hora de desalocar essas instâncias, e liberar a memória, basta nular TODAS as referências:
veiculo = nil
motorista = nil
Só que o ARC não conseguirá liberar os dois objetos. O que fizemos apenas foi criar um “Memory Leak”, pois temos dois objetos “soltos” na memória, sem termos referências para eles. Por que? Porque ARC não pode deletar um objeto enquanto existirem referências para ele. Nós deletamos as duas referências que criamos, mas e quanto às referências circulares? O Motorista faz uma referência (“Strong”) para o Veículo, e vice versa.
Referências fracas
Podemos criar referências fracas acrescentando o prefixo “weak” antes da declaração de uma variável. Isso significa que o ARC pode deletar o objeto referenciado, sem problema algum.
Para resolver a nossa referência cíclica, bastaria transformarmos uma (ou todas) as referências internas em “weak”:
class Motorista {
var nome: String = ""
weak var carro : Veiculo!
deinit {
println("Motorista sendo deletado")
}
}
Agora, ao nularmos a variável “veiculo”, a referência do Motorista para ele não impedirá o ARC de deletar o objeto.
Hora de mais um exercício
Encapsule o exercício 2 em uma classe.
Respostas
Para sua maior comodidade, as respostas estão dentro de arquivos “txt”, na pasta “capt4”, dos arquivos do livro.
Exercício 1: Inverter as palavras de uma frase
var texto = "Minha terra tem palmeiras onde canta o sabiá"
var palavras = texto.componentsSeparatedByString(" ")
for palavra in reverse(palavras) {
println(palavra)
}
sabiá
o
canta
onde
palmeiras
tem
terra
Minha
Exercício 2: Transformar uma expressão aritmética em notação polonesa
Atenção: Para facilitar o aprendizado, essa solução tem algumas limitações:
- Só trabalha com números inteiros;
- Só trabalha com números menores que 10;
- Só funciona com as 4 operações básicas.
O motivo é que a análise da expressão é feita caractere a caractere, o que facilita muito o código. Se não fosse dessa forma, teríamos os seguintes problemas:
- Análise de valores de mais de um dígito: 345;
- Análise de valores decimais: 57.2938;
- Análise de funções: seno(), cosseno() etc.
Eis o algoritmo:
var expressao = "((3 + 5) * 4) / 2"
var pilha : [String] = []
var resultado = ""
for caracter in expressao {
if caracter != " " {
switch caracter {
case "(":
pilha.append(String(caracter))
case ")":
while pilha.count > 0 {
let guardado = pilha.removeLast()
if guardado == "(" {
break
}
resultado += guardado
}
case "+", "-":
while pilha.count > 0 {
var guardado = pilha.last!
if guardado == "(" {
break
}
guardado = pilha.removeLast()
resultado += guardado
}
pilha.append(String(caracter))
case "*","/":
while pilha.count > 0 {
var guardado = pilha.last!
if guardado == "(" ||
guardado == "+" ||
guardado == "-" {
break
}
guardado = pilha.removeLast()
resultado += guardado
}
pilha.append(String(caracter))
default:
resultado += String(caracter)
}
}
}
if pilha.count > 0 {
resultado += pilha.removeLast()
}
resultado
O resultado é:
35+4*2/
Exercício 3: Converter a solução do exercício 2 em uma Classe
class Analisador {
func transformar(expressao: String) -> String {
var pilha : [String] = []
var resultado = ""
for caracter in expressao {
if caracter != " " {
switch caracter {
case "(":
pilha.append(String(caracter))
case ")":
while pilha.count > 0 {
let guardado = pilha.removeLast()
if guardado == "(" {
break
}
resultado += guardado
}
case "+", "-":
while pilha.count > 0 {
var guardado = pilha.last!
if guardado == "(" {
break
}
guardado = pilha.removeLast()
resultado += guardado
}
pilha.append(String(caracter))
case "*","/":
while pilha.count > 0 {
var guardado = pilha.last!
if guardado == "(" ||
guardado == "+" ||
guardado == "-" {
break
}
guardado = pilha.removeLast()
resultado += guardado
}
pilha.append(String(caracter))
default:
resultado += String(caracter)
}
}
}
if pilha.count > 0 {
resultado += pilha.removeLast()
}
return resultado
}
}
var an = Analisador()
let saida = an.transformar("((3 + 5) * 4) / 2")
Conclusão
Ufa! Vimos muita coisa sobre a linguagem Swift e sobre o Playground, certo? Mas acredite: ainda tem muuuuiiiiiita coisa que não falamos. Porém, o que vimos até agora, nos dá segurança para continuarmos nosso aprendizado.
E o Playground é sensacional, não? Eu o aconselharia a usar o Playground antes de testar qualquer lógica de aplicação.
Não se esqueça!
Acesse a página do curso para ver as outras lições, e sempre baixe novamente o zip do curso, pois, como é um trabalho em andamento, pode haver correções de erros e aprimoramentos.Se tiver dúvidas, use o fórum!
Esse "curso" não dá diploma algum! E todo o material é liberado sob licença "Creative Commons" compartilha igual.
Você pode compartilhar esse material da forma que desejar, desde que mantenha o mesmo tipo de licença e as atribuições de autoria original.
O trabalho "Criando apps para iPhone com Swift " de Cleuton Sampaio de Melo Jr está licenciado com uma Licença Creative Commons - Atribuição-CompartilhaIgual 4.0 Internacional. Isso inclui: Textos, páginas, gráficos e código-fonte.
Nenhum comentário:
Postar um comentário