Objective-C possui uma característica muito interessante e polêmica, enviar mensagens para nil é válido e não lança exceção como em outras linguagens. Não vou discutir se isso é bom ou ruim, mas é algo que gosto e uso frequentemente quando programo nessa linguagem.
Um uso comum seria quando é necessário testar se um array é vazio:
if(array.count == 0) { NSLog(@"empty array"); }
Isso funciona porque o retorno de qualquer mensagem enviada para nil é 0, nil ou NULL, dependendo do tipo de retorno da mensagem, semanticamente diferentes mas tecnicamente iguais a ZERO.
Então se array for nil, array.count retorna 0, mais uma vitória do bem e menos código escrito ?.
Sem pensar muito, podemos escrever o equivalente em Swift:
if array.count == 0 { print("empty array") }
Se array for um opcional, novamente sem pensar muito, poderíamos fazer um optional chaining, e só colocar um ?:
if array?.count == 0 { print("empty array") }
NÃO!! Quando array = nil, array?.count == 0 é falso, mas por quê?
A resposta está no funcionamento do optional chaining: ?, que tem um comportamento semelhante ao ! (force unwrapping), com a diferença de que se o valor opcional for nil, não é lançado um erro de runtime. Como não ocorre a interrupção do programa, a fim de evitar inconsistências, o resultado de chamadas de métodos, propriedades e subscripts sempre vai retornar um opcional, mesmo que o tipo original não seja. Por exemplo:
var array: [Int]? let count = array?.count // count is Int? not Int
Isso significa que quando array = nil, count = nil que não é igual a 0 e, por isso, o teste anterior falha! Ou seja em Swift, nil != 0!
Para escrever o teste de maneira que funciona, temos várias opções.
Definir que quando o resultado do count for nil o resultado esperado é 0:
if (array?.count ?? 0) == 0 { print("empty array") }
Testar o nil e fazer force unwrapping. Não me agrada o force unwrapping, sei que nesse caso nunca aconteceria um erro de runtime, mas prefiro evitar ao máximo o !:
if array == nil || array!.count == 0 { print("empty array") }
Por algum motivo ainda desconhecido para mim, nil é menor que qualquer Int. Então, temos uma opção que eu não recomendo, ¯\_(ツ)_/¯:
if !(array?.count > 0) { print("empty array") }
Com certeza deve haver mais uma dezena de maneiras de escrever, mas acho que já deu para ter uma ideia. Provavelmente o erro desse caso é transpor exatamente a mesma lógica do Objective-C para Swift.
Update: O Fabri e o Koga lembraram que o Array adota o protocolo CollectionType e, portanto, o isEmpty seria mais adequado:
if array?.isEmpty ?? true { print("empty array") }
O Fabri pensou mais no assunto e propôs uma extensão para o Optional de IntegerType que evitaria o problema apresentado:
extension Optional where Wrapped: IntegerType { var valueOrZero: Wrapped { return self ?? 0 } }
Assim o teste ficaria:
if (array?.count).valueOrZero == 0 { print("empty") }
Um bom exercício para evitar o erro, mas acho que a versão com o isEmpty ainda fica mais legível.
Criticas, sugestões e comentários são sempre bem-vindos, é só me pingar no @diogot ou no slack do iOS Dev BR.