Since static stored properties are not (yet) supported for generic types in swift, I wonder what is a good alternative.
My specific use-case is that I want to build an ORM in swift. I have an Entity
protocol which has an associatedtype for the primary key, since some entities will have an integer as their id
and some will have a string etc. So that makes the Entity
protocol generic.
Now I also have an EntityCollection<T: Entity>
type, which manages collections of entities and as you can see it is also generic. The goal of EntityCollection
is that it lets you use collections of entities as if they were normal arrays without having to be aware that there's a database behind it. EntityCollection
will take care of querying and caching and being as optimized as possible.
I wanted to use static properties on the EntityCollection
to store all the entities that have already been fetched from the database. So that if two separate instances of EntityCollection
want to fetch the same entity from the database, the database will be queried only once.
Do you guys have any idea how else I could achieve that?
All I can come up with is to separate out the notion of source (where the collection comes from) and then collection itself. And then the make the source responsible for caching. At that point the source can actually be an instance, so it can keep whatever caches it wants/needs to and your EntityCollection is just responsible for maintaining a CollectionType and/or SequenceType protocol around the source.
Something like:
class WebEntityCollection : SequenceType { ... }
would work if you have a typical paged web data interface. Then you could do something along the lines of:
This isn't ideal, but this is the solution I came up with to fit my needs.
I'm using a non-generic class to store the data. In my case, I'm using it to store singletons. I have the following class:
This is basically just a cache.
The function
singleton
takes the generic that is responsible for the singleton and a closure that returns a new instance of the singleton.It generates a string key from the generic instance class name and checks the dictionary (
singletons
) to see if it already exists. If not, it calls the closure to create and store it, otherwise it returns it.From a generic class, you can use a static property as described by Caleb. For example:
Testing the following, you can see that each singleton is only created once per generic type:
This solution may offer some insight into why this isn't handled automatically in Swift.
I chose to implement this by making the singleton static to each generic instance, but that may or may not be your intention or need.
Depending on how many types you need to support and whether inheritance is (not) an option for you, conditional conformance could also do the trick:
An hour ago i have a problem almost like yours. I also want to have a BaseService class and many other services inherited from this one with only one static instance. And the problem is all services use their own model (ex: UserService using UserModel..)
In short I tried following code. And it works!.
Hope it helps.
I think the trick was BaseService itself will not be used directly so NO NEED TO HAVE static stored property. (P.S. I wish swift supports abstract class, BaseService should be)
The reason that Swift doesn't currently support static stored properties on generic types is that separate property storage would be required for each specialisation of the generic placeholder(s) – there's more discussion of this in this Q&A.
We can however implement this ourselves with a global dictionary (remember that static properties are nothing more than global properties namespaced to a given type). There are a few obstacles to overcome in doing this though.
The first obstacle is that we need a key type. Ideally this would be the metatype value for the generic placeholder(s) of the type; however metatypes can't currently conform to protocols, and so therefore aren't
Hashable
. To fix this, we can build a wrapper:The second is that each value of the dictionary can be a different type; fortunately that can be easily solved by just erasing to
Any
and casting back when we need to.So here's what that would look like:
We are able to maintain the invariant that the metatype used for the key describes the element type of the array value through the implementation of
loadedEntities
, as we only store a[T]
value for aT.self
key.There is a potential performance issue here however from using a getter and setter; the array values will suffer from copying on mutation (mutating calls the getter to get a temporary array, that array is mutated and then the setter is called).
(hopefully we get generalised addressors soon...)
Depending on whether this is a performance concern, you could implement a static method to perform in-place mutation of the array values:
There's quite a bit going on here, let's unpack it a bit:
defer
so we can neatly return frombody
and then put the array back).We're using
with(_:_:)
here in order to ensure we have write access to_loadedEntities
throughout the entirety ofwithLoadedEntities(_:)
to ensure that Swift catches exclusive access violations like this:It turns out that, although properties are not allowed, methods and computed properties are. So you can do something like this:
Or: