Advertisement
  1. Code
  2. Swift

iOS do princípio com Swift: Mais de Swift em poucas palavras

Scroll to top
Read Time: 15 min
This post is part of a series called iOS From Scratch With Swift.
iOS From Scratch With Swift: Swift in a Nutshell
iOS From Scratch With Swift: Exploring the iOS SDK

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

Mesmo que este tutorial foque principalmente em funções e closures, vamos desviar um pouco no final e explorar protocolos e controles de acesso. Iremos trabalhar com protocolos mais tarde nesta série mas é importante conhece-las desde o inicio.

A alguns meses atrás, eu escrevi uma série sobre a linguagem Swift em que eu foquei atentamente em funções e closures. Este artigo resume o que eu escrevi nestes tutoriais. Se você quer mais informações sobre funções e closures, eu recomendo a leitura dos artigos desta série.

Antes de mergulharmos no tópico de funções e closures, é hora de iniciarmos o Xcode e criar um novo playground. Pronto? Vamos escrever algum código.

1. Funções

O que é uma função?

Uma função é nada mais que um bloco de código que pode ser executado quando você precisar. Eu gostaria de começar com um exemplo para discutirmos a anatomia de uma função em Swift. Adicione a definição da função a seguir ao seu playground.

1
func printHelloWorld() {
2
    print("Hello World!")
3
}

Uma função começa com a palavra chave func e é seguida pelo nome da função, printHelloWorld em nosso exemplo acima. Como em muitas linguagens, o nome de uma função é seguida por um par de parênteses que contém os parâmetros da função, a entrada da função.

O corpo da função fica dentro de um par de chaves. O corpo da função printHelloWorld() contém uma instrução na qual imprimimos a string "Hello World!" na saída padrão. Uma função básica fica mais ou menos assim em Swift. Para chamar a função, digitamos o nome da função seguido por um par de parênteses.

1
printHelloWorld()

Parâmetros

Vamos tornar o exemplo acima um pouco mais complexo, adicionando parâmetros à definição da função. Ao adicionar os parâmetros, fornecemos a função com valores de entrada que podem ser usados em seu corpo. No exemplo a seguir, definimos a função printMessage(_:), que recebe um parâmetro, message, do tipo String.

1
func printMessage(message: String) {
2
    print(message)
3
}

Uma função pode aceitar múltiplos parâmetros ou valores de entrada. Os parâmetros ficam dentro dos parênteses que segue o nome da função. O nome do parâmetro é seguido por dois pontos e seu tipo. Como você deve lembrar, isso é muito similar à declaração de uma variável ou constante. Isso simplesmente diz que o parâmetro message é do tipo String.

Chamar a função é similar ao que mostramos anteriormente. A diferença é que passamos um argumento.

1
printMessage("Hello World!")

O exemplo a seguir é parecido. A diferença é que a função define dois parâmetros, message do tipo String e times do tipo Int.

1
func printMessage(message: String, times: Int) {
2
    for i in 0..<times {
3
        print("\(i) \(message)")
4
    }
5
}

Apesar do nome da função ser idêntico ao da função original printMessage(_:), o tipo da função é diferente. É importante que você entenda a diferença. Cada função tem um tipo, que consiste do tipo do parâmetro e do tipo do retorno. Iremos explorar tipos de retorno em instantes. Funções podem ter o mesmo nome desde que seus tipos sejam diferentes como mostrado nas duas definições anteriores.

O tipo da primeira função é (String) -> (), já o tipo da segunda função é (String, Int) -> (). O nome de ambas as funções é o mesmo. Não se preocupe com o símbolo ->. Seu significado se tornará claro logo mais, quando discutirmos os tipos de retorno.

A segunda função printMessage(_:times:) define dois parâmetros, message do tipo String e times do tipo Int. Esta assinatura da função demonstra uma das características do Swift adotada do Objective-C, nomes de funções e métodos legíveis.

1
printMessage("Hello World!", times: 3)

Tipos de retorno

As funções que vimos até agora não nos retornam nada quando as chamamos. Vamos criar uma função que formata uma data. A função recebe dois argumentos, uma data do tipo NSDate e uma string de formatação do tipo String. Como as classes NSDate e NSDateFormatter são definidas no framework Foundation, precisamos importar o Foundation no início do playground.

1
import Foundation
2
3
func formatDate(date: NSDate, format: String = "YY/MM/dd") -> String {
4
    let dateFormatter = NSDateFormatter()
5
    dateFormatter.dateFormat = format
6
    return dateFormatter.stringFromDate(date)
7
}

Existem algumas coisas que requerem alguma explicação. O simbolo -> indica que a função retorna um valor. O tipo do valor é definido após o símbolo ->, String.

