How to represent Core Data optional Scalars (Bool/Int/Double/Float) in Swift?
(first noticed on: Xcode 8.2.1, iOS 10, Swift 3) (still present as of: Xcode 9 beta 3, iOS11, Swift 4)
We all know that the Core Data
concept of optionals
precedes and is not strictly tied to the Swift
concept of optionals
.
And we have accepted that even if a Core Data
attribute is marked as Non-optional
, the auto-generated NSManagedObject
subclass has an optional
type:
(some people manually remove the ?
with no adverse effects, some don't, but that's beside the point)
(From here on the example and screenshots are for Bool
properties, but same goes for Int16/32/64
, Double
, Float
)
Now I noticed the reverse - when a Core Data
attribute of type Bool
is marked as Optional
(and Use Scalar Type
is chosen, which Xcode does by default), the auto-generated class has a variable of a non-optional
type.
Does this make sense? Is it a bug? Is the behaviour documented anywhere?
And most importantly - how do I actually represent an optional Bool
?
I can think of some work-arounds, but they don't seem ideal (e.g. not using scalars, but going back to NSNumber
representation of the Bool
. Or (even worse) having a separate Bool
called something like isVerified_isSet
)
Note: I did a couple more tests and if the Default Value
is set to None
or to NO
, then the variable gets saved as false
(even if I never actually assign it in my code). If the Default Value
is set to YES
, then the variable gets saved as true
. Still, this means that (apparently) there is no way to logically represent this variable as not having been set yet.
Solution 1:
I see the same thing, and I consider it to be a bug. It's not documented anywhere that I can find. Apparently Core Data is applying Objective-C style assumptions here, where a boolean defaults to NO
, and an integer defaults to 0. The Core Data/Swift interface has some rough edges, and this is one I hadn't considered before.
It's a good find but I think you're stuck with it until Apple addresses it. You already know the best workarounds, which I agree aren't great. I recommend filing a bug.
Solution 2:
This happens because Objective-C scalar types do not have a notion of nil value. Source: handling-core-data-optional-scalar-attributes
Solution 3:
I would rather use Objective-C types to manage these cases than Swift types. In these cases, for scalars types, you can use NSNumber.
@NSManaged public var myDouble: NSNumber?
In the model myDouble is an optional double with nil value by default.
To get the real value you only need to use:
myEntity.myDouble?.doubleValue
Solution 4:
If you end up here with this:
@NSManaged var boolAttribute: Bool
and it is not being seen in Objective-C, and you have already disabled "Optional" and enabled "Use Scalar Type" on those attributes, then do yourself a favour.
Double check you have imported your Swift bridging header into that Objective-C file.
I did not and, well, I was most of the way to changing my Bools to NSNumbers before smacking my head and realising how foolish I had been.