Jeffrey Hicks

Jeffrey Hicks

Platform Eng @R360

Stanford CS193p 2023: Lecture 04 Notes

My notes from Lecture 4 covering MVVM implementation, access control, initializers, static members, and functional programming with closures

By Stanford CS193p • Sep 1, 2025
Lecture 4 | Stanford CS193p 2023
by Stanford CS193p
Published Aug 25, 2023

Screen capture of Lecture 4 of Stanford's CS193p course (Developing iOS Applications using SwiftUI) from Spring quarter 2023. Connecting the Memorize game's View Model to its Model and View.

MVVM Architecture in SwiftUI

The lecture focuses on implementing the Model-View-ViewModel (MVVM) pattern in SwiftUI, which is the fundamental architecture for SwiftUI apps.

The Three Components

1. Model (MemoryGame.swift)

  • UI independent - contains the game logic
  • The “truth” of your app
  • In the example: handles card matching logic

2. View (ContentView.swift)

  • Declarative UI that reflects the Model’s state
  • Stateless and reactive
  • Automatically redraws when data changes

3. ViewModel (EmojiMemoryGame.swift)

  • Binds the View to the Model
  • Acts as the “butler” for the View - arranging and presenting data nicely
  • Interprets the Model for the View

Key MVVM Rules

class EmojiMemoryGame {
    private var model: MemoryGame<String>  // ViewModel has access to Model
    
    var cards: Array<MemoryGame<String>.Card> {
        return model.cards  // Provides data to View
    }
}

Important principles:

  • View never talks directly to Model
  • Model never knows about View or ViewModel
  • ViewModel never stores View references - communication is one-way through published changes

Access Control

Swift uses access control to protect code integrity:

Private Properties

class EmojiMemoryGame {
    private var model: MemoryGame<String>  // Only this class can access
}

Private Setters

struct MemoryGame<CardContent> {
    private(set) var cards: Array<Card>  // Others can read, but only we can modify
}

Best Practice: Start with private by default, then make public only what’s needed.

Initializers in Swift

Structs vs Classes

Structs get free initializers with all properties:

struct Card {
    var isFaceUp: Bool
    var isMatched: Bool
    let content: CardContent
}
// Free init: Card(isFaceUp: Bool, isMatched: Bool, content: CardContent)

Classes only get free init if all properties have default values:

class EmojiMemoryGame {
    var model: MemoryGame<String>  // No default = must write init
}

Custom Initializers

init(numberOfPairsOfCards: Int, cardContentFactory: (Int) -> CardContent) {
    cards = []
    for pairIndex in 0..<numberOfPairsOfCards {
        let content = cardContentFactory(pairIndex)
        cards.append(Card(content: content))
        cards.append(Card(content: content))
    }
}

Static Properties and Functions

Static members solve initialization order problems:

class EmojiMemoryGame {
    private static let emojis = ["🚗", "✈️", "🚁", "🚂"]  // Initialized first
    
    private static func createMemoryGame() -> MemoryGame<String> {
        MemoryGame<String>(numberOfPairsOfCards: 4) { pairIndex in
            emojis[pairIndex]
        }
    }
    
    private var model = createMemoryGame()  // Can use static members
}

Key Points:

  • Static = global but namespaced to the type
  • Solves “property initializers run before self” errors
  • Can be made private for encapsulation

Functional Programming with Closures

The lecture demonstrates passing functions as arguments:

Function as Parameter Type

func init(numberOfPairsOfCards: Int, cardContentFactory: (Int) -> CardContent) {
    // cardContentFactory is a function parameter
}

Closure Syntax Evolution

From named function to inline closure:

// 1. Named function
func createCardContent(forPairAtIndex index: Int) -> String {
    return emojis[index]
}

// 2. Pass function
MemoryGame(numberOfPairsOfCards: 4, cardContentFactory: createCardContent)

// 3. Inline closure with full syntax
MemoryGame(numberOfPairsOfCards: 4, cardContentFactory: { (index: Int) -> String in
    return emojis[index]
})

// 4. Type inference
MemoryGame(numberOfPairsOfCards: 4) { index in
    emojis[index]
}

// 5. With $0 shorthand (not recommended here for readability)
MemoryGame(numberOfPairsOfCards: 4) { emojis[$0] }

Swift Language Features

For Loops

Swift only supports for-in loops:

for pairIndex in 0..<numberOfPairsOfCards {
    // Loop body
}

// Use _ when you don't need the iteration variable
for _ in 0..<numberOfPairsOfCards {
    // Just repeat n times
}

Function Parameter Naming

// External name suppression with _
func choose(_ card: Card) {
    // Called as: choose(someCard) not choose(card: someCard)
}

Type Inference

Swift infers types whenever possible:

// These are equivalent:
private var model: MemoryGame<String> = createMemoryGame()
private var model = createMemoryGame()  // Type inferred

// Array literals
let cards: Array<Card> = []
let cards = [Card]()
let cards = []  // If type can be inferred from context

Key Takeaways

  1. MVVM is essential - It provides clear separation of concerns in SwiftUI apps
  2. Access control matters - Use private and private(set) to protect your code
  3. Functional programming is core - Get comfortable passing functions as arguments
  4. Type inference reduces boilerplate - Let Swift figure out types when obvious
  5. Static members solve initialization problems - Use them for shared data needed during init

This lecture beautifully demonstrates how Swift’s language features support clean architecture in SwiftUI applications through practical implementation of a memory card game.

Related

#swiftui