I need a Swift dictionary that can store any kind of object. Some of the values will be CGColor
references. I have no issue creating the dictionary and storing the CGColor
references. The problem is trying to safely get them back.
let color = CGColor(gray: 0.5, alpha: 1)
var things = [String:Any]()
things["color"] = color
things["date"] = Date()
print(things)
That works and I get reasonable output. Later on I wish to get the color (which may or may not exist in the dictionary. So naturally I try the following:
if let color = things["color"] as? CGColor {
print(color)
}
But this results in the error:
error: conditional downcast to CoreFoundation type 'CGColor' will always succeed
In the end I came up with:
if let val = things["color"] {
if val is CGColor {
let color = val as! CGColor
print(color)
}
}
This works without any warnings in a playground but in my actual Xcode project I get a warning on the if val is CGColor
line:
'is' test always true because 'CGColor' is a Core Foundation type
Is there good solution to this problem?
I'm working with core graphics and layers and the code needs to work with both iOS and macOS so I'm trying to avoid UIColor
and NSColor
.
I did find Casting from AnyObject to CGColor? without errors or warnings which is related but doesn't seem relevant any more since I don't need the parentheses to eliminate the warning plus I'm trying to use optional binding which isn't covered by that question.
The problem is that Core Foundation objects are opaque, therefore a value of type CGColor
is nothing more than an opaque pointer – Swift itself currently doesn't know anything about the underlying object. This therefore means that you cannot currently use is
or as?
to conditionally cast with it, Swift has to always allow the given cast to succeed (this will hopefully change in the future though – ideally the Swift runtime would use CFGetTypeID
to check the type of the opaque pointer).
One solution, as shown by Martin in this Q&A, is to use CFGetTypeID
in order to check the type of the Core Foundation object – which, I would recommend factoring out into a function for convenience:
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")
}
And you could even generalise this to other Core Foundation types with a protocol:
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")
}