Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting Any to Optional

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`.
like image 895
Charlie Monroe Avatar asked Jan 06 '23 22:01

Charlie Monroe


1 Answers

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")                       */
like image 89
dfrib Avatar answered Jan 15 '23 23:01

dfrib