Intro to Generics

Introduction

Welcome to Lesson 6 of The Swift Fundamentals with Bob. When you think of the word, “generic”, what makes you think? Well, I’m sure you’ve thought of versatile code. What? Don’t you dare worry. I will walk you through. Remember, the number one goal is to write as little as possible, but produce as much.

Problem

I smell something.

For the reference, read more about code smell

Access Elements

Let us reflect on how we rolled in the past.

  1. let highSchoolGPA = [2.8, 3.2, 3.5, 3.8, 3.5]
  2. let favoritePeople = ["Elon Musk", "Steve Jobs", "Kevin O'leary"]
  3. let favoriteNumbers = [3, 20]

The Worst way to access elements

  1. highSchoolGPA[0]
  2. highSchoolGPA[1]
  3. highSchoolGPA[2]

Loop and Print

This is slightly better.

  1. func printDoubleElement(array: [Double]) {
  2. for GPA in array {
  3. print(GPA)
  4. }
  5. }
  6. func printStringElement(array: [String]) {
  7. for person in array {
  8. print("I love \(person)")
  9. }
  10. }
  11. func printNumberElement(array: [Int]) {
  12. for number in array {
  13. print("I like \(number)")
  14. }
  15. }

Needlessly many functions. It goes against the DRY principle.

Introducing Generics

A generic function allows you to pass any value regardless of types.

  1. func genericFunction<anything>(value: anything) {
  2. print(value)
  3. }
  4. func genericFunctions<WHATEVER>(value: WHATEVER) {
  5. print(value)
  6. }

Swift infers the type of anything or WHATEVER based on the input.

  1. genericFunction(value: "Bob") // anything is String
  2. genericFunctions(value: true) // WHATVER is Bool

Generic Loop

Let us create a generic function.

  1. func printElement<T>(array: [T]) {
  2. for element in array {
  3. print(element)
  4. }
  5. }

Call the function

  1. printElement(array: highSchoolGPA) // 2.8, 3.2, 3.5, ...
  2. printElement(array: favoritePeople) // 'Elon Musk", "Steve Jobs", ...

Generic code enables you to write flexible, reusable functions, classes, enums, protocols, and structs subject to requirements that you define.

Now only you can create generic functions, but also generic classes and structs. Let us begin with a non-generic struct.

Non-Generic Struct

Create a Family struct that contains members whose type is [String]. Add a method that appends to the array.

  1. struct Family {
  2. var members = [String]()
  3. mutating func push(member: String) {
  4. members.append(member)
  5. }
  6. }

Important: The mutating keyword is added to function of enum and struct that mutates its own property/properties.

Non-Generic Instance

  1. var myFam = Family()
  2. myFam.push(member: "Bob")
  3. myFam.members // ["Bob"]

The struct above only works with String. Some families may have names in Int. Then, you must create a new struct. It goes against the DRY principle.

Generic Struct

  1. struct genericFam<T> {
  2. var members = [T]()
  3. mutating func push(member: T) {
  4. members.append(member)
  5. }
  6. }

Instead of using WHATEVER or anything, T is a standard among iOS developers.

Define Type Explicitly

Now, let us create an object from the generic struct. You have to specify the type based on the rule in Lesson 1.

  1. var myGenericFamily = genericFam<String>()
  2. myGenericFamily.push(member: "Bobby")
  3. var genericFamily = genericFam<Int>()
  4. genericFamily.push(member: 123)

Define Type Implicitly

However, the type can be inferred based on the input.

  1. let myHappyFamily = genericFam(members: [1, 2, 3, 4, 5]) // T becomes Int

Generic Extension

You may add an extension to generics for more features. Let us grab the first element in the members property.

  1. extension genericFam {
  2. var firstElement: T? {
  3. if members.isEmpty {
  4. return nil
  5. } else {
  6. return members[0]
  7. }
  8. }
  9. }
  10. let geekFamilyMember = genericFam(members: ["Bob", "Bobby"])
  11. let firstElement = geekFamilyMember.firstElement // "Bob"

Type Constraints {#type-constraints}

So far, you could enter any value to define T. But, you may restricts the type you only want to interact with.

First, design two classes.

  1. class LOL {}
  2. class BabyLol: LOL {}

Now, let’s create a function that only allows you to enter LOL.

  1. func addLOLClassOnly<T: LOL>(array: [T]) { }

When you run the addLOLClassOnly function, you can’t add anything besides whose type is LOL.

  1. addLOLClassOnly(array: [LOL(), LOL(), LOL(), BabyLol()])
  2. addLOLClassOnly(array: [1, 2, 3, 4, 5]) // Error

Important: BabyLol() has been automatically upcasted to LOL.

Resources

Intro to Generics in Swift with Bob

Source Code

1006_generics.playground

Conclusion

You now understand how to maintain yourself dry through generic functions and structs. In later chapters, you will more about advanced generics along with enums and protocols. If you wish to review generic the syntax, I recommend you to watch the lecture again or refer to the article I’ve attached.

In the next lesson, you will learn how to provide shortcuts within classes and structs instead of calling methods and properties.

Note: Learn Swift with Bob is available on Udemy. If you wish to receive a discount link, you may sign up here.