Em Swift, nil é diferente de Zero
14 Feb 2016Objective-C
possui uma característica muito interessante e polêmica, enviar mensagens para nil
é válido e não lança excessã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 o 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 porque?
A resposta está em no funcionamento do optional chaining: ?
tem um comportamento semelhante ao !
(force unwrapping), com a diferença que se o valor opcional for nil
não é lançado um erro de runtime. Como não ocorre a interrupção do programa e afim de evitar inconsistências o resultado de chamadas de métodos, propriedades e subscripts sempre vão 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 devem haver mais uma dezena de maneiras de escrever mas acho que já deu para ter uma idéia. Provavelmente o erro desse caso é transpor exatamente a mesma lógica do Objective-C
para Swift
.
Update 2016/02/16 - 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 assunto1 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.
Diogo Tridapalli
@diogot
-
Assumindo que eu seja um cara teimoso e me recuse a usar o
isEmpty
↩