CloseHomeAboutBlogCourse
Bob I’m an iOS instructor/blogger from S.Korea.July 14 • 5 min read • Edit

Intro to Generics in Swift with Bob

I know you are frustrated. I’ve been there. Join me. I will walk you through.

Personal Story (You may skip)

As I was writing a blog post on Functional Programming in Swift, I realized readers must have a strong foundation in Generics and
Protocols to survive through.

To find out if there were any good resources about generics, I clicked every link on the first 5 pages of the google search. After having gone through a couple, I’ve come to a conclusion that I had to do something about it — I felt the urgency to stand up for my readers. I could imagine their struggle and pain I went through.

Prerequisites

In order to fully enjoy this article with me, make sure you are familiar with protocols, OOP, functions, and extensions. If not, you may find all of the required resources available here which is my personal learning journey.

Since we are talking about generics, I’m going to use generic examples from the Swift 3 documentation. Of course, my explanation isn’t generic.

Your Past

Before we talk about anything else, let’s look into your past. How you were taught and brainwashed. Imagine you need to print each element within an array, you probably did this.

let stringArray = ["1", "2", "3", "4"]

let intArray = [1, 2, 3, 4]

let doubleArray = [1.1, 1.2, 1.3, 1.4]

Okay, let’s print.

func printStringFromArray(a: [String]) {
 for s in a {
  print(s)
 }
}

func printIntFromArray(a: [Int]) {
 for i in a {
  print(i)
 }

}
func printDoubleFromArray(a: [Double]) {
 for d in a {
  print(d)
 }
}

The code above is just atrocious. It sucks. Each function only takes one specific type of an array. It repeats like an argument. We hate. Can there be just a single function that allows you to input any parameter whose type is undefined?

That was a rhetorical question. Of course. If there wasn’t, I’d have probably not written this article. Let’s deploy.

Generics, You Savage

Good. First of all, there is just one rule if you want to make your function generic. All you need to do is put <anything> right next to a function name. Let’s take a look,

func savageGenerics<anything>(value: anything) {
 print(value)
}

savageGenerics(value: 123)     // Int: Damn 😀
savageGenerics(value: "Bob")   // String: Killing it 😲
savageGenerics(value: 123.12)  // Double: Whoa 😵
savageGenerics(value: true)    // Bool: I'm done 😱

Just bear with me for a sec. Just to clarify, when you input any type to value, Swift automatically determines the type of value based on the input. For example, if you call,

savageGenerics(value: “Bob”)

The value type is now String and it can be use within the function. If you call,

savageGenerics(value: false)

The value type is now Bool. I hope you are clear with me by now. Of course, you don’t have to call it as anything. You can all it however you want.

func savageGenerics<T>(value: T) {
 print(value)
}

Let’s Apply

Great, since we learned what generics is, let the show begin.

func printElement<T>(array: [T]) {
 for element in array {
  print(element)
 }
}

So, all you have to is,

printElement(array: doubleArray)  // 1.1 1.2 1.3 1.4
printElement(array: intArray)     // 1 2 3 4
printElement(array: stringArray)  // "1" "2" "3" "4"

One function takes it all. That’s why I call this a savage. If you are not familiar with this term, refer to Urban Dictionary.

So, let’s pull up Apple’s definition of Generics,

Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner. — Swift Programming Language

In fact, Swift library that you use to interact with an array and dictionary has a lot of generic code.

Struct Met Generics

Generics not only applies to functions, but also to classes and struct. Let us say there is a struct called, Family whose array property is called, members. You may add or remove an element to the array.

struct Family {
 var members = [String]()

 mutating func push(member: String) {
  members.append(member)
 }

 mutating func pop() -> String {
  return members.removeLast()
 }
}

The keyword mutating is needed if you want to modify your property. Since you are adding and removing elements for members you have to add mutating for those functions.

Again, Problem

