My notes from Lecture 4 covering MVVM implementation, access control, initializers, static members, and functional programming with closures
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.
The lecture focuses on implementing the Model-View-ViewModel (MVVM) pattern in SwiftUI, which is the fundamental architecture for SwiftUI apps.
1. Model (MemoryGame.swift
)
2. View (ContentView.swift
)
3. ViewModel (EmojiMemoryGame.swift
)
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:
Swift uses access control to protect code integrity:
class EmojiMemoryGame {
private var model: MemoryGame<String> // Only this class can access
}
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.
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
}
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 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:
The lecture demonstrates passing functions as arguments:
func init(numberOfPairsOfCards: Int, cardContentFactory: (Int) -> CardContent) {
// cardContentFactory is a function parameter
}
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 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
}
// External name suppression with _
func choose(_ card: Card) {
// Called as: choose(someCard) not choose(card: someCard)
}
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
private
and private(set)
to protect your codeThis lecture beautifully demonstrates how Swift’s language features support clean architecture in SwiftUI applications through practical implementation of a memory card game.