Intro to iOS Development
  • Welcome
    • β˜€οΈIntroduction
    • 🐣Getting Started
    • 🐦Course Staff
  • Logistics
    • πŸ“œSyllabus
    • πŸ—“οΈSchedule
    • πŸ–ŠοΈGrading
    • πŸ™‹β€β™‚οΈOffice Hours
    • Ed Discussion
  • Assignments
    • 🍼A1: Swift Basics
    • πŸ§‘A2: Profile
    • πŸ’¬A3: ChatDev
    • πŸ‘¨β€πŸ³A4: ChefOS
    • πŸ‘¨β€πŸ³A4: ChefOS - SwiftUI
    • πŸ“±Hack Challenge
      • πŸ†FA23 Winners
  • Lectures
    • 1️⃣Logistics + Swift Basics
    • 2️⃣UIKit + AutoLayout
  • 3️⃣MVC + Navigation + Delegation
  • 4️⃣UITableView
  • 5️⃣UICollectionView
  • 6️⃣Networking I
  • 7️⃣Networking II
  • 8️⃣SwiftUI I
  • 9️⃣SwiftUI II
  • πŸ”ŸPersistence + SnapKit
  • πŸ””(11) Notifications
  • 🌎(12) Deployment and MapKit
  • Chapters
    • πŸ–οΈIntroduction
    • ☁️Git + GitHub
      • 1️⃣Git Installation
      • 2️⃣Git Basics
      • βž•Git+
    • 🐣Swift Basics
      • 1️⃣Variables and Constants
      • 2️⃣Data Types
      • 3️⃣Operators
      • 4️⃣Data Structures
      • 5️⃣Conditionals
      • 6️⃣Loops
      • 7️⃣Functions
      • 8️⃣Closures
      • 9️⃣Optionals
    • 🧰UIKit + AutoLayout
      • 1️⃣Classes
      • 2️⃣UIKit
      • 3️⃣AutoLayout
    • πŸ“ΊMVC + Navigation + Delegation
      • 1️⃣MVC
      • 2️⃣Navigation
      • 3️⃣Delegation
    • πŸ“UITableView
      • 1️⃣What is a UITableView?
      • 2️⃣UITableView Setup
    • πŸ“šUICollectionView
      • 1️⃣What is a UICollectionView?
      • 2️⃣UICollectionView Setup
    • 🌐Networking I
      • 1️⃣HTTP Requests
      • 2️⃣Callbacks
      • 3️⃣Codable
    • 🌍Networking II
      • 1️⃣Alamofire
      • 2️⃣GET Requests
      • 3️⃣POST Requests
      • 4️⃣URLSession
    • πŸ’ΎPersistence + SnapKit
      • 1️⃣Persistence
      • 2️⃣SnapKit
    • πŸ•ŠοΈSwiftUI
      • 1️⃣Introduction to SwiftUI
      • 2️⃣Getting Started with SwiftUI
      • 3️⃣Views + Modifiers
      • 4️⃣Layouts
      • 5️⃣Navigation
      • 6️⃣Property Wrappers
    • πŸŽ›οΈWidgets
      • 1️⃣Introduction to Widgets
      • 2️⃣Setting Up Widgets
      • 3️⃣Building Widgets
      • 4️⃣Configuring Widgets
    • 🧱Project Foundation
    • βœ…Testing
      • 1️⃣Unit Testing
    • πŸ‘£Debugging
      • 1️⃣OSLog
      • 2️⃣Crashlytics
      • 3️⃣Analytics
    • ☁️CI/CD
      • 1️⃣Xcode Cloud
      • 2️⃣AppStore Shipping
  • Guides
    • πŸ”¨Xcode Project Setup
    • 🎨Figma
    • πŸ“¬Postman
    • πŸ₯₯CocoaPods
    • 🧰UIKit Handbook
    • πŸ“‘Tab Views
      • UITabBarController
      • TabView
  • Work in progress
    • 🧡Concurrency
    • 2️⃣UI Testing
    • πŸ•Reactive Programming
    • 🧠Memory Management
      • πŸ”ARC
    • πŸ“¦Storage
    • πŸ“£Notifications
  • Archived
    • SP24
      • Logistics
        • πŸ“œSyllabus
        • πŸ—“οΈSchedule
        • πŸ–ŠοΈGrading
        • πŸ™‹β€β™‚οΈOffice Hours
      • Assignments
        • 🍼A1: Swift Basics
        • πŸ§‘A2: Profile
        • πŸ’¬A3: ChatDev
        • πŸ‘¨β€πŸ³A4: ChefOS
        • πŸ“±Hack Challenge
          • πŸ†FA23 Winners
      • Lecture
        • 0️⃣Course Intro + Logistics + Git Setup
        • 1️⃣Swift Basics
        • 2️⃣UIKit + AutoLayout
        • 3️⃣MVC + Navigation + Delegation
        • 4️⃣UITableView
        • 5️⃣UICollectionView
        • 6️⃣Networking I
        • 7️⃣Networking II
        • 8️⃣Persistence + SnapKit
        • πŸ”ŸSwiftUI
        • πŸ”’TabViews
    • FA23
      • Logistics
        • 🐣Getting Started
        • 🐦Course Staff
        • πŸ“œSyllabus
        • πŸ—“οΈSchedule
        • πŸ–ŠοΈGrading
        • πŸ™‹β€β™‚οΈOffice Hours
      • Assignments
        • 🍼A1: Swift Basics
        • πŸ§‘A2: Profile
        • πŸ’¬A3: ChatDev
        • πŸ‘¨β€πŸ³A4: ChefOS
        • πŸ“±Hack Challenge
          • πŸ†FA23 Winners
      • Lectures
        • 0️⃣Course Intro + Logistics
        • 1️⃣Swift Basics
        • 2️⃣UIKit + AutoLayout
        • 3️⃣MVC + Navigation + Delegation
        • 4️⃣UITableView
        • 5️⃣UICollectionView
        • 6️⃣Networking I
        • 7️⃣Networking II
        • 8️⃣Persistence + SnapKit
        • πŸŽ›οΈWidgets
          • πŸ‘ΌIntroduction to Widgets
          • βš’οΈSetting Up Widgets
          • 🧱Building Widgets
          • πŸ‘¨β€πŸ’»Configuring Widgets
        • πŸ”ŸSwiftUI
    • SP23
      • Logistics
        • Lecture Schedule
        • Syllabus
        • Grading
        • SP23 Office Hours
      • Chapters
        • 1. Intro to Swift & Xcode
          • Lecture Handout
          • Lecture Demo
          • 🍼Project: Swift Basics
        • 2. UIKit and AutoLayout
          • Lecture Handout
          • Lecture Demo
          • πŸ›’Project: UIKit + AutoLayout
        • 3. Navigation, MVC, and Delegation
          • Lecture Handout
          • Lecture Demo
          • Project: Navigation + Delegation
        • 4. UITableView
          • Lecture Handout
          • Lecture Demo
          • Project: UITableView
        • 5. UICollectionView
          • Lecture Handout
          • Lecture Demo
          • Project: UICollectionView
        • 6. Networking I
          • Lecture Handout
          • Lecture Demo
          • Project: Persistence
        • 7. Networking II
          • Lecture Handout
          • Lecture Demo
            • Message Board
          • Project: Networking II
        • 8. Swift UI
        • 9. CocoaPods
          • 🍫Lecture Handout
          • πŸ§‘β€πŸ³Lecture Demo
      • Cheat Sheets
        • Setting Up a New Xcode Project
        • Submitting Your Projects
        • Setting Up CocoaPods
    • 2022
      • SwiftUI
    • 2021
      • Adding Flare
      • Project: UIView Animations (Optional - Extra Credit)
      • UIView Animations
      • Xcode Tips and Tricks
    • 2019
      • Firebase
      • Persistence: UserDefaults
  • Swift Guide (ARCHIVED)
    • About this Textbook
    • Documentation
    • Constants and Variables
      • Variable Properties
      • Lazy and Static Variables
    • Functions
    • Ranges
    • Arrays
      • Basic Array Operations
      • Iteration and Enumeration
      • Advanced Array Operations
    • Tuples
    • Conditions and While Loops
    • For Loops
    • Enums and Switches
      • Enums with Associated Values
      • Indirect Enums
    • Classes and Structs
    • Optionals
    • Dictionaries
      • Dictionary Implementation
    • Closures
    • Constraints
    • Generics
    • Protocols
      • Protocols With Associated Types
    • Casting
    • Errors
    • Networking
      • Result
    • Inout
