Swift: Teste opcional para nada

137

Estou usando o Xcode 6 Beta 4. Tenho uma situação estranha em que não consigo descobrir como testar adequadamente os opcionais.

Se eu tiver um xyz opcional, é a maneira correta de testar:

if (xyz) // Do something

ou

if (xyz != nil) // Do something

Os documentos dizem para fazê-lo da primeira maneira, mas descobri que, às vezes, a segunda maneira é necessária e não gera um erro do compilador, mas outras vezes, a segunda maneira gera um erro do compilador.

Meu exemplo específico está usando o analisador XML GData em ponte para swift:

let xml = GDataXMLDocument(
    XMLString: responseBody,
    options: 0,
    error: &xmlError);

if (xmlError != nil)

Aqui, se eu fizesse:

if xmlError

sempre retornaria verdadeiro. No entanto, se eu fizer:

if (xmlError != nil)

então funciona (como funciona no Objective-C).

Existe algo no XML do GData e a maneira como ele trata os opcionais que estão faltando?

tng
fonte
4
Podemos ver um exemplo completo para seu caso inesperado e casos de erro, por favor? (E eu sei que é difícil, mas tente começar a perder os suportes em torno das condicionais!)
Matt Gibson
1
isso foi alterado no Xcode 6 beta 5
newacct 5/14
Acabei de atualizar a pergunta com o caso inesperado.
tng
Ainda não atualizei para o beta 5. Eu farei isso em breve; bom ver ?? em Swift e comportamento opcional mais consistente.
tng

Respostas:

172

No Xcode Beta 5, eles não permitem mais:

var xyz : NSString?

if xyz {
  // Do something using `xyz`.
}

Isso produz um erro:

não está em conformidade com o protocolo 'BooleanType.Protocol'

Você precisa usar um destes formulários:

if xyz != nil {
   // Do something using `xyz`.
}

if let xy = xyz {
   // Do something using `xy`.
}
ktzhang
fonte
5
Alguém pode explicar por que xyz == nil não está funcionando?
Christoph
O segundo exemplo deve ler: // fazer algo usando xy (que é a principal diferença em casos de uso entre as duas variantes)
Alper
@Christoph: A mensagem de erro é a constante 'xyz' usada antes de ser inicializada. Eu acho que você precisa acrescentar '= nil' no final da linha de declaração de xyz.
User3207158
Para quem é novato em trabalhar rápido como eu sou e está se perguntando: você ainda precisa desembrulhar o xyz dentro do bloco if. É porém segura, mesmo se você forçar unwrap
Omar
@ Omar Essa é a beleza da segunda forma, você usa xy dentro do bloco sem precisar desembrulhá-lo.
Erik Doernenburg 27/11/19
24

Para adicionar às outras respostas, em vez de atribuir a uma variável com nome diferente dentro de uma ifcondição:

var a: Int? = 5

if let b = a {
   // do something
}

você pode reutilizar o mesmo nome de variável assim:

var a: Int? = 5

if let a = a {
    // do something
}

Isso pode ajudar a evitar a falta de nomes de variáveis ​​de criativos ...

Isso tira proveito do sombreamento variável suportado no Swift.

zevij
fonte
18

Uma das maneiras mais diretas de usar opcionais é o seguinte:

Supondo que xyzseja do tipo opcional, como Int?por exemplo.

if let possXYZ = xyz {
    // do something with possXYZ (the unwrapped value of xyz)
} else {
    // do something now that we know xyz is .None
}

Dessa forma, você pode testar se xyzcontém um valor e, se houver, trabalhar imediatamente com esse valor.

Com relação ao erro do seu compilador, o tipo UInt8não é opcional (observe o número '?') E, portanto, não pode ser convertido em nil. Verifique se a variável com a qual você está trabalhando é opcional antes de tratá-la como uma.

Isaac Drachman
fonte
1
Acho esse método ruim porque crio uma nova variável (constante) desnecessariamente, e preciso criar um novo nome que na maioria das vezes seja pior do que o anterior. Leva mais tempo para escrever e para o leitor.
Lombas
3
É o método padrão e o método recomendado para desembrulhar uma variável opcional. "'se deixe" é seriamente poderoso.
precisa saber é o seguinte
@ gnasher729 mas e se você quiser usar apenas a parte else #
Brad Thomas
15

