I have a manager object with a public interface defined by a Protocol (to allow different versions for different situations). Since other objects will only know about the manager as its protocol type, they need to be able to query whether it has an object of a specific type. If the manager has a member of that type, it will return the instance. Otherwise, it will return nil.
protocol ObjectRequestable {
func getObject<T>(type: T.Type) -> T?
}
struct ShadowFilter {
let width: Float
}
struct RainbowFilter {
}
class FilterManager: ObjectRequestable {
private let shadowFilter = ShadowFilter(width: 8)
func getObject<T>(type: T.Type) -> T? {
switch type {
case ShadowFilter.self: return shadowFilter
default: return nil
}
}
}
let filterManager = FilterManager()
// Should contain the shadowFilter instance.
let shadow = filterManager.getObject(type: ShadowFilter.self)
// Should contain nil.
let rainbow = filterManager.getObject(type: RainbowFilter.self)
The case ShadowFilter.self: return shadowFilter line produces 2 compiler errors:
Cannot convert return expression of type 'ShadowFilter' to return type 'T?'
Expression pattern of type 'ShadowFilter.Type' cannot match values of type 'T.Type'
Is this sort of scenario possible, or is there a different way I should be thinking about this?
if type == ShadowFilter.self {
return (shadowFilter as! T)
}
else {
return nil
}
but not this (fails to compile):
switch type {
case ShadowFilter.self: return (shadowFilter as! T)
// π Error: Expression pattern of type 'ShadowFilter.Type' cannot match values of type 'T.Type'
default: return nil
}
I think this is because the first hits a specific overload of == in the standard library for comparing two Any.Type?s, whereas the switch version will only work if the pattern matching operator ~= was overloaded or if the argument is Equatable (because there's a specific ~= overload for Equatable types).
That's cleaner looking, but I feel like it's going to be less obvious what types are involved (my real-world code has 7-8 different types that can be requested), so I'm going to keep my wordier version.
Of course, I don't recommend that anybody define an operator like that in their own code base, for the same reasons it's a bad idea to implement retroactive conformances for types and protocols that you don't own.
In an ideal world, metatypes would just be Equatable and Hashable.
This approach, and my suggestion of case is for that matter, have a subtle difference in how theyβll handle subclasses compared to the if β¦ == β¦ or the Wrapper approaches in case that matters.
The cast alone and the case is will succeed for a super type even if they instance is of a subtype depending on the order of the checks, but the equality based approaches should only return exact matches.