无法从 Swift 字典中检索 CGColor

rma*_*ddy 1 macos core-graphics core-foundation ios swift

我需要一个可以存储任何类型对象的 Swift 字典。一些值将作为CGColor参考。我在创建字典和存储CGColor参考文献方面没有问题。问题是试图安全地让他们回来。

let color = CGColor(gray: 0.5, alpha: 1)
var things = [String:Any]()
things["color"] = color
things["date"] = Date()
print(things)
Run Code Online (Sandbox Code Playgroud)

那行得通,我得到了合理的输出。后来我希望得到颜色(字典中可能存在也可能不存在。所以我很自然地尝试以下操作:

if let color = things["color"] as? CGColor {
    print(color)
}
Run Code Online (Sandbox Code Playgroud)

但这会导致错误:

错误:有条件地向下转换为 CoreFoundation 类型“CGColor”将始终成功

最后我想出了:

if let val = things["color"] {
    if val is CGColor {
        let color = val as! CGColor
        print(color)
    }
}
Run Code Online (Sandbox Code Playgroud)

这在操场上没有任何警告,但在我的实际 Xcode 项目中,我在线上收到警告if val is CGColor

'is' 测试总是正确的,因为 'CGColor' 是一个 Core Foundation 类型

这个问题有什么好的解决办法吗?

我正在处理核心图形和图层,代码需要同时适用于 iOS 和 macOS,所以我试图避免UIColorNSColor.

我确实找到了从 AnyObject 到 CGColor 的 Casting?没有相关但似乎不再相关的错误或警告,因为我不需要括号来消除警告,而且我正在尝试使用该问题未涵盖的可选绑定。

Ham*_*ish 8

问题在于 Core Foundation 对象是不透明的,因此 type 的值CGColor只不过是一个不透明的指针——Swift 本身目前对底层对象一无所知。因此,这意味着您当前不能使用isas?有条件地使用它进行强制转换,Swift 必须始终允许给定的强制转换成功(尽管这有望在未来发生变化 - 理想情况下 Swift 运行时将用于CFGetTypeID检查不透明指针的类型) .

正如 Martin此问答中所示,一种解决方案是使用CFGetTypeID以检查 Core Foundation 对象的类型 - 为了方便起见,我建议将其分解为一个函数:

func maybeCast<T>(_ value: T, to cfType: CGColor.Type) -> CGColor? {
  guard CFGetTypeID(value as CFTypeRef) == cfType.typeID else {
    return nil
  }
  return (value as! CGColor)
}

// ...

if let color = maybeCast(things["color"], to: CGColor.self) {
  print(color)
} else {
  print("nil, or not a color")
}
Run Code Online (Sandbox Code Playgroud)

您甚至可以使用协议将其推广到其他 Core Foundation 类型:

protocol CFTypeProtocol {
  static var typeID: CFTypeID { get }
}

func maybeCast<T, U : CFTypeProtocol>(_ value: T, to cfType: U.Type) -> U? {
  guard CFGetTypeID(value as CFTypeRef) == cfType.typeID else {
    return nil
  }
  return (value as! U)
}

extension CGColor : CFTypeProtocol {}
extension CGPath  : CFTypeProtocol {}

// Some CF types don't have their ID imported as the 'typeID' static member,
// you have to implement it yourself by forwarding to their global function.
extension CFDictionary : CFTypeProtocol {
  static var typeID: CFTypeID { return CFDictionaryGetTypeID() }
}


// ...

let x: Any? = ["hello": "hi"] as CFDictionary

if let dict = maybeCast(x, to: CFDictionary.self) {
  print(dict)
} else {
  print("nil, or not a dict")
}
Run Code Online (Sandbox Code Playgroud)