A função recebe dois argumentos e o segundo argumento tem um valor padrão. Isto é indicado com a atribuição após o tipo do segundo argumento. O valor padrão do format é "YY/MM/dd". Isso significa que podemos chamar a função com apenas um argumento. O auto-completar do Xcode demonstra isso.

Default Value for ParameterDefault Value for ParameterDefault Value for Parameter

Se uma função não tem um tipo de retorno, o símbolo -> é omitido. É por isso que a função printHelloWorld() não tem um símbolo -> na definição de seu método.

Nome local e externo de parâmetros

Anteriormente neste tutorial, definimos a função printMessage(_:). Apesar de termos dado um nome ao parâmetro, message, não usamos o nome quando chamamos a função. Em vez disso, podemos apenas passar o valor para o parâmetro message.

1
func printMessage(message: String) {
2
    print(message)
3
}
4
5
printMessage("Hello World!")

O nome que definimos na definição da função é um nome local de parâmetro. O valor do parâmetro pode ser referenciado apenas por este nome no corpo da função. Mas os parâmetros das funções são um pouco mais flexíveis do que isso.

Objective-C é conhecido por seus nomes de método longos. Apesar disto parecer desajeitado e deselegante para quem esta de fora, isto torna os métodos fáceis de entender e, se bem escolhido, muito descritivo. Se você acha que perdeu este benefício quando mudou para o Swift, então você terá uma surpresa.

Quando uma função recebe muitos parâmetros, nem sempre fica óbvio qual argumentos corresponde a qual parâmetro. Dê uma olhada no exemplo a seguir para entender melhor o problema.

1
func power(a: Int, b: Int) -> Int {
2
    var result = a
3
    
4
    for _ in 1..<b {
5
        result = result * a
6
    }
7
    
8
    return result
9
}
10
11
power(2, 3)

A função power(_:b:) eleva o valor de a pelo exponente b. Ambas são do tipo Int. Enquanto a maioria das pessoas intuitivamente passaria o valor base como o primeiro argumento e o expoente como o segundo argumento, isto não esta claro pelo tipo, nome e assinatura da função.

Para evitar confusão, podemos dar aos parâmetros de uma função nomes externos. Podemos então usar estes nomes externos quando a função for chamada para inequivocamente indicar qual argumento corresponde a qual parâmetro. Dê uma olhada na atualização do exemplo a seguir.

1
func power(base a: Int, exponent b: Int) -> Int {
2
    var result = a
3
    
4
    for _ in 1..<b {
5
        result = result * a
6
    }
7
    
8
    return result
9
}

Note que o corpo da função não mudou, pois o nome local não foi alterado. Entretanto, quando chamamos a função atualizada, a diferença é clara e o resultado muito menos confuso.

1
power(base: 2, exponent: 3)

Mesmo que o tipo de ambas as funções sejam o mesmo, (Int, Int) -> Int, as funções são diferentes. Em outras palavras, a segunda função não é uma redeclaração da primeira função. A sintaxe de chamada da segunda função pode lembrar algumas do Objective-C. Não apenas os argumentos ficam claramente descritos, a combinação do nome da função e os parâmetros descrevem a finalidade da função.

Em Swift, o primeiro parâmetro, por padrão, não tem nome externo. É por isso que a assinatura da função printMessage(_:) inclui um _ como primeiro parâmetro. Se quisermos definir um nome externo para o primeiro parâmetro, então a definição do método ficará um pouco diferente.

1
func printMessage(message message: String) {
2
    print(message)
3
}
4
5
printMessage(message: "Hello World!")

Funções Globais e Encadeadas

Os exemplos que vimos até agora são conhecidos como funções globais, porque são definidos em um escopo global. Funções também podem ser encadeadas. Funções encadeadas podem ser chamadas apenas no escopo em que são definidas. Isso significa que uma função encadeada pode ser chamada apenas pela função em que foi definida. O próximo exemplo clareia isso.

1
func printMessage(message: String) {
2
    let a = "hello world"
3
    
4
    func printHelloWorld() {
5
        print(a)
6
    }
7
}

2. Closures

O que é uma Closure?

Se você ja trabalhou com blocos em C e Objective-C ou closures em JavaScript, então você não terá dificuldade em entender o conceito das closures. Nós já trabalhos com closures neste artigo, pois funções são closures também. Vamos começar com o básico e inspecionar a anatomia de uma closure.

Uma closures é um bloco de funcionalidade que você pode passar pelo seu código. Você pode passar uma closure como um argumento de uma função ou você pode armazena-la como uma propriedade de um objeto. Closures tem muitos casos de uso.

O nome closure indica uma das características chave das closures. Uma closures captura as variáveis e constantes do contexto em que ela é definida. Isso é referido, as vezes, como fechamento sobre essas variáveis e constantes.

Sintaxe 

A sintaxe basica de uma closure não é difícil. Dê uma olhada no exemplo a seguir.

1
let a = {(a: Int) -> Int in
2
    return a + 1
3
}

A primeira coisa que você irá notar é que toda a closure fica dentro de chaves. Os parâmetros de uma closures ficam dentro de parênteses, separados do tipo de retorno pelo símbolo ->. A closures acima recebe um argumento, a, do tipo Int e retorna um Int. O corpo da closure começa depois da palavra chave in.

Closures nomeadas, ou seja, funções encadeadas e globais, são um pouco diferentes. O exemplo abaixo demonstra essas diferenças.

1
func increment(a: Int) -> Int {
2
    return a + 1
3
}

As diferenças mais importantes é o uso da palavra-chave func e a posição dos parâmetros e tipo de retorno. Uma closure começa e termina com chave, envolvendo os parâmetros, tipo de retorno e corpo. Apesar dessas diferenças, lembre-se que toda função é uma closure. Mas nem toda closure é uma função.

Closures como parâmetros

Closures são poderosas e o exemplo a seguir demonstra quão útil elas podem ser. No exemplo, criamos um array de estados. Nós chamamos a função map() no array para criar um novo array que contém apenas os primeiras duas letras de cada estado como uma string maiúscula.

1
var states = ["California", "New York", "Texas", "Alaska"]
2
3
let abbreviatedStates = states.map({ (state: String) -> String in
4
    let index = state.startIndex.advancedBy(2)
5
    return state.substringToIndex(index).uppercaseString
6
})
7
8
print(abbreviatedStates)

No exemplo acima, a função map() é chamada no array states, transformando seu conteúdo e retornando um novo array que contém os valores transformados. O exemplo também demonstra o poder da inferência de tipo do Swift. Como chamamos a função map() no array de string, o Swift sabe que o argumento do state é do tipo String. Isso significa que podemos omitir o tipo como demonstra o exemplo atualizado a seguir.

1
let abbreviations = states.map({ (state) -> String in
2
    return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString
3
})

Há muito mais coisas que podemos omitir do exemplo acima, resultando na linha a seguir.

1
let abbreviations = states.map({ state in state.substringToIndex(state.startIndex.advancedBy(2)).uppercaseString })

Deixe-me explicar o que está acontecendo. O compilador pode inferior que retornamos uma string da closure que passamos á função map(), o que significa que não há razão para incluir o tipo do retorno na definição de expressão da closure. Apesar de tudo, podemos fazer isso apenas se o corpo da closure incluir uma única instrução. Neste caso, podemos colocar esta instrução sobre a mesma linha em que definimos a closure, como mostrado no exemplo acima. Como não há tipo de retorno na definição e não há tipo de retorno precedente ao símbolo ->, podemos omitir o parênteses envolvendo os parâmetros da closure.

Nome abreviado dos argumentos

E não para por aqui, entretanto. Podemos fazer uso do nome abreviado dos argumentos para simplificar, ainda mais, a expressão da closure acima. Quando usamos a expressão da closure em uma linha, como o exemplo acima, podemos omitir a lista de parâmetros, incluindo a palavra chave in que separa os parâmetros do corpo da closure.

No corpo da closure, referenciamos os argumentos usando os nome abreviado dos argumentos que o Swift nos fornece. O primeiro argumento é referenciado por $0, o segundo por $1, etc.

No exemplo atualizado a seguir, eu omiti a lista de parâmetros e a palavra chave in e substitui o argumento state no corpo da closure com o nome abreviado do argumento, $0. A instrução resultante fica mais concisa e sem comprometer a legibilidade. 

1
let abbreviations = states.map({ $0.substringToIndex($0.startIndex.advancedBy(2)).uppercaseString })

Trailing Closures

A linguagem Swift também define um conceito conhecido como trailing closures. Se você passar uma closure como último argumento de uma função, você pode colocar esta closure fora dos parênteses da chamada da função. O exemplo a seguir demonstra como isto funciona.

1
let abbreviations = states.map() { $0.substringToIndex($0.startIndex.advancedBy(2)).uppercaseString }

Se o único argumento da função chamada for uma closure, então é possível omitir os parênteses da chamada da função.

1
let abbreviations = states.map { $0.substringToIndex($0.startIndex.advancedBy(2)).uppercaseString }

Note que isso também funciona para funções que contem múltiplas instruções. Na verdade, isso é a principal razão do trailing closures estarem disponíveis no Swift. Se uma closure é longa ou complexa e ela é o argumento de uma função, muitas vezes é melhor usar a sintaxe trailing closure.

1
let abbreviations = states.map { (state: String) -> String in
2
    let toIndex = state.startIndex.advancedBy(2)
3
    let newState = state.substringToIndex(toIndex)
4
    return newState.uppercaseString
5
}

