My notes from Lecture 3 covering MVVM architecture, struct vs class, generics, protocols, and functional programming in SwiftUI
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.
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.
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:
View: The UI layer that serves as a “visual manifestation of the Model”. Views are:
ViewModel: The “gatekeeper” that connects View to Model. It serves as:
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.
The biggest difference is that structs are value types and classes are reference types.
structs store data directly in the variable with no pointers. Key characteristics:
let
makes truly immutable, var
allows changesstruct MemoryGame<CardContent> {
var cards: [Card]
var indexOfTheOneAndOnlyFaceUpCard: Int?
// Free init automatically created:
// init(cards: [Card], indexOfTheOneAndOnlyFaceUpCard: Int?)
}
classes store a pointer to heap memory. Key characteristics:
let
class variables can have their contents modified99% of your code should use structs. Use classes only for:
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 define what a type can do, not how it does it. They specify:
get
) or read-write (get set
)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 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 StringsParameter names are not part of the function type.
Focuses on data encapsulation. You encapsulate real-world concepts with their associated data and functionality. Uses inheritance for code reuse when objects are similar.
Focuses on describing how things behave. The key benefits are:
Swift combines functional programming with protocol-oriented programming, using protocols to define behaviors that structs can adopt.
Models should not import SwiftUI. When creating Model files in Xcode, choose “Swift File” not “SwiftUI View”.
Swift is exceptionally good at knowing when structs change. This capability is fundamental to both copy-on-write performance and SwiftUI’s reactive updates.
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.
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.