Applying Protocol-Oriented Programming in Development

Oct 17 2023 · Swift 5.9, iOS 17, Xcode 15

Lesson 02: Protocol Design & Composition

Demo 3

Episode complete

Play next episode

Next
Transcript

Demo

Head back to the playground in Xcode. First, define a new protocol called RandomEntertainmentPicker to help you pick what to do this evening:

protocol RandomEntertainmentPicker {
    associatedtype Item
    func getItemToEnjoy() -> Item?
}

This defines a protocol that returns a random item to enjoy. Then, conform MediaShelf to both MediaCollection and this protocol:

struct MediaShelf<T: MediaItem>: MediaCollection, RandomEntertainmentPicker {
    var items: [T] = []
    
    func getItemToEnjoy() -> T? {
        items.randomElement()
    }
}

This adds RandomEntertainmentPicker to the list of protocols that MediaShelf conforms to, then implements getItemToEnjoy() by returning a random item from the array.

Now, use the new protocol to find a video game to play:

print("Let's play \(videoGameShelf.getItemToEnjoy()?.title ?? "Nothing!")")

Run the playground and see what you’ll be playing tonight!

Next, create a new function that uses protocol composition at the call site:

func printItemDetails(_ item: MediaItem & Codable) throws {
    let jsonEncoder = JSONEncoder()
    let data = try jsonEncoder.encode(item)
    if let string = String(data: data, encoding: .utf8) {
        print(string)
    }
}

This is very similar to the example with generics, except that it uses protocol composition to ensure you can only pass items to this function that match the requirements. Print out the details for Catan:

try printItemDetails(catan)

Run the playground. You’ll see the JSON data for Catan. Try and pass a movie to the function; you’ll get a compiler error:

try printItemDetails(noTimeToDie)

You get that compiler error because movies aren’t Codable, so they don’t satisfy the requirements for the function.

[Remove the line of code]

Finally, create a new type to represent a group of card games:

struct CardGames: RandomEntertainmentPicker {
    let games: [CardGame]
    
    func getItemToEnjoy() -> CardGame? {
        return games.randomElement()
    }
}

This defines a new type that conforms to RandomEntertainmentPicker and holds a CardGame collection, which was defined in the starter project. You can add some games and find something to play:

let bridge = CardGame(title: "Bridge")
let solitaire = CardGame(title: "Solitaire")

let cardGames = CardGames(games: [bridge, solitaire])
guard let cardGameToPlay = cardGames.getItemToEnjoy() else {
    fatalError("We have no card games!")
}
print("We have some cards so we'll play \(cardGameToPlay.title)")

Run the playground and discover which game you’ll play tonight.

CardGames don’t conform to MediaItem. You can’t store them on a MediaShelf, because that doesn’t make sense. But it does make sense to be able to find a card game to play.

This shows how you can use protocols to define specific behaviors and use protocol composition to combine them when it makes sense.

See forum comments
Cinema mode Download course materials from Github
Previous: Instruction 3 Next: Conclusion