【问题标题】:Casting Any to Optional将 Any 转换为 Optional
【发布时间】:2015-12-30 16:49:47
【问题描述】:

我正在研究一组表示实体及其属性的类,然后可以从实体动态创建编辑器表视图。这些属性使用泛型来捕获属性类型。为了使用 KVO 并生成自动设置器,这些属性包含一个关键路径。这是属性类的一个非常简化的版本:

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&lt;MyEntity, NSDate?&gt;(keyPath: "optionalDate")

这里的问题是,在setValue 方法中,转换为AnyObject 将失败,因为值为Optional&lt;NSDate&gt;,不能转换为AnyObject - 即使objValue as? NSDate 将返回nil objValue.Some(_)

我正在寻找一种解决方案,如何通过检测和解开 Optional 在自动设置器中解决此问题。

我尝试的任何转换都会导致编译器抱怨转换为更可选的类型,或者最终将Optional 包裹在另一个Optional 中。

有谁知道如何检测Any的值 Optional,如果是,提取值从可选的,将其转换为 AnyObject?

给你一个在操场上尝试的例子:

let any: Any = Optional<String>("123")
any.dynamicType // -> Optional<String>.Type

var object: AnyObject? = nil

/// ... -> put value from `any` to `object`.

【问题讨论】:

    标签: swift generics casting


    【解决方案1】:

    您可以通过添加一个虚拟协议并使用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 类型由它扩展(如 Charlies 的回答),您可以在 any 是可选的或不在一行中时处理事件,使用可选链接和nil 合并运算符:

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

    【讨论】:

      【解决方案2】:

      所以,这就是解决方案。需要一些解决方法,但可以根据需要工作。感谢@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查看我的更新答案;工作整齐。
      • 请注意,您仍然需要断言 any 实际上是可选的:如果您使用 let any: Any = String("123") 进行测试,上述解决方案将由于强制转换而产生运行时异常 (as! ) 到_XUOptional。我个人更喜欢Mirror 解决方案,因为您不需要扩展基本的Optional 类型,但可以单独使用“外部”方法。
      • 当然,可选的强制展开是出于测试目的。在实际代码中,这看起来很像if let optional = obj as? _XUOptional { return optional.objectValue }。甚至更短 - (obj as? _XUOptional)?.optionValue.
      • 无论如何,感谢您的回答,我真的很高兴能完成这项工作!几乎整个下午都在试图解决这个问题!
      • 没关系,object = (any as? _XUOptional)?.objectValue 工作得非常好,干得好! :)
      猜你喜欢
      • 1970-01-01
      • 2018-09-14
      • 1970-01-01
      • 1970-01-01
      • 2019-06-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多