Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift if let evaluates successfully on Optional(nil)

I have a custom object called Field. I basically use it to define a single field in a form.

class Field {

    var name: String
    var value: Any?

    // initializers here...
}

When the user submits the form, I validate each of the Field objects to make sure they contain valid values. Some fields aren't required so I sometimes deliberately set nil to the value property like this:

field.value = nil

This seems to pose a problem when I use an if-let to determine whether a field is nil or not.

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 set breakpoints in the above if-else and when field.value has been deliberately set to nil, it goes through the if-let block, not the else. However, for the fields whose field.value I left uninitialized and unassigned, the program goes to the else block.

I tried printing out field.value and value inside the if-let block:

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

And this is what I get:

field.value: Optional(nil), value: nil

So I thought that maybe with optionals, it's one thing to be uninitialized and another to have the value of nil. But even adding another if inside the if-let won't make the compiler happy:

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

How do I get around this? I just want to check if the field.value is nil.

like image 276
MLQ Avatar asked Oct 20 '22 01:10

MLQ


1 Answers

I believe this is because Any? allows any value and Optional.None is being interpreted as just another value, since Optional is an enum!

AnyObject? should be unable to do this since it only can contain Optional.Some([any class object]), which does not allow for the case Optional.Some(Optional) with the value Optional.None.

This is deeply confusing to even talk about. The point is: try AnyObject? instead of Any? and see if that works.


More to the point, one of Matt's comment mentions that the reason he wants to use Any is for a selection that could be either a field for text input or a field intended to select a Core Data object.

The Swifty thing to do in this case is to use an enum with associated values, basically the same thing as a tagged/discriminated union. Here's how to declare, assign and use such an 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
    }
}

Using an enum like this, there's no need to worry about which things could be hiding inside that Any?. There are two cases and they are documented. And of course, the selection variable can be an optional without any worries.

like image 94
Jesper Avatar answered Oct 23 '22 22:10

Jesper