Swift 3.0, 4.0

Existem principalmente duas maneiras de verificar opcional para zero. Aqui estão exemplos com comparação entre eles

1. se deixar

if leté a maneira mais básica de verificar opcional para zero. Outras condições podem ser anexadas a essa verificação nula, separadas por vírgula. A variável não deve ser nula para se mover para a próxima condição. Se apenas a verificação nula for necessária, remova condições extras no código a seguir.

Fora isso, se xnão for nulo, o fechamento if será executado e x_valestará disponível dentro. Caso contrário, o fechamento else será acionado.

if let x_val = x, x_val > 5 {
    //x_val available on this scope
} else {

}

2. guarda deixe

guard letpode fazer coisas semelhantes. Seu principal objetivo é torná-lo logicamente mais razoável. É como dizer Certifique-se de que a variável não seja nula; caso contrário, pare a função . guard lettambém pode fazer a verificação de condição extra como if let.

As diferenças são que o valor desembrulhado estará disponível no mesmo escopo guard let, conforme mostrado no comentário abaixo. Isso também leva a tal ponto que no fecho de outra pessoa, o programa tem de sair do escopo atual, por return, breaketc.

guard let x_val = x, x_val > 5 else {
    return
}
//x_val available on this scope
Fangming
fonte
11

Do guia de programação rápida

Se declarações e desembrulhar forçado

Você pode usar uma instrução if para descobrir se um opcional contém um valor. Se um opcional tiver um valor, ele será avaliado como verdadeiro; se não tiver nenhum valor, será avaliado como falso.

Portanto, a melhor maneira de fazer isso é

// swift > 3
if xyz != nil {}

e se você estiver usando a xyzinstrução if se. Em seguida, você pode desembrulhar a xyzinstrução if na variável constante xyz.

if let yourConstant = xyz{
      //use youtConstant you do not need to unwrap `xyz`
}

Esta convenção é sugerida por applee será seguida por devlopers.

codester
fonte
2
No Swift 3 você tem que fazer if xyz != nil {...}.
Psythirl
No Swift 3, os opcionais não podem ser usados ​​como booleanos. Primeiro porque é um C-ismo que nunca deveria estar no idioma em primeiro lugar, e segundo porque causa confusão absoluta com o Bool opcional.
precisa saber é o seguinte
9

Embora você ainda precise comparar explicitamente um opcional nilou usar a ligação opcional para extrair adicionalmente seu valor (ou seja, os opcionais não são implicitamente convertidos em valores booleanos), é importante observar que o Swift 2 adicionou a guardinstrução para ajudar a evitar a pirâmide de destruição ao trabalhar com vários valores opcionais.

Em outras palavras, suas opções agora incluem a verificação explícita de nil:

if xyz != nil {
    // Do something with xyz
}

Ligação opcional:

if let xyz = xyz {
    // Do something with xyz
    // (Note that we can reuse the same variable name)
}

E guarddeclarações:

guard let xyz = xyz else {
    // Handle failure and then exit this code block
    // e.g. by calling return, break, continue, or throw
    return
}

// Do something with xyz, which is now guaranteed to be non-nil

Observe como a ligação opcional comum pode levar a um recuo maior quando há mais de um valor opcional:

if let abc = abc {
    if let xyz = xyz {
        // Do something with abc and xyz
    }        
}

Você pode evitar esse aninhamento com guardinstruções:

guard let abc = abc else {
    // Handle failure and then exit this code block
    return
}

guard let xyz = xyz else {
    // Handle failure and then exit this code block
    return
}

// Do something with abc and xyz
Chris Frederick
fonte
1
Como mencionado acima, você também pode fazer isso para se livrar das estatísticas opcionais aninhadas. se abc = abc? .xyz? .algo {}
Suhaib 23/09/16
1
@ Suhaib Ah, é verdade: você também pode usar o encadeamento opcional ( developer.apple.com/library/prerelease/content/documentation/… ) para acessar rapidamente as propriedades aninhadas. Não mencionei aqui porque pensei que poderia ser muito tangente à pergunta original.
Chris Frederick
Ótima resposta! Mas Swift, OMG ... ainda está muito longe de ser um programador!
turingtested
@turingtested Obrigado! Na verdade, acho que isso é amigável para programadores, no sentido de garantir que você não tentará acidentalmente usar um nilvalor, mas concordo que a curva de aprendizado é um pouco íngreme. :)
Chris Frederick
4

Extensão de Protocolo Swift 5

Aqui está uma abordagem usando a extensão de protocolo para que você possa incorporar facilmente uma verificação nula opcional:

import Foundation

public extension Optional {

    var isNil: Bool {

        guard case Optional.none = self else {
            return false
        }

        return true

    }

    var isSome: Bool {

        return !self.isNil

    }

}

Uso

var myValue: String?

if myValue.isNil {
    // do something
}

if myValue.isSome {
    // do something
}
Brody Robertson
fonte
Recebi alguns votos negativos sobre esta resposta e gostaria de saber o porquê. Pelo menos, comente se você está indo para o voto negativo.
Brody Robertson
1
Provavelmente é o swiftanálogo do pythonistasque tenta manter a linguagem em alguma forma de pureza pitônica. Minha vez? Acabei de colocar seu código no meu mix de utilitários.
javadba 17/06
2

Agora você pode fazer rapidamente o seguinte, o que lhe permite recuperar um pouco do objetivo-c if nil else

if textfieldDate.text?.isEmpty ?? true {

}
Nicolas Manzini
fonte
2

Em vez de if, o operador ternário pode ser útil quando você deseja obter um valor com base no fato de algo ser nulo:

func f(x: String?) -> String {
    return x == nil ? "empty" : "non-empty"
}
qed
fonte
xyz == nilexpressão não funciona, mas x != nilfunciona. Não sei porque.
Andrew
2

Outra abordagem, além de usar ifou guardinstruções para fazer a ligação opcional, é estender Optionalcom:

extension Optional {

    func ifValue(_ valueHandler: (Wrapped) -> Void) {
        switch self {
        case .some(let wrapped): valueHandler(wrapped)
        default: break
        }
    }

}

ifValuerecebe um fechamento e o chama com o valor como argumento quando o opcional não é nulo. É usado desta maneira:

var helloString: String? = "Hello, World!"

helloString.ifValue {
    print($0) // prints "Hello, World!"
}

helloString = nil

helloString.ifValue {
    print($0) // This code never runs
}

Você provavelmente deve usar um ifou, guardno entanto, essas são as abordagens mais convencionais (portanto familiares) usadas pelos programadores Swift.

Tiago
fonte
2

Uma opção que não foi especificamente abordada é usar a sintaxe de valor ignorado do Swift:

if let _ = xyz {
    // something that should only happen if xyz is not nil
}

Gosto disso, pois a verificação nilparece deslocada em um idioma moderno como o Swift. Acho que a razão pela qual ela se sente deslocada nilé basicamente um valor sentinela. Acabamos com os sentinelas em praticamente todos os outros lugares da programação moderna, então nilparece que isso também deveria acontecer.

Ben Lachman
fonte
1

Você também pode usar Nil-Coalescing Operator

O nil-coalescing operator( a ?? b) desembrulha um opcional ase ele contiver avalor ou retorna ao valor padrão, bse aestiver nil. A expressão a é sempre de um tipo opcional. A expressão bdeve corresponder ao tipo que é armazenado dentro a.

let value = optionalValue ?? defaultValue

Se optionalValuefor nil, atribui valor automaticamente adefaultValue

yoAlex5
fonte
Eu gosto disso, porque oferece a opção fácil de especificar um valor padrão.
thowa 20/03
0
var xyz : NSDictionary?

// case 1:
xyz = ["1":"one"]
// case 2: (empty dictionary)
xyz = NSDictionary() 
// case 3: do nothing

if xyz { NSLog("xyz is not nil.") }
else   { NSLog("xyz is nil.")     }

Este teste funcionou como esperado em todos os casos. BTW, você não precisa dos suportes ().

Mundi
fonte
2
O caso do OP está preocupado é: if (xyz != nil).
Zaph 02/08/19
0

Se você tem condicional e gostaria de desembrulhar e comparar, que tal aproveitar a avaliação de curto-circuito da expressão booleana composta, como em

if xyz != nil && xyz! == "some non-nil value" {

}

É verdade que isso não é tão legível como alguns dos outros posts sugeridos, mas faz o trabalho e é um tanto sucinto do que as outras soluções sugeridas.

RT Denver
fonte