Advertisement
  1. Code
  2. Swift

Introdução à Swift: Parte 2

Scroll to top
Read Time: 18 min

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.

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.