The problem is, you can only append String to the element because not only members is predefined as [String] but also pop() and push() only allow String to work with. This isn’t generic. This aint’ cool. If you want to create a family whose name is in Int, you have to create an entire struct.

Let me introduce generic struct that allows you to insert anything regardless. Similar to the function we worked with, you put <anything> next to its name.

struct SavageFamily<T> {
 var members = [T]()

 mutating func push(member: T) {
  members.append(member)
 }

 mutating func pop() -> T {
  return members.removeLast()
 }
}

So, now let’s create an object whose type can be either 1. Explicitly stated or 2. Inferred.

1. Explicitly Stated

Let’s create an object using the SavageFamily struct. Just like a function, you have to insert <> to its name. For example,

var myFam = SavageFamily<String>()
var numberFam = SavageFamily<Int>()

You’ve stated that the myFam instance will only be able interact withString and numberFam with Int. The benefit is that you don’t need to create two separate structs in order to produce the result above. So, let’s interact.

// myFam
myFam.members = ["Mom", "Dad", "Sister", "Bob"]
myFam.push(member: "Dog")

// numberFam
numberFam.members = [1, 2, 3, 4, 5]
numberFam.pop()

2. Inferred

When you create an object, you don’t have to specify its type just like creating a variable. Swift is smart enough to guess its type and store. For example,

let yolo = "Bob" // yolo is String

Similarly, it applies to generic struct as well. So, you can create a generic object like,

let friendFam = SavageFamily(members: [“Cash outside”, “Bruh”])
let trueFam = SavageFamily(members: [false, false, false])

Apparently, friendFam can only interact with String and trueFam can only interact with Bool. You don’t have to deal with those <> when you create an object using a generic struct.

Generic Extension

Not only that, you can in fact add an extension to generic struct or class as well.

If you aren’t familiar with extension, feel free to watch my YouTube video

Let’s add a property that grabs the first element.

extension SavageFamily {

 var firstElement: T? {
  if members.isEmpty {
   return nil
  } else {
   return members[0]
  }
 }

}

Now, you can freely use the property within an object

let tigerFam = SavageFamily<String>()
tigerFam.firstElement  // nil

let lionFam = SavageFamily(members: ["Father", "Mother"])
lionFam.firstElement   // "Father" Optionals

Great. You’ve come a long way. I’m thankful for your attention and love. Let’s grab our last nugget.

Type Constraint

If you remember when you created a generic function,

func doSomething<HelloKitty>(array: [HelloKitty]) {}
func doNothing<T>(sth: T) {}

You were able to add anything regardless. You could add any object you name it. However, what if you want to limit to just a couple types? What if you only limit a function that only can take certain classes?

Right next to T, you can specify types you only work with. Let’s create a class called, LOL.

Foo and boo are too boring

class LOL {}

Okay, let’s create a generic function that can only take LOL

func addClassOnly<T: LOL>(array: [T]) {
}

addClassOnly(array: [LOL(), LOL(), LOL()]) // Works
addClassOnly(array: ["Lol", "Lol", "Lol"]) // Doesn't Work

Just letting you know, if you subclass Lol like this,

class ChildLOL: LOL {}

You can still add to the function which also can accept value whose type is LOL.

addClassOnly(array: [LOL(), LOL(), ChildLOL(), ChildLOL()])

Why? ChildLol() has been automatically upcasted to Lol. If you are not sure what type casting is, feel free to join the Swift intermediate course.

That’s it for now.

Last Remarks

From now on, you will see a lot of <T> and <U>. I’ve only covered the theoretical aspects of generics, but at least you know what you are reading when you encounter the savage. If you think you enjoyed this article or learned something new, please ♥️ or comment below to show your support and love!

In the next series, I will talk about how to apply Generics to Protocol and AssociatedType. If interested, feel free to follow me! 🤗

Resources

Source Code

About Me

iOS Developer from South Korea. Feel free to follow my story on Instagram or get serious on LinkedIn