Extend Swift Associated Types in Protocol to compose behaviour!
Associated Type
is a great way to add generic constraint in a Swift protocol. If you are new to generic constraint or associated types, you should checkout these wonderful articles by NatashaTheRobotđź‘Ś This article aims to exlpain how we can extend behaviour of associatedTypes
of a child protocol while inheriting from it’s base protocol 🙌.
Let’s take up a use case to understand what we are trying to achieve here. Every app has a UITableview
or UICollectionView
. To populate them, we either make an api request or hit the cache or sometimes do both. Recently I started working on multiples generic data-providers which could take care of all the above tasks. I know it sounds like too much work for protocols but hold on, you will discover later how I’ll extend associatedType
to make specific data providers like CacheDataProvider
& APIDataProvider
derived from a generic parent protocol DataProvider
.
protocol DataProvider {
associatedtype Data
var data: ([Data]) -> Void { get set }
}
protocol CacheDataProvider: DataProvider {
associatedtype Data: NSManagedObject
}
protocol APIDataProvider: DataProvider {
associatedtype Data: Parsable
}
DataProvider
is the base protocol which has a generic constraint named Data
which will be provided by the individual screen conforming it. Data
defines the type of object that screen is dependent on like, list of users or list of magazines or list of movies etc.
CacheDataProvider
is a child protocol, derived from DataProvider
, which takes care of fetching cache data for a screen. In our case it should be a subclass of NSManagedObject
. So what we have achieved here is that we have extended the meaning of Data
from it’s parent protocol to something which makes more sense for cache related operations.
APIDataProvider
is also a child protocol, derived from DataProvider
which takes care of fetching new data from server. In our case it should be parsable, which converts a json into a concrete object. Here again we have extended the behaviour of Data
to something which is more meaningful for fetching data via api.
This means that we can extend the meaning of Data
as we like and it could differ for different child protocols. Now let’s see how they both provide the data in their own way,
extension CacheDataProvider {
func fetchCacheData() {
...
Data.allObjects()
...
}
}
extension APIDataProvider { func fetchAPIData() {
...
jsonList.map { Data.parse($0) }
...
}
}
As we can see, in case of CacheDataProvider
we can call a function allObjects()
since Data
acts like a NSManagedObject
& allObjects()
is a helper method on NSManagedObject
to fetch all the cache objects.
In case of APIDataProvider
we can call a function parse()
which converts json into a concrete object since Data
acts like Parsable
which has a method to parse data.
Even though Data
is not defined by DataProvider
but as we have seen we can extend it’s meaning to anything we want as suited for the child protocol we create. If a screen only need cache then one can only conform to CacheDataProvider
or if only need new data from api then APIDataProvider
or if both are needed then one can conform to both the protocols to get cache first and then server data.
Thus associatedType
is indeed a powerful way to extend functionality. I have used a similar approach along with ReactiveSwift
to make different kinds of data providers including a paginated data provider which handles pagination for me 🚀 If you like the article, do share the kinds of associatedType
you use in your projects in the comments!