将任何类型的数据转换为可选类型

4

我正在开发一组代表实体及其属性的类,可以从这些实体动态生成编辑表视图。这些属性使用泛型来捕获属性类型。为了使用KVO并生成自动setter,这些属性包含一个键路径。下面是一个非常简化的属性类示例:

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)
    }
}

这个方法几乎可以适用于任何内容,但是可选项却有问题。例如: let property = XUEntityProperty(keyPath: "optionalDate")
问题在于,在setValue方法中,将Optional转换为AnyObject会失败,因为value是Optional类型,不能强制转换为AnyObject - objValue as? NSDate即使objValue是.Some(_)也会返回nil。
我正在寻找一种解决方案,通过检测和取消包装Optional,在自动设置器中解决此问题。
我尝试的任何类型转换都会导致编译器抱怨要么将其强制转换为更多的Optional类型,要么最终将Optional包装到另一个Optional中。
是否有人知道如何检测Any值是否是Optional,并从Optional中提取值并将其转换为AnyObject?
以下是在playground中尝试的示例:
let any: Any = Optional<String>("123")
any.dynamicType // -> Optional<String>.Type

var object: AnyObject? = nil

/// ... -> put value from `any` to `object`.
2个回答

6

您可以通过添加虚拟协议并使用is进行类型检查,然后使用Mirror(..)从可选的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提供的解决方案,非常简洁 (+1!)。我想补充一个内容。

鉴于协议_XUOptional已经定义,并且已经通过它扩展了Optional类型(就像Charlie的答案中一样),您可以使用可选链和nil合并运算符在一行代码中处理any是可选的还是不可选的事件:

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")                       */

3
所以,这是解决方案。需要一些变通处理,但能够按照需求正常运作。感谢@dfri 提出关于协议的想法!
private protocol _XUOptional {

    var objectValue: AnyObject? { get }

}

extension Optional: _XUOptional {

    /// This property will return nil if the value is nil,
    /// or if the value isn't castable to AnyObject. String,
    /// Int, Bool and a few others are automatically converted
    /// to their ObjC counterparts.
    var objectValue: AnyObject? {
        switch self {
        case .None:
            return nil
        case .Some(_):
            return self! as? AnyObject
        }
    }

}

let any: Any = Optional<String>("123")
let obj = (any as! _XUOptional).objectValue!
obj.dynamicType /// _NSContiguousString.Type

看一下我的更新答案,使用 Mirror;效果很好。 - dfrib
请注意,您仍然需要断言any实际上是一个可选项:如果您使用let any: Any = String(“123”)进行测试,则以上解决方案将由于强制转换(as!)为_XUOptional而产生运行时异常。我个人更喜欢Mirror解决方案,因为您不需要扩展基本的Optional类型,而可以仅使用“外部”方法来工作。 - dfrib
当然,可选项的强制解包是为了测试目的。在实际代码中,这看起来非常像 if let optional = obj as? _XUOptional { return optional.objectValue }。甚至更短 - (obj as? _XUOptional)?.optionValue - Charlie Monroe
无论如何,感谢你的回答,我真的很高兴让它工作了!几乎整个下午都在尝试修复这个问题! - Charlie Monroe
请务必检查一下,如果我漏掉了什么。请注意,我在这里放置的代码示例有些简化,并且是从 Playground 中获取的,与实际项目不同,实际项目会更加注重细节。 - Charlie Monroe
1
没关系,object = (any as? _XUOptional)?.objectValue 很好地工作了,干得好! :) - dfrib

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接