2014-09-16 2 views
5

Ho un oggetto personalizzato chiamato Campo. Fondamentalmente lo uso per definire un singolo campo in una forma.Swift if let valuta correttamente su Opzionale (nil)

class Field { 

    var name: String 
    var value: Any? 

    // initializers here... 
} 

Quando l'utente invia il modulo, ho convalidare ciascuna delle Field oggetti per assicurarsi che essi contengono valori validi. Alcuni campi non sono necessari in modo a volte deliberatamente nil alla proprietà value in questo modo:

field.value = nil 

Questo sembra costituire un problema quando uso un se-let per determinare se un campo è nullo o meno.

if let value = field.value { 
    // The field has a value, ignore it... 
} else { 
    // Add field.name to the missing fields array. Later, show the 
    // missing fields in a dialog. 
} 

I impostare i punti di interruzione nel sopra if-else e quando field.value è stato deliberatamente a zero, esso passa attraverso il blocco se-lasciare, non il resto. Tuttavia, per i campi di cui field.value non è stato inizializzato e non assegnato, , il programma passa al blocco else.

ho provato stampare field.value e value all'interno del blocco se-let:

if let value = field.value { 
    NSLog("field.value: \(field.value), value: \(value)") 
} 

E questo è ciò che ottengo:

field.value: Opzionale (zero), il valore di: nil

Quindi ho pensato che forse con optionals, una cosa è non inizializzata e un'altra per avere il valore e di zero. Ma aggiungendo un altro se dentro l'if-let non renderemo felice il compilatore:

if let value = field.value { 
    if value == nil { // Cannot invoke '==' with an argument list of type '(Any, NilLiteralConvertible)' 
    } 
} 

Come aggirare questo? Voglio solo verificare se il field.value è nil.

+1

Impossibile riprodurre. Quale versione di Xcode usi? –

+0

Xcode 6 GM. Ho provato a ricominciare, ancora senza fortuna. :( –

+0

Puoi pubblicare un esempio completo autosufficiente che dimostri il problema? –

risposta

3

Credo che questo sia perché Any? consente qualsiasi valore e Optional.None viene interpretato come solo un altro valore, dal momento che Optional è un enum!

AnyObject? dovrebbe essere in grado di fare questo perché solo può contenere Optional.Some([any class object]), che non consente per il caso Optional.Some(Optional) con il valore Optional.None.

Questo è profondamente fuorviante anche solo per parlare. Il punto è: prova AnyObject? invece di Any? e vedi se funziona.


Più precisamente, uno dei commento di Matt afferma che la ragione per cui vuole usare Any è per una selezione che potrebbe essere o un campo per l'immissione di testo o in un campo destinato a selezionare un oggetto Core Data.

La cosa Swifty da fare in questo caso è utilizzare uno enum with associated values, praticamente la stessa cosa di tagged/discriminated union.Ecco come dichiarare, assegnare e utilizzare tale enum:

enum DataSelection { 
    case CoreDataObject(NSManagedObject) 
    case StringField(String) 
} 

var selection : DataSelection? 

selection = .CoreDataObject(someManagedObject) 

if let sel = selection { // if there's a selection at all 
    switch sel { 
     case .CoreDataObject(let coreDataObj): 
      // do what you will with coreDataObj 
     case .StringField(let string): 
      // do what you will with string 
    } 
} 

Utilizzando un enum come questo, non c'è bisogno di preoccuparsi che le cose potrebbero essere nascosti all'interno di quella Any?. Ci sono due casi e sono documentati. E, naturalmente, la variabile di selezione può essere un optional senza preoccupazioni.

+1

La risposta corretta è" evitare "Any' a tutto costa ". Se stai usando opzionale 'Any', sei fregato. – Sulthan

+2

Sì, perché non sembra che ci sia un modo per la lingua di distinguere tra "Optional.None" che è inteso per l'uso come valore effettivo o per contrassegnare l'assenza di un valore in opzionale "Any". – Jesper

0

C'è un suggerimento per sostituire il mio tipo Any? con un enum ma non ho potuto ottenere questo errore dalla mia testa. Cambiare il mio approccio non cambia il fatto che qualcosa è sbagliato con il mio attuale e ho dovuto capire come sono arrivato a un output Optional(nil).

Sono riuscito a riprodurre l'errore scrivendo il seguente controller di visualizzazione in un nuovo progetto a vista singola. Si noti la firma init.

import UIKit 

class Field { 
    var name: String = "Name" 
    var value: Any? 

    init(_ name: String, _ value: Any) { 
     self.name = name 
     self.value = value 
    } 
} 

class AppState { 
    var currentValue: Field? 
} 

class ViewController: UIViewController { 
    override func viewDidLoad() { 
     super.viewDidLoad() 

     let f = Field("Amount", AppState().currentValue) 
     NSLog("\(f.value)") 

    } 
} 

In breve, ero di passaggio un valore nullo (AppState().currentValue) per un inizializzatore che accetta Any, e assegna a una struttura il cui tipo è Any?. La cosa divertente è che se qui sono passato direttamente nil invece, il compilatore si lamenta:

let f = Field("Amount", nil) // Type 'Any' does not conform to protocol 'NilLiteralConvertible' 

Sembra che da qualche parte lungo la strada, Swift avvolge il valore nil di AppState().currentValue in un optional, da qui Optional(nil).

+0

'nil' non esiste come valore (solo come parola chiave), ma' Optional.None' è il valore predefinito di qualsiasi variabile opzionale e stampa 'nil' quando viene chiesto di descriversi. – Jesper