Powered by GitBook
On this page
  • Fear Mongering Preface
  • Associated Types
  • Generic Constraint
  • Conditional Functions
  • Credits

Was this helpful?

Export as PDF
  1. Swift Guide (ARCHIVED)
  2. Protocols

Protocols With Associated Types

PreviousProtocolsNextCasting

Last updated 3 years ago

Was this helpful?

This is one of the few articles with a source. See the bottom of the page for more information.

Fear Mongering Preface

Be warned this is one of the hardest, most frustrating, and rare Swift features (at least when using UI). This is because it flips protocols on its head and forces you to think about types differently. Not to mention it often forces either the use of enums with associated values (another advanced topic) or type erasure (a topic I have yet to understand).

As you will find throughout this article, this a topic even I am still trying to grasp. So, I will do my best to explain everything as well as I can, but there will be some gaps in my knowledge. In fact, there is evidence that this is an unfinished feature, so my knowledge may become outdated soon.

Okay, for those I have not scared yet, the advantage for protocols with associated types or PATs is kind of like generics with classes (just kind of). I like to think of them as abstract classes (in Java) that are generic. If you remember from the discussion of protocols, our implementation of sets were limited to single types–we used integers specifically.

Associated Types

Let's fix this by making this parametric. Similar to generics, we need to create a name for our parametrizing type. To do this, we use the keyword associatedType followed by a type name, which can be constrained (more on this later). Let's call the type of our elements of our set Element: associatedType Element.

