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
.
There's a tip to replace my
Any?
type with an enum but I couldn't get this error out of my head. Changing my approach doesn't change the fact that something is wrong with my current one and I had to figure out how I arrived at anOptional(nil)
output.I was able to reproduce the error by writing the following view controller in a new single-view project. Notice the
init
signature.In short, I was passing a nil value (
AppState().currentValue
) to an initializer that acceptsAny
, and assigns it to a property whose type isAny?
. The funny thing here is if I directly passednil
instead, the compiler will complain:It seems that somewhere along the way, Swift wraps the
nil
value ofAppState().currentValue
in an optional, henceOptional(nil)
.I believe this is because
Any?
allows any value andOptional.None
is being interpreted as just another value, sinceOptional
is an enum!AnyObject?
should be unable to do this since it only can containOptional.Some([any class object])
, which does not allow for the caseOptional.Some(Optional)
with the valueOptional.None
.This is deeply confusing to even talk about. The point is: try
AnyObject?
instead ofAny?
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:
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.