Roni Leyes
The Aesthetic Programmer
4 min readMar 4, 2018

--

Swift 4 Generics — A generic class that has a variable that conforms to a protocol with associated type.

A lot of times I find myself struggling with Swift’s generic protocols and associated types.
Every time I want to “POPify” a feature, in my head I imagine 1 thing and when coding, I run into some strange errors and language limitations which I don’t expect.

Last week, a guy in my team asked me about this subject and we found ourselves trying a lot of workarounds to make this work.

What he wanted was: A generic class that has a variable that conforms to a protocol with associated type.

This might sound strange so let’s just dive into it and you’ll understand.

Prepare a swift playground and..

Take this case: I want to make a protocol of some data provider. This protocol has a single func that gives that data:

protocol DataProvider {    func giveData() -> Any}

and I want a class that consumes data :

class DataConsumer {   var data : Any?   var dataProvider : DataProvider?   func getData(){      self.data = self.dataProvider?.giveData()   }}

For now, everything seems ok.

But — what if we want that couple(Giver&consumer) to work with the same type of data? Sounds ideal for one of Swift’s amazing features — Generics.

Declaring the DataConsumer with an associated type (‘T’) that conforms to some protocol. Then DataConsumer’s ‘dataProvider’ var is a type that conforms to DataProvider with the same associated type (‘T’) declared in DataConsumer.

This could be nice. Imagine initializing DataConsumer and it’s data type in some outer scope and then when trying to assign a dataGiver, autocomplete offers a completion with the type you have declared when initializing the consumer.

Let’s try it out

Let’s create a protocol for data type first :

protocol DataType {}

Now we’ll want the Giver and the Consumer to work with the same type.

Let’s edit our Data Consumer and make it a generic type.

class DataConsumer <T:DataType> {   var data : T?   var dataProvider : DataProvider?   func getData(){      self.data = self.dataProvider?.giveData()   }}

The compiler will now shout that giveData() returns Any instead of T.

Let’s edit DataProvider :

protocol DataProvider {   associatedtype T : DataType   func giveData() -> T}

Now back again to our DataConsumer to fix our dataProvider var to have an associated type like DataConsumer:

var dataProvider : DataProvider<T>?

Whoops… The compiler now says :

Cannot specialize non-generic type 'DataProvider'

I personally didn’t understand this error right away and had to search the internet.

The compiler now offers a solution for that error (Xcode 9):

Replace '<T>' with "

First of all, what is “? that’s a strange error.

Second, clicking ‘fix’ does that:

var dataProvider : DataProvider?

And now the compiler complains about something else :

Protocol 'DataProvider' can only be used as a generic constraint because it has Self or associated type requirements

Welp. If so, let’s try it the other way. Instead of declaring an associated type to the protocol, Let’s make the function have a generic parameter and remove the associatedtype declaration:

func giveData<T:DataType>() -> T

Seems like the compiler doesn’t complain at all now.
Let’s try to work with what we have now…

Let’s create a JSON consumer, giver and data type:

struct JSONData : DataType {}final class JSONDataProvider : DataProvider{   func giveData<T>() -> T {      return JSONData()   }}

Compiler will now scream again:

Cannot convert return expression of type 'JSONData' to return type 'T'

What? but we wrote in our protocol that T is of type DataType and our JSONData struct conforms to DataType..?

Well, we can go and do this, like Xcode offers:

return JSONData() as! T

Ugly as hell, but might work..

Before we go on, add this extension that will help us identify our type:

extension DataType {   func whoAmI(){      print(type(of: self))   }}

Now, right after JSONDataProvider class decleration, add these lines:

let dataConsumer = DataConsumer<JSONData>()dataConsumer.dataProvider = JSONDataProvider()dataConsumer.getData()dataConsumer.data?.whoAmI()

Woohoo! The console shows:

JSONData

Well, we got one more problem. Add this line right after the call to whoAmI():

dataConsumer.dataProvider = XMLDataProvider()

This compiles successfully and that is not what wanted.
Calling getData() now will cause a crash, obviously. You can go and try it out.

That’s because we can’t force a specific DataGiver type(Remember? “ Cannot specialize non-generic type 'DataProvider' “).

This solution, besides not giving us what we wanted, uses casting, is longer and makes our code less understandable.

After spending some time on this subject. I finally ended doing something else. It’s enough, but not what I wanted.

Instead of having both parties look at the same type, DataConsumer is now a generic class that has a type parameter that conforms to DataProvider.

Again, didn’t what I exactly wanted. But still does the job.

In my idea, the JSONProvider and XMLProvider class declarations could be dropped.

At the end, I am pretty happy with the results, although, like always, they are not as I imagined :)

--

--