3. Protocolos

Protocolos é um componente importante da linguagem Swift. O conceito não é difícil de entender se você conhece sobre protocolos em Objective-C ou interfaces em Java. Um protocolo define um padrão ou interface focada em uma função em particular. Ela faz isso através de definição de propriedades e métodos necessários para realizar esta tarefa. 

Definir um protocolo é similar a definição de uma classe ou estrutura. No exemplo a seguir, definimos o protocolo Repairable. O protocolo define duas propriedades, time e cost, e um método repair(). A propriedade time é apenas de leitura enquanto a propriedade cost é de leitura e escrita.

1
protocol Repairable {
2
    var time: Float { get }
3
    var cost: Float { get set }
4
    
5
    func repair()
6
}

Uma clase ou estrutura pode adotar o protocolo ficando em conformidade com ele. Isso significa que ela é obrigada a implementar as propriedades e os métodos definidos pelo protocolo. Vamos atualizar a classe Boat que implementamos no tutorial anterior.

1
class Boat: Repairable {
2
    
3
    var speed: Float = 0
4
    var lifeboats: Int = 2
5
    
6
    var time: Float = 10.0
7
    var cost: Float = 100.0
8
    
9
    func deployLifeboats() {
10
        // ...

11
    }
12
    
13
    func repair() {
14
        // ...

15
    }
16
    
17
}

É fácil assim ficar em conformidade com um tipo de protocolo. Mesmo que um protocolo não possa ser instanciado como uma classe ou estrutura, um protocolo é um tipo valido. De uma olhada no exemplo a seguir em que o protocolo Repairable é usado como o tipo de argumento de função.

1
func bringInForRepairs(toBeRepaired: Repairable) {
2
    // ...

3
}
4
5
let myDamagedBoat = Boat()
6
7
bringInForRepairs(myDamagedBoat)

Davis Allie recentemente escreveu um ótimo artigo sobre programação orientada a protocolos em Swift. Se você se interessar em aprender mais sobre protocolos e seu potencial em Swift, então eu encorajo você a ler o artigo do Davis.

4. Controle de acesso

Eu gostaria de concluir esta introdução ao Swift falando sobre controle de acesso. Como o nome sugere, controle de acesso define qual código tem acesso a qual código. Níveis de controle de acesso aplicar-se a métodos, funções, tipos, etc. A Apple simplesmente se refere a entidades. Há três níveis de controle de acesso: público, interno e privado.

  • Public: Entidades marcadas como públicas são acessíveis por entidades tanto do mesmo modulo como de outros modulos, como um projeto, framework ou library. Este nível de acesso é ideia para expor a interface de um framework.
  • Internal: Este é o nível de acesso padrão. Em outras palavras, se não especificar o nível de controle de acesso, o internal será aplicado. Uma entidade com um nível de acesso internal é acessível apenas por entidades definidas no mesmo modulo.
  • Private: Uma entidade declarada como private é acessível apenas pelas entidades definidas no mesmo arquivo fonte.

Dê uma olhada no exemplo a seguir em que atualizei a classe Boat. A própria classe foi marcada como public, o que significa que ela é acessível de qualquer lugar. A propriedade cost é implicitamente marcada como internal, já que não especificamos um nível de acesso de controle. O mesmo serve para o método deployLifeboats().

1
public class Boat {
2
    
3
    public var speed: Float = 0
4
    public var lifeboats: Int = 2
5
    
6
    var cost: UInt = 100
7
    
8
    func deployLifeboats() {
9
        // ...

10
    }
11
    
12
    private func scheduleMaintenance() {
13
        
14
    }
15
}

O método sheduleMaintenance() foi marcado como private, o que significa que ele pode ser chamado apenas por entidades definidas no mesmo arquivo fonte em que a classe Boat for definida. É importante entender isso, por que há uma ligeira diferença com o que as outras linguagens de programação consideram um método ou propriedade private.

Se marcamos a classe Boat como internal removendo a palavra chave public, o compilador irá emitir um aviso. Ele nos diz que não podemos marcar speed e lifeboats com public já que o Boat está marcado como internal. O compilador está certo. Não faz sentido marcar propriedades de uma classe internal como public.

Access Control WarningAccess Control WarningAccess Control Warning

Conclusão

A linguagem Swift é fácil de aprender, mas ainda há muito mais do que o que tratamos nos últimos dois tutoriais. Você irá aprender mais sobre Swift, uma vez que começarmos a criar aplicativos. No próximo artigo, olharemos o SDK do iOS.

Se você tiver qualquer pergunta ou comentário, você pode deixa-los nos comentários abaixo ou me procurar no Twitter.

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

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