Ronaldo
Ronaldo Desenvolvedor, pai, cidadão do mundo.

Optionals: quebrando tudo com swift!

Esta é, sem dúvida, a feature mais quente do swift.

Optionals: quebrando tudo com swift!

Entre todas as características da linguagem Swift, a que mais me chamou a atenção, quando a Apple apresentou a linguagem pela primeira vez, foi o uso de opcionais. Essa foi, sem dúvida, uma grande sacada e uma forma bem interessante de homogeneizar um conceito já há muito tempo arraigado na computação: o problema do valor inicial.

A ideia por trás do opcional é simples: criar uma variável cujo valor seja opcional. Isto quer dizer o seguinte: esta variável é de um tipo bem determinado, mas o valor que ela carrega é opcional. Ou seja, em algum momento no ciclo de vida desta variável ela conterá um valor que indica que ela está vazia, ou seja, não tem valor algum.

Para entender por que este conceito é legal, vamos pensar no seguinte problema: precisamos determinar o maior número de um conjunto finito de números. Se fôssemos resolver este problema em C, teríamos algo como isto:

    int max (int *numbers, int size) {
      int greater = -1;
      int *p = numbers;
      while (p - numbers < size) {
        if (*p > greater) {
          greater = *p;
        }
        ++p;
        return greater;
      }
    }

Considere que numbers é um array de números inteiros e size é o tamanho deste array. Aritméticas de ponteiro à parte, o algoritmo é bem simples: ele inicializa greater com um valor inicial e sai comparando este número com todos os demais existentes no array.

É um algoritmo sem-vergonha. Se a lista de números for toda negativa com números menores que -1, está feita a merda. Uma saída simples é inicializar greater com o primeiro elemento do array e sair comparando com o resto, o que resolveria o problema, mas não ilustraria o que quero com os opcionais. Reescrevendo esta jóia em Swift, teremos algo assim:

    func max (numbers: [Int])-> Int {
        var max: Int?
        for x in numbers {
            if max == nil || max! < x {
                max = x
            }
        }
        return max!
    }

Observe que max é do tipo inteiro, mas com uma interrogação na frente. Isto quer dizer que max é uma variável opcional. A despeito da péssima escolha de nomes para a função e sua variável local, há alguma coisa interessante acontecendo por aqui.

Para que você acesse o valor de uma variável opcional é necessário realizar um processo chamado de unwrapping. O que acontece é o seguinte: ao declarar uma variável como opcional o compilador gera uma estrutura de código em torno dela para que um valor especial, nil, seja armazenado nela. A palavra nil é uma contração da palavra latina nihil, que significa, no bom português, nada.

Em Objective C era possível usar nil como valor inválido, mas apenas para variáveis estruturadas como classes e estruturas. Variáveis de tipos integrais não podiam usar esta característica, o que criava um hiato na linguagem.

Bom, em Swift você pode usar opcional em qualquer tranqueira que inventar, não importa se sua variável é estruturada ou integral. Isso homogeiniza as coisas. No nosso exemplinho sem-vergonha, há uma verificação explícita contra nil, apesar de não haver nenhuma inicialização explícita para este valor. Isto se deve ao fato do runtime do Swift inicializar automaticamente os opcionais com o valor nil.

Para realizar o unwrapping é preciso usar dois pequenos operadores: ? ou !. No exemplo, usei o operador !, que faz o unwrapping forçado, enquanto que o operador ? faz o unwrapping condicionado. Qual a diferença entre eles?

Se a variável tiver um valor nil, ou seja, um valor inválido, o unwrapping forçado vai provocar uma falha de runtime e sua aplicação vai cair de forma descontrolada. No caso do unwrapping condicionado, o runtime tentará extrair o valor da variável. Se a variável for nil, nada acontece.

Assim, você deve usar o unwrapping forçado sempre que tiver certeza de que a variável tem um valor válido nela, como foi o caso do nosso algoritmo sem-vergonha.

Uma construção muito comum para realizar o unwrapping é através do if:

    if let valorUnwrapped = valorOpcional {
    }

Se valorOpcional tiver um valor válido, ele é atribuído a valorUnwrapped e você pode usar esta constante dentro do if sem se preocupar com o danadinho do nil. Isto é equivalente a fazer o seguinte:

    if valorOpcional != nil {
        let valorUnwrapped = valorOpcional!
    }

Unwrapping Automático

Você pode declarar uma variável assim:

var idade: Int!

Isso daí é muito comum quando você cria outlets no seu código, quando está programando no Cocoa Touch ou no Cocoa. Esta sintaxe quer dizer o seguinte: idade é uma variável opcional, mas cujo unwrapping será implícito.

Assim, pode-se fazer o teste deste tipo de variável contra nil para verificar-se se a variável é válida. Tentar acessar uma variável opcional sem que ela tenha um valor válido vai provocar um erro no seu programa.

Assim:

var outro = idade + 1

provoca um erro em tempo de execução. Ao tentar fazer o unwrapping de idade, runtime verifica que não há valor válido e aí a casa cai. O ideal, aqui seria fazer algo como abaixo

    var outro = 0
    if idade == nil {
     outro = idade + 1
    }

Observe que não foi preciso indicar o unwrapping de idade. O unwrapping é feito automaticamente pelo runtime.

Anti-patterns

Já falei do trem bonito. Agora é hora de falar do trem feio. Tem um raio de um povo que faz unwrapping desta forma:

    if let valorOpcional = valorOpcional {
    }

A ideia é aproveitar o nome da variável. Nem preciso dizer que isso é uma péssima ideia. Funciona, mas deixa seu código bem mais escroto de ler. Muitas vezes o povo usa esta forma apenas para saber se a variável é válida ou não.

Outro anti-pattern interessante é fazer isto para ativar métodos de objetos opcionais:

    if let objetoOpcional = objetoOpcional {
     objetoOpcional.metodoDescolado()
    }

Por que isso é ruim? Por que dá para fazer a mesma coisa sem essa tanteira de código:

objetoOpcional?.metodoDescolado()

Faz a mesma coisa. O ? quer dizer que fazemos o unwrapping condicionado, ou seja, se tiver algo lá, executa o método, senão não faz nada.

Bem, é isso aí. Espero que isso esclareça dúvidas quanto ao uso de opcionais no seu programa.

Happy Coding!