So I have a new dependency injection framework, Factory.
Factory allows for scoped instances, basically allowing you to cache services once they're created. And one of those scopes is shared. Any instance shared will be cached and returned just as long as someone in the outside world maintains a strong reference to it. After the last reference releases the object the cache releases the object and a new instance will be created on the next resolution.
This is implemented, obviously, as simply maintaining a weak reference to the created object. If the weak reference is nil it's time to create a new object.
And therein lies the problem
Weak references can only apply to reference types.
Factory uses generics internally to manage type information. But I can create Factories of any type: Classes, structs, strings, whatever.)
Scopes use dictionaries of boxed types internally. If an instance exists in the cache and in the box it's returned. So what I'd like to do is create this...
private struct WeakBox<T:AnyObject>: AnyBox {
weak var boxed: T
}
The AnyObject conformance is need in order to allow weak. You get a compiler error otherwise. Now I want to box and cache an object in my shared scope with something like this...
func cache<T>(id: Int, instance: T) {
cache[id] = WeakBox(boxed: instance)
}
But this also gives a compiler error. (Generic struct WeakBox requires T to be a class type.)
So how to bridge from on to the other? Doing the following doesn't work. Swift shows a warning that "Conditional cast from 'T' to 'AnyObject' always succeeds" and then converts the type anyway.
func cache<T>(id: Int, instance: T) {
if let instance = instance as? AnyObject {
cache[id] = WeakBox(boxed: instance)
}
}
I'd be happy with the following, but again, same problem. You can't test for class conformance and you can't conditionally cast to AnyObject. Again, it always succeeds.
private struct WeakBox: AnyBox {
weak var boxed: AnyObject?
}
func cache<T>(id: Int, instance: T) {
if let instance = instance as? AnyObject {
cache[id] = WeakBox(boxed: instance)
}
}
What I'm doing at the moment is something like...
private struct WeakBox: AnyBox {
weak var boxed: AnyObject?
}
func cache<T>(id: Int, instance: T) {
cache[id] = WeakBox(boxed: instance as AnyObject)
}
Which works, but that instance as AnyObject
cast depends on some very weird Swift to Objective-C bridging behavior.
Not being able to test for class conformance at runtime is driving me bonkers, and seems like a semi-major loophole in the language.
You can't test for conformance, and you can't cast for conformance.
So what can you do?