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!