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?
cukr
2
You need to return shadowFilter as! T
tera
3
Interestingly this works:
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
}
2 Likes
Thank you! I updated my code like this and it works now:
func getObject<T>(type: T.Type) -> T? {
if type == ShadowFilter.self, let sf = shadowFilter as? T {
return sf
} else {
return nil
}
}
tera
5
Nice. Alternatively:
func getObject<T>(type: T.Type) -> T? {
if let v = shadowFilter as? T { return v }
else { return nil }
}
allevato
(Tony Allevato)
6
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).
3 Likes
tera
7
How do I do this in this example?
The following seems to be working (at least it fixes the compilation error):
func ~= (t0: Any.Type, t1: Any.Type) -> Bool {
t0 == t1
}
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.
I really appreciate your help!
allevato
(Tony Allevato)
9
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.
tera
10
Could you think of a particular scenario when this overload would fail?
Was it just an oversight to not have it in the standard library?
I guess this is a bigger ask.
tera
11
A cleaner version:
struct Wrapper {
let type: Any.Type
static func ~= (t0: Wrapper, t1: Wrapper) -> Bool {
t0.type == t1.type
}
}
class FilterManager: ObjectRequestable {
private let shadowFilter = ShadowFilter(width: 8)
func getObject<T>(type: T.Type) -> T? {
switch Wrapper(type: type) {
case Wrapper(type: ShadowFilter.self): return (shadowFilter as! T)
default: return nil
// 👌 compiles fine
}
}
}
2 Likes
jflan
(John Flanagan)
12
Hi Josh 
Using case is ShadowFilter.Type also seems to work:
class FilterManager: ObjectRequestable {
private let shadowFilter = ShadowFilter(width: 8)
func getObject<T>(type: T.Type) -> T? {
switch type {
case is ShadowFilter.Type: return shadowFilter as! T?
default: return nil
}
}
}
3 Likes
ksluder
(Kyle Sluder)
13
I just use ObjectIdentifier.
1> func typesEqual<T, U>(_ t: T.Type = T.self, _ u: U.Type = U.self) -> Bool { ObjectIdentifier(t) == ObjectIdentifier(u) }
2> typesEqual(String.self, Int.self)
$R0: Bool = false
3> typesEqual(String.self, String.self)
$R1: Bool = true
1 Like
tera
14
This is the simplest solution so far.
jflan
(John Flanagan)
15
Riffing on this idea; you could match the Wrapper against Any.Type to avoid having to wrap each case statement:
class FilterManager: ObjectRequestable {
private let shadowFilter = ShadowFilter(width: 8)
func getObject<T>(type: T.Type) -> T? {
switch Wrapper(type: type)
case ShadowFilter.self: return shadowFilter as! T?
default: return nil
}
}
struct Wrapper {
let type: Any.Type
static func ~= (t0: Any.Type, t1: Wrapper) -> Bool {
t0 == t1.type
}
}
}
3 Likes
Or even
func getObject<T>(type: T.Type) -> T? {
shadowFilter as? T
}
If there multiple properties like that:
func getObject<T>(type: T.Type) -> T? {
(shadowFilter as? T) ?? (blurFilter as? T) ?? (rainbowFilter as? T)
}
1 Like
jflan
(John Flanagan)
17
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.
tera
18
Nice, possibly the best so far.
I'd also consider the following approach:
protocol Filter {}
struct ShadowFilter: Filter {
let width: Float
}
struct RainbowFilter: Filter {}
class FilterManager: ObjectRequestable {
var allFilters: [Filter] = [
ShadowFilter(width: 8),
RainbowFilter()
]
func getObject<T>(type: T.Type) -> T? {
allFilters.first { $0 is T } as? T
}
}
Or even
protocol ObjectRequestable {
func getObject<T: Filter>(type: T) -> T
}
protocol Filter {
static var value: Self { get }
}
struct ShadowFilter: Filter {
let width: Float
static let value = ShadowFilter(width: 8)
}
struct RainbowFilter: Filter {
static let value = RainbowFilter()
}
class FilterManager {
func getObject<T: Filter>(type: T) -> T {
T.value
}
}
I went with something like:
class FilterManager: ObjectRequestable {
private let shadowFilter = ShadowFilter(width: 8)
func getObject<T>(type: T.Type) -> T? {
if type == ShadowFilter.self {
return shadowFilter as? T
} else {
return nil
}
}
}
My reasons were:
- The code is a more obvious "if I have this type then return it" organization.
- Doesn't require any operator overloads or extensions.
- Complexity expands it to new lines, rather than chaining onto a single very-long line. Slightly better for version control.
- Doesn't require additional members to be added to the class.
1 Like