Jeffrey Hicks

Jeffrey Hicks

Platform Eng @R360

Stanford CS193p 2023: Lecture 03 Notes

My notes from Lecture 3 covering MVVM architecture, struct vs class, generics, protocols, and functional programming in SwiftUI

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

Screen capture of Lecture 3 of Stanford's CS193p course (Developing iOS Applications using SwiftUI) from Spring quarter 2023. MVVM Architecture and the Swift Type System.

This lecture introduces two fundamental concepts for iOS development: the MVVM architectural pattern and Swift’s type system. These concepts form the foundation for building scalable, maintainable SwiftUI applications.

MVVM Architecture (Model-View-ViewModel)

The Importance of Separation

Separating logic and data from UI is critically important in SwiftUI. SwiftUI is built around the concept of having your data and logic completely separate from the UI that displays it to users.

The Three Components

Model: Contains all the logic and data of your application. In a Memorize game, this includes “what happens when you click on a card” and “are the cards face up or face down”. The Model is:

  • UI Independent: Never imports SwiftUI
  • The Single Source of Truth: All data and logic queries must go through the Model
  • Generic and Flexible: Could be a simple struct, SQL database, machine learning code, or REST API

View: The UI layer that serves as a “visual manifestation of the Model”. Views are:

  • Stateless: Don’t hold their own state, only display what’s in the Model
  • Declarative: You describe what the UI should look like, not how to build it
  • Reactive: Automatically updates when the Model changes

ViewModel: The “gatekeeper” that connects View to Model. It serves as:

  • Interpreter: Converts complex Model data (like SQL) into simple variables for the View
  • Protector: Prevents the UI from damaging the Model
  • Change Detector: Notices when the Model changes and publishes updates
  • Intent Processor: Converts user interactions into meaningful Model operations

The Data Flow Process

Model to View: When something changes in the Model, the ViewModel notices and publishes “something changed”. SwiftUI then intelligently redraws only the affected Views.

View to Model: User interactions are processed as “user intent” - semantic actions like “choose this card” rather than UI-specific actions like “tap”. The ViewModel translates these intents into Model modifications.

Three Connection Approaches

  1. @State in View: Extremely minimal separation, only for very simple, temporary Views
  2. Gatekeeper Pattern: The recommended approach - all Model access goes through ViewModel
  3. Hybrid Approach: ViewModel sometimes allows direct Model access, but creates tangled architecture as apps grow

Swift Type System Fundamentals

struct vs class: The Core Distinction

The biggest difference is that structs are value types and classes are reference types.

Value Types (struct)

structs store data directly in the variable with no pointers. Key characteristics:

  • Copy Semantics: When passed to functions or assigned, you get a copy
  • Copy-on-Write: For performance, Swift only copies when data is modified
  • Functional Programming Foundation: Enables provability - functions always behave predictably
  • No Inheritance: Inheritance is considered limiting and unnecessary
  • Powerful Free Initializer: Creates init for all stored properties
  • Explicit Mutability: let makes truly immutable, var allows changes
struct MemoryGame<CardContent> {
    var cards: [Card]
    var indexOfTheOneAndOnlyFaceUpCard: Int?
    
    // Free init automatically created:
    // init(cards: [Card], indexOfTheOneAndOnlyFaceUpCard: Int?)
}

Reference Types (class)

classes store a pointer to heap memory. Key characteristics:

  • Shared References: Multiple variables can point to the same instance
  • Automatic Reference Counting: Memory cleaned up when reference count hits zero
  • Single Inheritance: Traditional OOP inheritance model
  • Weak Free Initializer: Only works if all properties have default values
  • Always Mutable: Even let class variables can have their contents modified

When to Use Each

99% of your code should use structs. Use classes only for:

  • ViewModels: Because they need to be shared across multiple Views
  • Backward compatibility: With older iOS patterns (rarely needed)

Generics: “Don’t Care” Types

Generics let you build types that work with any data type. This enables type-agnostic programming.

struct Array<Element> {
    func append(_ element: Element) // Element is a "don't care" type
    subscript(index: Int) -> Element
}

var numbers = Array<Int>() // Now Element becomes Int

The generic type parameter (Element) becomes concrete when you create an instance. Swift uses generics extensively and they become very powerful when combined with protocols.

Protocols: Defining Behavior

Protocols define what a type can do, not how it does it. They specify:

  • Method signatures: Function names, parameters, and return types (no implementation)
  • Property requirements: Whether properties are read-only (get) or read-write (get set)
  • Inheritance: Protocols can inherit from other protocols
protocol View {
    var body: some View { get } // Property requirement
}

struct ContentView: View { // "behaves like" a View
    var body: some View {
        Text("Hello")
    }
}

Protocol conformance is about behavior, not inheritance. When you say struct ContentView: View, you’re declaring that ContentView behaves like a View, not that it inherits from View.

Functions as Types

Functions are first-class types in Swift. Function types are written as:

  • (Int, Int) -> Bool: Takes two Ints, returns Bool
  • () -> Void: Takes nothing, returns nothing
  • () -> [String]: Takes nothing, returns array of Strings

Parameter names are not part of the function type.

Programming Philosophy: Functional vs Object-Oriented

Object-Oriented Programming

Focuses on data encapsulation. You encapsulate real-world concepts with their associated data and functionality. Uses inheritance for code reuse when objects are similar.

Functional Programming

Focuses on describing how things behave. The key benefits are:

  • Provability: You can prove functions will always behave the same way
  • No External Interference: Value types prevent outside code from corrupting your data
  • Behavior-Based Design: Focus on what things can do rather than what they contain

Swift combines functional programming with protocol-oriented programming, using protocols to define behaviors that structs can adopt.

Practical Development Patterns

File Organization

Models should not import SwiftUI. When creating Model files in Xcode, choose “Swift File” not “SwiftUI View”.

Change Detection

Swift is exceptionally good at knowing when structs change. This capability is fundamental to both copy-on-write performance and SwiftUI’s reactive updates.

User Intent Pattern

Convert UI actions into semantic Model operations. Instead of methods like tap(), create methods like choose(card:) that express what the user intends to accomplish.

This architectural approach and type system understanding provides the foundation for building robust, maintainable iOS applications with SwiftUI.

Swift Type System Review

Protocols set contracts, requiring properties and functions.

Structs and Classes implement (conform to) protocols by providing those functions/properties.

Classes can inherit from other classes, but structs cannot.

Generics can be used to parameterize structs, classes, protocols, and functions—making them work with any type.

Functions are the building blocks: they can be protocol requirements, generic, etc.; methods in structs/classes are just functions bound to the type.

In SwiftUI, structs like ContentView conform to the View protocol, fulfilling it by providing a body function that builds their UI.

Related

#swiftui