Portuguese (Português) translation by Erick Patrick (you can also view the original English article)
No primeiro artigo dessa série sobre Swift, falamos sobre a filosofia dela, demos uma olhada na sua sintaxe e destacamos diferenças chaves em relação a Objective-C. Nesse artigo, continuaremos a explorar a sintaxe da Swift. Também aprenderemos sobre opcionais e como administração de memória funciona em Swift.
1. Condicionais & Laços
If
Declarações if são idênticas em Swift e Objective-C, exceto por duas diferenças sutis:
- Parêntese ao redor da variável da condição são opcionais
- chaves são requeridas
Essas são, basicamente as únicas diferenças para as declarações if de Objective-C.
Séries
Como vimos no primeiro artigo, Swift inclui dois operadores de alcance, ..<
e ...
, para especificar valores de séries. Eles são o operador de série semi-fechada e o operador de série fechada.
Um série semi-fechada, tal qual 1..<5
, representa os valores 1, 2, 3 e 4, excluindo o 5. Uma série fechada, como 1...5
, representa os valores 1, 2, 3, 4, e inclui o 5.
Séries podem ser usadas em laços for
, subscript de array
e até declarações switch
. Vejamos alguns exemplos:
1 |
// for loop example |
2 |
for i in 1..<10 { |
3 |
|
4 |
} |
5 |
// iterates from 1 to 9 |
1 |
// array subscript example |
2 |
let someArray = ["apple","pair","peach","watermelon","strawberry"] |
3 |
for fruit in someArray[2..<4] { |
4 |
println(fruit) |
5 |
} |
6 |
// outputs: peach and watermelon |
1 |
// switch example |
2 |
switch someInt { |
3 |
case 0: |
4 |
// do something with 0 |
5 |
case 1..<5: |
6 |
// do something with 1,2,3,4 |
7 |
case 5...10: |
8 |
// do something with 5,6,7,8,9,10 |
9 |
default: |
10 |
// everything else |
11 |
} |
Switch
Declarações switch são muito mais poderosas na Swift que em Objective-C. Em Objective-C, o resultado da expressão de uma declaração switch precisa ser um inteiro e os valores de cada declaração case deve ser uma constante ou expressão de constante. Não é assim na Swift. Nela, as declarações case podem ser de qualquer tipo, incluindo séries.
Na Swift, switch não precisa de break
e ele não passa automaticamente de um caso para outro. Ao criar declarações switch, é preciso cuidado pois todas as condições precisam ser lidadas pelos casos. Falhar nisso, resultará em erro de compilação. Uma forma de cobrir todas as condições é incluir uma declaração de caso default
.
Eis um exemplo de um switch
com casos em String
:
1 |
let vegetable = "red pepper" |
2 |
switch vegetable { |
3 |
case "celery": |
4 |
let vegetableComment = "Add some raisins and make ants on a log." |
5 |
case "cucumber", "watercress": |
6 |
let vegetableComment = "That would make a good tea sandwich." |
7 |
default: |
8 |
let vegetableComment = "Everything tastes good in soup." |
9 |
} |
Na Swift, declarações case não passam por padrão. Isso é uma decisão de design deliberada para evitar erros. Se um caso específico precisas passar, podemos usar a palavra-chave fallthrough
para indicar para o compilador.
1 |
switch someInt { |
2 |
case 0: |
3 |
// do something with 0 |
4 |
case 1: |
5 |
// do something with 1 |
6 |
case 2: |
7 |
// do something with 2 |
8 |
fallthrough |
9 |
default: |
10 |
//do something for everything else |
11 |
} |
12 |
|
13 |
// case 2 will fall through to default case |
E não para aí. Swift adiciona dois recurso ao switch, ligação de valores e cláusula where. Ligação de valores é usado com as palavras-chave case let
para ligar uma constante a um caso. A cláusula where adiciona uma condição extra ao caso usando a palavra-chave where
.
Esses dois conceitos são melhores explicados em exemplos. O código abaixo mostra como ligação de valores funciona.
1 |
let somePoint = (xaxis:2, yaxis:0) |
2 |
switch somePoint { |
3 |
case (let x, 0): |
4 |
println("on the x-axis with an x value of \(x)") |
5 |
case (0, let y): |
6 |
println("on the y-axis with a y value of \(y)") |
7 |
case let (x, y): |
8 |
println("somewhere else at (\(x), \(y))") |
9 |
} |
O primeiro caso, case (let x, 0)
casará quando yaxis
for igual a 0
e xaxis
qualquer valor, além de atribuir seu valor a x
para uso dentro do caso.
Eis um exemplo da cláusula where em ação.
1 |
let vegetable = "red pepper" |
2 |
switch vegetable { |
3 |
case "celery": |
4 |
println("Add some raisins and make ants on a log.") |
5 |
case let x where x.hasSuffix("pepper"): |
6 |
println("I'm allergic to \(x)") |
7 |
default: |
8 |
println( "Everything tastes good in soup.") |
9 |
} |
10 |
|
11 |
// outputs: I'm allergic to red pepper |
2. Funções & Clausuras
Funções
Definir funções em clausuras é fácil em Swift. Desenvolvedores de Objective-C as conhecem melhores como funções e blocos.
Na Swift, parâmetros de função podem ter valores padrão, reminiscente de linguagens de script, como PHP e Ruby.
A sintaxe para funções é como a seguir:
1 |
func functionName(parameterName: Type = DefaultValue) -> returnType { |
2 |
[...]
|
3 |
return returnType; |
4 |
}
|
Para escrever a função sayHello
que tem o parâmetro name
do tipo String
e returna um Bool
quando bem sucedida, escrevemos o seguinte:
1 |
func sayHello(name: String) -> Bool { |
2 |
println("hello \(name)"); |
3 |
return true; |
4 |
} |
5 |
|
6 |
sayHello("World") |
7 |
// output |
8 |
// hello World |
Para passar um valor padrão para o parâmetro name
, a implementação da função deveria ser assim:
1 |
func sayHello(name: String = "World") -> Bool { |
2 |
println("hello \(name)"); |
3 |
return true; |
4 |
} |
5 |
|
6 |
sayHello() |
7 |
// output |
8 |
// hello World |
9 |
|
10 |
sayHello("mike") |
11 |
// output |
12 |
// hello mike |
Um recurso inexistente em Objective-C são as tuplas. Em Swift, funções podem retornar valores múltiplos na forma de tuplas. Tuplas são consideradas uma única variável, significando que podemos passá-las como qualquer variável.
Tuplas são fáceis de usar. Na verdade, já trabalhamos com tuplas no artigo anterior quando enumeramos um dicionário. No próximo trecho de código, o par chave/valor é uma tupla.
1 |
for (key,value) in someDictionary { |
2 |
println("Key \(key) has value \(value)" |
3 |
} |
Como elas são usadas como nos beneficiamos delas? Vejamos outro exemplo. Modifiquemos a função sayHello
acima para retornar um booleano quando bem sucedido, assim como a mensagem em si. Para tanto, retornamos uma tupla, (Bool, String)
. A versão atualizada de sayHello
parece com isso:
1 |
func sayHello(name: String = "World") -> (Bool, String) { |
2 |
let greeting = "hello \(name)" |
3 |
return (true, greeting); |
4 |
} |
5 |
|
6 |
let (success, greeting) = sayHello() |
7 |
println("sayHello returned success:\(success) with greeting: \(greeting)"); |
8 |
// output |
9 |
// sayHello returned success:1 with greeting: hello World |
Tuplas estão na lista de desejos de vários programadores de Objective-C há tempos.
Outro recurso legal das tuplas é que podemos nomear as variáveis retornadas. Se revisitarmos o exemplo anterio e dermos nomes às variáveis da tupla, teremos o seguinte:
1 |
func sayHello(name: String = "World") -> (success: Bool, greeting: String) { |
2 |
let greeting = "hello \(name)" |
3 |
return (true, greeting); |
4 |
} |
5 |
|
6 |
let status = sayHello() |
7 |
println("sayHello returned success:\(status.success) with greeting: \(status.greeting)"); |
8 |
// output |
9 |
// sayHello returned success:1 with greeting: hello World |
Isso significa que, ao invés de definir uma constante separada para cada elemento retornada da tupla, podemos acessar os elementos da tupla retornada usando a notação de ponto como visto acima, status.success
e status.greeting
.
Clausuras
Clausuras em Swift são o mesmo que blocos em Objective-C. Elas podem ser definidas em linha, passadas como parâmetro ou retornadas por funções. Usamo-nas exatamente como usamos blocos em Objective-C.
Definir clausuras é fácil. Na verdade, uma função é um tipo especial de clausura. Logo, faz todo sentido que uma clausura pareça com uma função.
Clausuras são tipos de primeira-classe, ou seja, podem ser passadas para e retornadas de funções como qualquer outro tipo, como Int
, String
, Bool
, etc. Ela são, essencialmente, blocos de código que podemos invocar depois e ter acesso ao escopo que elas definiras.
Criar uma clausura sem nome é tão simples quanto envolver um bloco de código em chaves. Os parâmetros e tipo de retorno da clausura são separados do corpo dela, com a palavra-chave in
.
Digamos que queremos definir uma clausura que retorne true
se um número é par, então a clausura seria algo assim:
1 |
let isEven = { |
2 |
(number: Int) -> Bool in |
3 |
let mod = number % 2 |
4 |
return (mod==0) |
5 |
} |
A clausura isEven
receber um Int
como parâmetro único e retorna um Bool
. O tipo da clausura é (number: Int) -> Bool
ou (Int -> Bool)
. Podemos invocar isEven
em qualquer lugar do código do mesmo jeito que blocos de código em Objective-C.
Para passar uma clausura desse tipo como parâmetro de uma função, usamos o tipo da clausura na definição da função:
1 |
let isEven = { |
2 |
(number: Int) -> Bool in |
3 |
let mod = number % 2; |
4 |
return (mod==0); |
5 |
} |
6 |
|
7 |
func verifyIfEven(number: Int, verifier:(Int->Bool)) ->Bool { |
8 |
return verifier(number); |
9 |
} |
10 |
|
11 |
verifyIfEven(12, isEven); |
12 |
// returns true |
13 |
|
14 |
verifyIfEven(19, isEven); |
15 |
// returns false |
No exemplo acima, o parâmetro verifier
de verifyIfEven
é uma clausura que passamos para a função.
3. Classes & Estruturas
Classes
É hora de falar das bases da programação orientada a objetos, as classes. Classes, como mencionado antes, são definidas em um arquivo único de implementação com extensão .swift. Declarações de propriedade e métodos são definidas lá.
Criamos uma classe com a palavra-chave class
seguida do nome da classe. A implementação da classe é envolvida por chaves. Como em Objective-C, a convenção de nomeação de classes é usar iniciar com letra maiúscula.
1 |
class Hotel { |
2 |
//properties |
3 |
//functions |
4 |
} |
Para criar uma instância de Hotel
, escrevemos:
1 |
let h = Hotel() |
Na Swift, não é preciso invocar init
nos objetos, ele já é chamado automaticamente por nós.
Herança de classe segue o mesmo padrão de Objective-C, um dois-pontos separa o nome da classe de sua super-classe. No exemplo abaixo, Hotel
herda de BigHotel
.
1 |
class BigHotel: Hotel { |
2 |
|
3 |
} |
Como em Objective-C, usamos a notação de ponto para acessas propriede do objeto. Contudo, Swift também usa a notação de ponto paa invocar métodos de classe e instância, como abaixo.
1 |
// Objective-C |
2 |
UIView* view = [[UIView alloc] init]; |
3 |
[self.view addSubview:view]; |
4 |
|
5 |
// Swift |
6 |
let view = UIView() |
7 |
self.view.addSubview(view) |
Propriedades
Outra diferença em relação a Objective-C é que Swift não distingue em variáveis de instância (ivars) e propriedades. Uma variável de instância é uma propriedade.
Declarar uma propriedades é como definir uma variável ou constante, usando var
e let
. A única diferença é o contexto que elas são definidas, isso é, o contexto de uma classe.
1 |
class Hotel { |
2 |
let rooms = 10 |
3 |
var fullRooms = 0 |
4 |
} |
No exemplo acima, rooms
é um valor imutável, uma constante, com valor 10
e fullRooms
é uma variável com valor inicial 0
que podemos mudar depois. As propriedades devem ser inicializadas quando declaradas. A única excssão é quando são opcionais, que discutiremos já.
Propriedades Computadas
A Swift também define propriedades computadas. Nada mais são que getter e setter que não guardam valores. Como os nomes indicam, são computadas ou avalidadas em execução.
Abaixo temos um exemplo de propriedade computada. Alteramos rooms
para var
no resto dos exemplos. Veremos já o porque.
1 |
class Hotel { |
2 |
var rooms = 10 |
3 |
var fullRooms = 0 |
4 |
var description: String { |
5 |
get { |
6 |
return "Size of Hotel: \(rooms) rooms capacity:\(fullRooms)/\(rooms)" |
7 |
} |
8 |
} |
9 |
} |
Como description
é apenas de leitura e tem apenas uma declaração return
, podemos omitir a palavra-chave get
e as chaves e apenas usar o return
. É uma abreviação e é o que usaremos pelo resto do tutorial.
1 |
class Hotel { |
2 |
var rooms = 10 |
3 |
var fullRooms = 0 |
4 |
var description: String { |
5 |
return "Size of Hotel: \(rooms) rooms capacity:\(fullRooms)/\(rooms)" |
6 |
} |
7 |
} |
Também podemos definir propriedades de leitura e escrita. Em Hotel
, queremos uma propriedade emptyRooms
que obtem o número de quartos livres no hotel, mas também queremos atualizar fullRooms
ao ajustar emptyRooms
. Fazemo isso através da palavra-chave set
, mostrada abaixo.
1 |
class Hotel { |
2 |
var rooms = 10 |
3 |
var fullRooms = 0 |
4 |
var description: String { |
5 |
return "Size of Hotel: \(rooms) rooms capacity:\(fullRooms)/\(rooms)" |
6 |
} |
7 |
var emptyRooms :Int { |
8 |
get { |
9 |
return rooms - fullRooms |
10 |
} |
11 |
set { |
12 |
// newValue constant is available here |
13 |
// containing the passed value |
14 |
if(newValue < rooms) { |
15 |
fullRooms = rooms - newValue |
16 |
} else { |
17 |
fullRooms = rooms |
18 |
} |
19 |
} |
20 |
} |
21 |
} |
22 |
|
23 |
let h = Hotel() |
24 |
h.emptyRooms = 3 |
25 |
h.description |
26 |
// Size of Hotel: 10 rooms capacity:7/10 |
No setter emptyRooms
, a constante newValue
nos é dada para representar o valor passado pelo setter. Também é importante notar que propriedades computadas são sempre declaradas como variáveis, usando var
, porque seus valores computados podem mudar.
Métodos
Já cobrimos funções mais cedo no artigo. Métodos nada mais são que funções atreladas a um tipo, como uma classe. No exemplo a seguir, implementamos um método de instância, bookNumberOfRooms
, em Hotel
.
1 |
class Hotel { |
2 |
var rooms = 10 |
3 |
var fullRooms = 0 |
4 |
var description: String { |
5 |
return "Size of Hotel: \(rooms) rooms capacity:\(fullRooms)/\(rooms)" |
6 |
} |
7 |
var emptyRooms :Int { |
8 |
get { |
9 |
return rooms - fullRooms |
10 |
} |
11 |
set { |
12 |
// newValue constant is available here |
13 |
// containing the passed value |
14 |
if(newValue < rooms) { |
15 |
fullRooms = rooms - newValue |
16 |
} else { |
17 |
fullRooms = rooms |
18 |
} |
19 |
} |
20 |
|
21 |
} |
22 |
func bookNumberOfRooms(room:Int = 1) -> Bool |
23 |
{ |
24 |
if(self.emptyRooms>room) { |
25 |
self.fullRooms++; |
26 |
return true |
27 |
} else { |
28 |
return false |
29 |
} |
30 |
} |
31 |
} |
32 |
|
33 |
let h = Hotel() |
34 |
h.emptyRooms = 7 |
35 |
h.description |
36 |
//Size of Hotel: 10 rooms capacity:3/10 |
37 |
h.bookNumberOfRooms(room: 2) |
38 |
// returns true |
39 |
h.description |
40 |
//Size of Hotel: 10 rooms capacity:5/10 |
41 |
h.bookNumberOfRoom() |
42 |
// returns true |
43 |
h.description |
44 |
//Size of Hotel: 10 rooms capacity:6/10 |
45 |
|
46 |
Inicializadores
O inicializador padrão das classes é init
. Nela, configuramos os valores iniciais da instância criada.
Por exemplo, se precisamos de uma subclasse de Hotel
com 100 quartos, então precisamos inicializar a propriedade rooms
com 100
. Lembremo-nos que alteramos rooms
de uma constante para uma variável logo mais cedo. O motivo disso é que não podemos alterr constantes herdadas em uma subclasse, apenas variáveis herdadas podem mudar.
1 |
class BigHotel: Hotel { |
2 |
init() { |
3 |
super.init() |
4 |
rooms = 100 |
5 |
} |
6 |
} |
7 |
|
8 |
let bh = BigHotel() |
9 |
println(bh.description); |
10 |
//Size of Hotel: 100 rooms capacity:0/100 |
Inicializadores também podem alterar parâmetos. O exemplo a seguir mostra como funciona.
1 |
class CustomHotel: Hotel { |
2 |
init(size:Int) { |
3 |
super.init() |
4 |
rooms = size |
5 |
} |
6 |
} |
7 |
|
8 |
let c = CustomHotel(size:20) |
9 |
c.description |
10 |
//Size of Hotel: 20 rooms capacity:0/20 |
Sobrescrevendo Métodos e Propriedades Computadas
É uma das coisas mis legais em Swift. Em Swift, uma classe pode sobrescrever tanto métodos como propriedades computadas. Para isso, usamos override
. Sobrescrevamos description
na classe CustomHotel
:
1 |
class CustomHotel: Hotel { |
2 |
init(size:Int) { |
3 |
super.init() |
4 |
rooms = size |
5 |
} |
6 |
override var description:String { |
7 |
return super.description + " Howdy!" |
8 |
} |
9 |
} |
10 |
|
11 |
let c = CustomHotel(size:20) |
12 |
c.description |
13 |
// Size of Hotel: 20 rooms capacity:0/20 Howdy! |
O resultado é que description
retornar o retorno do método description
da superclasse com a "Howdy!"
anexada a ele.
O mais legal de sobrescrever métodos e propriedades computadas é a palavra-chave override
. Quando o compilador a vê, ele verifica se a superclasse implementa o método sendo sobrescrito. O compilador também verifica se a propriedade e métodos de uma classe estão em conflito com as propriedades e métodos herdados na árvore da herança.
Por um erro de digitação no método sobrescrito, podemos passar horas tentando descobrir o problema. Em Swift, o compilador dirá exatamente o que há de errada na situação.
Estruturas
Estruturas, definidas com a palavra-chave struct
, são mais poderosas em Swift que em C ou Objective-C. Em C, estruturas definem apenas valores e ponteiros. Na Swift também é assim, mas também suportam métodos e propriedades computadas.
Qualquer coisa que fazemos com classe, podemos com estruturas, com duas diferenças importantes:
- estruturas não suportam herança como classes
- estruturas são passadas por valor enquanto classes por referência
Eis alguns exemplos de estruturas em Swift:
1 |
struct Rect { |
2 |
var origin: Point |
3 |
var size: Size |
4 |
var area: Double { |
5 |
return size.width * size.height |
6 |
} |
7 |
func isBiggerThanRect(r:Rect) -> Bool { |
8 |
return (self.area > r.area) |
9 |
} |
10 |
} |
11 |
|
12 |
struct Point { |
13 |
var x = 0 |
14 |
var y = 0 |
15 |
} |
16 |
|
17 |
struct Size { |
18 |
var width = 0 |
19 |
var height = 0 |
20 |
} |
4. Opcionais
Solução Para Um Problema
Opcionais são um novo conceito se vier do Objective-C. Elas resolvem um problema que programadores se deparam. Quando acessamos uma variável que não temos certeza de seu valor, retornamos um indicador, conhecido como sentinela, para indicar que o que foi retornado não é um valor. Vejamos com um exemplo do Objective-C:
1 |
NSString* someString = @"ABCDEF"; |
2 |
NSInteger pos = [someString rangeOfString:@"B"].location; |
3 |
|
4 |
// pos = 1
|
No exemplo acima, tentamos achar a posição de @"B"
em someString
. Se for achado, sua localização ou posição é salva em pos
. Mas o que acontece se não encontrado?
A documentação diz que rangeOfString:
retorna um NSRange
com a constante NSNotFound
em location
. No caso de rangeOfString:
a sentinela é NSNotFound
. Sentinelas são usadas para indicar que o retorno não é válido.
Em Cocoa, existem vários usso desse conceito, mas o valor do sentinela varia de acordo com o contexto, 0
, -1
, NULL
, NSIntergerMax
, INT_MAX
, Nil
, etc. O problema pra o programador é que devemos lembrar qual sentinela é usado em qual contexto. Se o programador não tomar cuidado, pode confundir um valor válido com um sentinel e vice-versa. Swift resolve esse problema com opcionais. Para citar Brian Lanier "Opcionais são o sentinal que comandarão todos".
Opcionais tem dois estados, nil
, que significa que o opcional não tem valor, e um segundo valor, que significa ter um valor válido. Pense neles como um pacote com um indicador que diz se o conteúdo é válido ou não.
Uso
Todos tipos em Swift podem ser opcionais. Definimos um opção adicionando ?
após a declaração do tipo:
1 |
let someInt: Int? |
2 |
|
3 |
// someInt == nil |
Atribuímos um valor ao pacote do opcionais do mesmo jeito que a variáveis e constantes.
1 |
someInt = 10 |
2 |
|
3 |
// someInt! == 10 |
Lemremo-nos que opcionais são como pacotes. Quando declaramos let someInt: Int?
, definimos uma caixa vazia com um valor nil
. Ao atribuit 10
ao opcional, a caixa contem o inteiro de valor 10
e seu indicador ou estado torna-se not nil.
Para obter o conteúdo de um opção usamos !
. Devemos ter certeza que o opcional tem um valor válido antes de desembrulhá-lo. Falhando, causará um erro de execução. É assim como acessamos o valor de um opcional:
1 |
if ( someInt != nil) { |
2 |
println("someInt: \(someInt!)") |
3 |
} else { |
4 |
println("someInt has no value") |
5 |
} |
6 |
|
7 |
// someInt: 10 |
O padrão acima é tão comum em Swift que podemos simplificar o código acima usando a ligação opcional, if let
. Vejamos o código atualizado abaixo.
1 |
if let value = someInt { |
2 |
println("someInt: \(value)") |
3 |
} else { |
4 |
println("someInt has no value") |
5 |
} |
Opcionais são o único tipo que podem ter o nil
. Constantes e variáveis não podem ser inicializados com nil
. Isso é parte da política de segurança da Swift, onde todas variáveis e constantes não opcionais devem ter valores.
5. Administração de Memória
Se lembrarmos bem, quando ARC foi introduzido, usávamos strong
e weak para definir a relação de objetos. Swift também tem o modelo strong
e weak
de domínio, mas introduz um novo, unowned
. Vejamos cada modelo de domínio de objeto em Swift.
strong
Referências forte são o padrão em Swift. A maior parte do tempo, possuímos o objeto que referenciamos e somos o responsável por manter o objeto referenciado vivo.
Como referências fortes são o padrão, não é preciso manter uma referência forte explicitamente a um objeto, qualquer referência é um referência forte.
weak
Uma referência fraca em Swift indica que a referência aponta para um objeto que não somos responsáveis. É usado principalmente entre dois objetos que não precisam um do outro por perto para continuar seus ciclos de vida.
Tem um porém, contudo. Em Swift, referências fracas sempre devem ser variável com um tipo opcional, porque elas recebem nil
quando o objeto referenciado é desalocado. A palavra-chave weak
é usada na declaração da variável:
1 |
weak var view: UIView? |
unowned
Referências sem domínio são novas para programdores de Objective-C. Nela, não somos responsáveis pela manutenção do objeto referênciado, assim como nas fracas.
A diferença é que as referências sem domínio não recebem nil
quando o objeto referenciado é desalocado. Outra diferença importante para referências fracas é que as sem domínio são definidas como tipos não opcionais.
Referências sem domínio podem ser constantes. Um objeto sem domínio ão existe sem seu dono, assim, uma referência sem domínio nunca será nil
. Elas precisão da palavra-chave unowned
antes da definição da variável ou constante.
1 |
unowned var view: UIView |
Conclusão
Swift é uma linguagem maravilhosa, bem profunda e cheia de potencial. É legal criar programas nela e remove muito do código base que escrevemos no Objective-C e ainda garantindo que nosso código é seguro.
Recomendamos muito A Linguagem de Programação Swift, disponível de graça na iBook Store da Apple.