I am working on a set of classes that represent entities and their properties which then can create an editor table view from the entity dynamically. These properties are using generics to capture the property type. In order to use KVO and generate an automatic setter, these properties contain a key path. Here is a very simplified version of the property class:
class XUEntityProperty<Entity: NSManagedObject, Value> {
let keyPath: String
var customSetter: ((Entity, Value) -> Void)?
func setValue(value: Value, onEntity entity: Entity) {
/// If custom setter is set, use it.
if let setter = self.customSetter {
setter(entity, value)
return
}
/// Otherwise set the object using the keypath.
guard let objValue = value as? AnyObject else {
XUThrowAbstractException() // Use custom setter
}
entity.setValue(objValue, forKeyPath: self.keyPath)
}
}
This works very well with almost anything. The issue is when it comes to optionals. E.g.:
let property = XUEntityProperty<MyEntity, NSDate?>(keyPath: "optionalDate")
The issue here is that in the setValue
method, the cast to AnyObject
will fail since the value is Optional<NSDate>
, which cannot be cast to AnyObject
- objValue as? NSDate
will return nil
even if the objValue
is .Some(_)
.
And I'm looking for a solution how to solve this within the automatic setter by detecting and unwrapping the Optional
.
Any cast I attempt leads to the compiler complaining that casting to a more optional type, or eventually enwraps the Optional
inside another Optional
.
Does anyone know how to detect if a value of Any
is kind of Optional
and if so, extract the value from the optional, casting it to AnyObject
?
To give you an example to try in a playground:
let any: Any = Optional<String>("123")
any.dynamicType // -> Optional<String>.Type
var object: AnyObject? = nil
/// ... -> put value from `any` to `object`.
You can check for optional type by adding a dummy protocol and using the is
for type check, and there after the Mirror(..)
for extracting the actual typed value from the optional Any
:
protocol IsOptional {}
extension Optional : IsOptional {}
/* Detect if any is of type optional */
let any: Any = Optional<String>("123")
var object : AnyObject? = nil
switch any {
case is IsOptional:
print("is an optional")
if let (_, a) = Mirror(reflecting: any).children.first {
object = a as? AnyObject
}
default:
print("is not an optional")
} /* Prints "is an optional" */
/* Detect if any2 is of type optional */
let any2: Any = String("123")
switch any2 {
case is IsOptional:
print("is an optional")
// ...
default:
print("is not an optional")
} /* Prints "is not an optional" */
Charlie Monroes own final solution below is very neat (+1!). I thought I'dd add one addition to it.
Given that protocol _XUOptional
has been defined and Optional
type extended by it (as in Charlies answer), you can can handle events both when any
is optional or not in one single line, using optional chaining and the nil coalescing operator:
let anyOpt: Any = Optional<String>("123")
let anyNotOpt: Any = String("123")
var object: AnyObject?
object = (anyOpt as? _XUOptional)?.objectValue ?? (anyOpt as? AnyObject)
/* anyOpt is Optional(..) and left clause of nil coalescing operator
returns the unwrapped .objectValue: "123" as 'AnyObject' */
object = (anyNotOpt as? _XUOptional)?.objectValue ?? (anyNotOpt as? AnyObject)
/* anyNotOpt is not optional and left-most optional chaining of left
clause returns nil ('anyNotOpt as? _XUOptional' -> nil).
In that case, right clause will successfully cast the non-optional
'Any' type to 'AnyObject' (with value "123") */
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With