protocol Set {
    associatedtype Element
    /**
    Number of unique elements in the set
     */
    var length: Int { get }
    
    /**
    Unique elements in the set
     */
    var values: [Element] { get set }
    
    /**
    Adds `element` to the set.
     */
    mutating func add(_ element: Element)
    
    /**
    - returns:
     whether a set contains an element
     */
    func contains(_ element: Element) -> Bool
}

Then, when we want a type to conform or adopt our Set implementation, we can explicitly dictate the type with a typealias or be consistent with our typing.

struct IntegerSetExplicit: Set {
    typealias Element = Int
    
    var length: Int { values.count }
    
    var values = [Element]()
    
    mutating func add(_ element: Element) {
        if !contains(element) {
            values.append(element)
        }
    }
    
    func contains(_ element: Element) -> Bool {
        values.contains(element)
    }
    
    
}

struct IntegerSetImplicit: Set {
    
    var length: Int { values.count }
    
    var values = [Int]()
    
    mutating func add(_ element: Int) {
        if !contains(element) {
            values.append(element)
        }
    }
    
    func contains(_ element: Int) -> Bool {
        values.contains(element)
    }
}

Generic Constraint

This seems all well and dandy... Why is this so hard?

Let's try storing it in a variable...

var mySet: Set = ...

Oops, that doesn't compile... Well, actually that makes sense. If we think about this as a generic, we need to specify the type. Let's fix that:

protocol IntegerSet: Set where Element == Int {}
var myIntegerSet: IntegerSet = ...

The same error!

In fact, when working with PATs you will see this often:

"error: protocol '<SomeProtocol>' can only be used as a generic constraint because it has Self or associated type requirements"

In short, PATs are not types. You can only use them as constraints! This is a huge problem. Let's revisit the Swift standard library.

This, funnily enough, as Gallagher points out, is actually a list of all the ways you cannot use protocols with associated types.

Now you can get around some of this with Opaque typing, but that is a topic for another article, as I have yet to understand them!

So, what does this mean for us? We can't use PATs as types. We can only use them to show conformance. This means instead of using our protocol Set as a type we need a type that conforms to Set. If you have not reviewed generics and constraints, this may seem like a great sin and that Swift breaks all of your conceptions surrounding parametric polymorphism, but it doesn't. It's just annoying.

Let's say we want to write a function concatenate that takes in a bunch of sets and concatenates the result. If we were working with a normal protocol, we could do:

func concatenate(_ sets: [Set]) -> Set

But since, again, we can only use Set as a tool to show conformance. But, we want to keep this as widely applicable as possible. So, we use generics!

func concatenate<T: Set>(sets: [T]) -> T {
    assert(sets.count > 0)
    var base = sets[0]
    for set in sets[1..<sets.count] {
        for elt in set.values {
            base.add(elt)
        }
    }
    return base
}

This is basically the same, except for two differences. The less important is that we have given Set a new name. The more important is that all of our implementations of Set must be the exact same. This was not the case with normal protocols. This is where things get extra dicey.

If you want multiple implementations in the same list, there are two methods you can use. The first, which I highly suggest is enums with associated values–it's so OCaml and is very nice. The other is type erasure, which I sadly am not familiar with yet, so you will need to look into it yourself. But my vague understanding is that you use a wrapper type to abstract away the associated type and distill it into its barebones features to guarantee only what you need.

Conditional Functions

We can use constraints to add functionality! We can add constraints directly to the associated type if it is part of the invariant or conditionally to certain functions.

See constraints for more:

contains seems like something we can implement broadly. Assuming we have access to equality, we can just iterate through the values and check!

extension Set {
    
    func contains(_ element: Element) -> Bool where Element: Equatable {
        for val in values {
            if val == element {
                return true
            }
        }
        return false
    }
    
}

Or, better yet, we can just Array's implementation of contains!

extension Set {
    
    func contains(_ element: Element) -> Bool where Element: Equatable {
        values.contains(element)
    }
    
}

For those of you that remember default implementations, this allows us to create conditional default implementations! This means that if we ever implement Set with a type that conforms to Equatable, we don't need to implement contains! Otherwise, we do.

Credits

Most of my understanding as well as my discussion originates from the following video. It is somewhat outdated, but it is a nice narrative of why this feature is the way it is and how to use it (kind of):

Enums with Associated Values
Constraints