What Is a Programming Paradigm?
💡 The most important idea
A programming paradigm is a style of thinking about and organising code.
Paradigms are not competing religions from which we must choose only one.
A modern iOS application can use several paradigms at the same time, with each one solving a different part of the problem.
Swift is often described as a multi-paradigm programming language.
This means Swift supports several different approaches to writing and organising software.
We can write imperative code that gives the computer a sequence of instructions.
We can organise long-lived behaviour inside objects.
We can model capabilities using protocols.
We can transform collections using functional operations.
We can react to events and changes in state.
We can also use SwiftUI to declare what an interface should look like for the current state of the application.
These approaches are not mutually exclusive.
A well-designed application may use all of them.
What Does the Word Paradigm Mean?
A programming paradigm is a general model for how we express a solution in code.
It affects the kinds of questions we ask while designing the program.
An imperative programmer may ask:
What sequence of instructions should the computer execute?
An object-oriented programmer may ask:
Which object should own this state and behaviour?
A functional programmer may ask:
How can this value be transformed into another value?
An event-driven programmer may ask:
What should happen when this event occurs?
A reactive programmer may ask:
What parts of the system depend upon this changing state?
A declarative programmer may ask:
What result should exist for the current inputs?
These are different ways of describing and dividing the same application.
Swift Is a Multi-Paradigm Language
Swift was not designed around only one programming style.
It supports several paradigms because different problems benefit from different forms of expression.
| Paradigm | Main Question | Common Swift Example |
|---|---|---|
| Imperative | What instructions should run? | Assignments, loops and control flow |
| Object-oriented | Which object owns this state and behaviour? | Classes and reference identity |
| Protocol-oriented | Which capabilities does this type provide? | Protocols and protocol extensions |
| Functional | How should values be transformed? | map, filter and reduce |
| Event-driven | What happens when an event occurs? | Callbacks, notifications and delegate methods |
| Reactive | What should update when state changes? | Observation, bindings and publishers |
| Declarative | What should the result look like? | SwiftUI view declarations |
The presence of one paradigm does not remove the need for the others.
SwiftUI did not turn every part of iOS development into declarative programming.
It introduced a declarative way to describe a user interface.
The model, networking layer, database integration and application services may still use event-driven, imperative, object-oriented, protocol-oriented and functional code.
Imperative Programming
Imperative programming describes a sequence of commands that change the state of the program.
var total = 0
for price in prices {
total += price
}
print(total)
The code tells the computer how to calculate the result.
- Create a variable.
- Loop through the prices.
- Add each price to the total.
- Print the result.
Much of ordinary Swift code is imperative.
A function may retrieve some data, validate it, update a property and call another function.
func loadProfile() async {
isLoading = true
errorMessage = nil
do {
profile = try await apiClient.fetchProfile()
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
This is a sequence of instructions.
There is nothing outdated or inferior about imperative code.
It is often the clearest way to express application behaviour.
Object-Oriented Programming
Object-oriented programming organises state and behaviour around objects with identity.
final class ProfileModel {
var name: String
var isOnline: Bool
init(name: String, isOnline: Bool) {
self.name = name
self.isOnline = isOnline
}
func markAsOnline() {
isOnline = true
}
}
The ProfileModel owns its properties and the operations that affect them.
Because it is a class, it has reference identity.
Different parts of the application can refer to the same instance.
This is useful when several screens or services need to share one source of truth.
Protocol-Oriented Programming
Protocol-oriented programming describes behaviour in terms of capabilities rather than concrete inheritance hierarchies.
protocol ProfileLoading {
func fetchProfile() async throws -> Profile
}
A type can conform to the protocol without needing to inherit from a particular base class.
final class APIProfileLoader: ProfileLoading {
func fetchProfile() async throws -> Profile {
// Download the profile.
}
}
The protocol tells us what the type can do.
The concrete type decides how that behaviour is implemented.
Functional Programming
Functional programming often focuses on transforming values rather than issuing a long sequence of mutations.
let activeNames = users
.filter(\.isActive)
.map(\.name)
.sorted()
Each operation produces a new value.
The code reads as a transformation:
Users
│
▼
Active users
│
▼
Names
│
▼
Sorted names
Swift is not a purely functional language, but it contains many functional ideas.
Closures, higher-order functions, immutable values and value semantics all make functional techniques natural to use.
Event-Driven Programming
An event-driven system responds to events that occur over time.
An event may be:
- A button being pressed.
- A network request completing.
- A database record changing.
- A notification arriving.
- A location update being received.
- A timer firing.
- A user signing in or out.
The application does not necessarily perform all of its work in one continuous sequence.
Instead, it waits for something to happen and then responds.
func saveButtonPressed() {
saveProfile()
}
func databaseDidChange(
profile: Profile
) {
currentProfile = profile
}
apiClient.fetchProfile { result in
handle(result)
}
Most real iOS applications are heavily event-driven.
The user performs actions.
Servers return responses.
Databases publish changes.
The operating system delivers notifications and lifecycle events.
The model responds to those events and updates the application state.
The Model Is Often Event-Driven
In a modern iOS application, the model is not merely a collection of passive structures.
The model may contain the main workings of the feature.
It may:
- Load information from an API.
- Listen for changes in a database.
- Apply business rules.
- Coordinate several services.
- Store the latest application state.
- Respond to user intentions.
- Publish changes for the interface.
Consider a model that listens to an external source of profile data.
import Observation
@Observable
@MainActor
final class ProfileModel {
var profile: Profile?
var isLoading = false
var errorMessage: String?
private let repository: ProfileRepository
init(repository: ProfileRepository) {
self.repository = repository
}
func start() async {
isLoading = true
do {
for try await profile in repository.profileUpdates() {
self.profile = profile
}
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
The model listens for a sequence of changes.
Each new profile is an event.
When an event arrives, the model updates its state.
This is event-driven behaviour.
Why Listen for Database Changes?
The information displayed on a screen may change outside the current device.
Another user may edit a shared record.
A server process may update a status.
The same user may make a change on another device.
A database observer or stream can inform the model when the external value changes.
for try await launch in repository.launchUpdates() {
currentLaunch = launch
}
The purpose is to keep the application’s local state aligned with its external source of truth.
Remote database changes
│
▼
Repository emits event
│
▼
Model receives event
│
▼
Model state changes
│
▼
SwiftUI observes affected state
│
▼
Interface description is reevaluated
Reactive Programming
Reactive programming is concerned with values that change over time and the parts of the system that depend upon those values.
Instead of manually instructing every consumer to refresh, we establish a relationship between state and its dependants.
In SwiftUI, a view reads observable state.
struct ProfileView: View {
let model: ProfileModel
var body: some View {
Text(model.profile?.name ?? "No profile")
}
}
The view depends upon the profile name.
When that observed value changes, SwiftUI can reevaluate the relevant view declaration.
The relationship is reactive:
State changes
│
▼
Dependent view becomes invalid
│
▼
View declaration is evaluated again
The developer does not usually need to call a method such as:
profileLabel.refreshFromModel()
The dependency between the state and the interface has already been described.
Reactive Does Not Mean the Same Thing as Declarative
Reactive and declarative programming are closely related in SwiftUI, but they describe different ideas.
Reactive programming concerns how changes flow through the system.
Declarative programming concerns how the desired result is expressed.
The model changes.
SwiftUI reacts to that change.
The view declaration then describes the interface that should exist for the new state.
Model state changes
│
▼
Reactive dependency triggers reevaluation
│
▼
Declarative view code describes the new interface
Declarative Programming
Declarative programming describes what the result should be rather than listing every command required to produce it.
A SwiftUI view might say:
struct LaunchStatusView: View {
let isLaunching: Bool
var body: some View {
VStack {
Text("Rocket Mission")
if isLaunching {
ProgressView()
Text("Preparing for launch")
} else {
Text("Ready")
}
}
}
}
This code describes two possible interfaces.
If isLaunching is true, show a progress indicator and a message.
Otherwise, show the ready state.
We do not manually create every label, insert it into a hierarchy, remove the previous label and update all constraints.
We declare what the interface should contain for the current value.
The Earlier Imperative UIKit Style
With UIKit, interface updates are often expressed imperatively.
func updateInterface(isLaunching: Bool) {
if isLaunching {
progressView.startAnimating()
progressView.isHidden = false
statusLabel.text = "Preparing for launch"
} else {
progressView.stopAnimating()
progressView.isHidden = true
statusLabel.text = "Ready"
}
}
This code tells the interface objects exactly what to do.
Start this animation.
Hide or reveal this object.
Replace this text.
The developer is responsible for moving the existing interface from its previous state into its next state.
The SwiftUI Style
var body: some View {
if isLaunching {
VStack {
ProgressView()
Text("Preparing for launch")
}
} else {
Text("Ready")
}
}
The developer describes the desired result for each state.
SwiftUI is responsible for reconciling that description with the interface it is already managing.
UIKit commonly asks:
“Which existing interface objects should I mutate?”
SwiftUI commonly asks:
“What should the interface be for the current state?”
SwiftUI Is a Declarative Layer
SwiftUI gives application developers a declarative layer for describing interfaces on Apple platforms.
That layer interoperates with the existing platform frameworks, including UIKit on iOS.
It would be too simplistic to say that every SwiftUI View directly becomes one corresponding UIKit view.
SwiftUI owns the process of interpreting view declarations, maintaining identity, tracking dependencies and updating the rendered interface.
However, it is useful to understand SwiftUI as a higher-level interface system operating alongside and above the mature platform infrastructure beneath it.
SwiftUI Did Not Make the Whole Application Declarative
A common misunderstanding is that adopting SwiftUI means every part of the application should now be written declaratively.
That is not the case.
SwiftUI is especially declarative at the view layer.
The model may still need explicit, event-driven operations.
func submitOrder() async {
guard canSubmit else {
return
}
isSubmitting = true
do {
let receipt = try await orderService.submit(order)
latestReceipt = receipt
} catch {
submissionError = error
}
isSubmitting = false
}
This function contains imperative control flow.
It responds to an event.
It performs asynchronous work.
It updates observable state.
A SwiftUI view can then declare how those state values should be represented.
struct OrderView: View {
let model: OrderModel
var body: some View {
VStack {
if model.isSubmitting {
ProgressView("Submitting order")
}
if let receipt = model.latestReceipt {
Text("Receipt: \(receipt.number)")
}
if let error = model.submissionError {
Text(error.localizedDescription)
}
Button("Submit") {
Task {
await model.submitOrder()
}
}
}
}
}
The feature combines several paradigms.
- The button press is an event.
- The model performs imperative work.
- The service may be object-oriented and protocol-oriented.
- The state is observed reactively.
- The view is described declaratively.
Passing a Feature of the Model to a View
A large application model may contain far more information than one screen needs.
We should avoid giving every view unrestricted access to the entire application.
Instead, a feature-specific model can expose the state and actions required by one interface.
@Observable
@MainActor
final class NextLaunchModel {
var launch: Launch?
var isLoading = false
var errorMessage: String?
private let launchRepository: LaunchRepository
init(launchRepository: LaunchRepository) {
self.launchRepository = launchRepository
}
func load() async {
isLoading = true
defer { isLoading = false }
do {
launch = try await launchRepository.fetchNextLaunch()
} catch {
errorMessage = error.localizedDescription
}
}
}
The view receives the model for this feature.
struct NextLaunchView: View {
let model: NextLaunchModel
var body: some View {
Group {
if model.isLoading {
ProgressView("Contacting mission control")
} else if let launch = model.launch {
LaunchDetailsView(launch: launch)
} else if let errorMessage = model.errorMessage {
Text(errorMessage)
} else {
Text("No launch loaded")
}
}
.task {
await model.load()
}
}
}
The model contains the workings.
The view contains a declarative representation of the model’s current state.
The View Is a Function of State
A useful mental model is to think of a SwiftUI view as a function of its inputs.
View description = f(current state)
For example:
func description(
for state: LaunchState
) -> some View {
switch state {
case .idle:
Text("Ready")
case .loading:
ProgressView()
case .loaded(let launch):
Text(launch.name)
case .failed(let message):
Text(message)
}
}
The same state should produce the same intended interface description.
When the state changes, the description may change.
SwiftUI then determines what work is required to make the managed interface reflect the new result.
SwiftUI Views Are Value Types
SwiftUI views are usually structures.
struct LaunchView: View {
let launch: Launch
var body: some View {
Text(launch.name)
}
}
The LaunchView value is a lightweight description.
It is not the persistent on-screen object in the same sense as a UIView instance.
SwiftUI may create and evaluate view values frequently.
This is practical because the value describes the interface rather than owning all of the long-lived rendering machinery itself.
What Happens When State Changes?
Suppose the model initially contains no launch.
model.launch = nil
The view declaration may produce:
Text("No launch loaded")
Later, a networking event updates the model.
model.launch = downloadedLaunch
SwiftUI observes that a value read by the view has changed.
The affected view declaration is evaluated again.
It may now produce:
LaunchDetailsView(
launch: downloadedLaunch
)
SwiftUI reconciles the new description with the interface state it is already managing.
Does SwiftUI Simply Compare Two View Structs?
It is tempting to imagine that SwiftUI always stores two complete view values and performs a simple equality comparison between them.
That is a useful introductory picture, but it is not a complete description of the framework.
SwiftUI views do not generally need to conform to Equatable.
The framework uses information including:
- View identity.
- Structural position.
- Observed dependencies.
- State managed by the framework.
- The types making up the view hierarchy.
- Framework-specific reconciliation rules.
SwiftUI can reevaluate a view’s body and determine how the managed interface should be updated.
The important mental model is not that Swift performs an ordinary == operation on two screens.
The important idea is:
The previous rendered state is already known to SwiftUI.
The new view declaration describes the desired state.
SwiftUI reconciles the difference and performs the required updates.
Identity Still Matters
Although SwiftUI view declarations use value types, the framework still needs to understand identity.
Consider a list of launches.
List(launches) { launch in
Text(launch.name)
}
SwiftUI needs to know which launch is which.
If one launch changes position, the framework should understand that it is the same logical item moving rather than an entirely unrelated value appearing.
struct Launch: Identifiable {
let id: UUID
let name: String
}
Declarative programming does not eliminate identity.
It changes who manages much of the interface identity and mutation.
Persistent State Cannot Live Only in Temporary View Values
SwiftUI view values may be recreated.
Therefore, long-lived mutable state cannot be treated as an ordinary stored variable inside a temporary view description.
SwiftUI provides property wrappers and observable models to connect persistent state to those descriptions.
struct CounterView: View {
@State private var count = 0
var body: some View {
Button("Count: \(count)") {
count += 1
}
}
}
The CounterView is a value.
SwiftUI manages the persistent state associated with the view’s identity.
The value declaration reads that state and describes how it should appear.
Shared State Often Belongs in a Reference Model
When several views need access to the same changing data, a reference model may be appropriate.
@Observable
@MainActor
final class MissionModel {
var nextLaunch: Launch?
var connectionStatus: ConnectionStatus = .offline
func connect() async {
// Listen for remote events.
}
}
The model is a class because shared identity may be useful.
Several view values can refer to the same instance.
struct MissionDashboard: View {
let model: MissionModel
var body: some View {
VStack {
ConnectionStatusView(
status: model.connectionStatus
)
if let nextLaunch = model.nextLaunch {
LaunchSummaryView(launch: nextLaunch)
}
}
}
}
The class stores and coordinates the changing state.
The SwiftUI structures describe representations of that state.
The Combined Architecture
A modern feature may look like this:
External API or database
│
│ events
▼
Repository or service
│
│ values and callbacks
▼
Feature model
│
│ observable state
▼
SwiftUI view
│
│ declarative description
▼
SwiftUI reconciliation
│
▼
Rendered iOS interface
Each layer uses the paradigm that suits its responsibility.
The Service Layer May Be Event-Driven
protocol LaunchRepository {
func launchUpdates() -> AsyncThrowingStream<Launch, Error>
}
The repository produces events over time.
The Model May Be Imperative and Event-Driven
func beginObservingLaunches() async {
do {
for try await launch in repository.launchUpdates() {
latestLaunch = launch
}
} catch {
errorMessage = error.localizedDescription
}
}
The model explicitly responds to each event.
The Observation Layer Is Reactive
SwiftUI tracks that the view depends upon latestLaunch.
When it changes, the relevant view declaration becomes eligible for reevaluation.
The View Layer Is Declarative
var body: some View {
if let latestLaunch {
LaunchDetailsView(launch: latestLaunch)
} else {
Text("Waiting for launch data")
}
}
The view describes the interface for each possible state.
The Paradigms Complement One Another
This is the most important architectural conclusion.
Event-driven programming and declarative programming are not competing options from which the entire application must select one winner.
Reactive programming does not replace the model.
Functional programming does not replace objects.
Protocol-oriented programming does not prevent imperative functions.
Each paradigm can handle a different concern.
| Concern | Useful Paradigm |
|---|---|
| Responding to a database update | Event-driven |
| Coordinating a networking operation | Imperative and asynchronous |
| Sharing one model between several consumers | Object-oriented |
| Abstracting a repository capability | Protocol-oriented |
| Transforming downloaded values | Functional |
| Propagating state changes to dependants | Reactive |
| Describing the visible interface | Declarative |
A Rocket Launch Example
Imagine that the application displays the next rocket launch.
The database can change the launch date.
The model must listen for that external event.
@Observable
@MainActor
final class NextLaunchModel {
var launch: Launch?
var connectionState: ConnectionState = .connecting
private let repository: LaunchRepository
init(repository: LaunchRepository) {
self.repository = repository
}
func start() async {
do {
for try await launch in repository.nextLaunchUpdates() {
self.launch = launch
connectionState = .connected
}
} catch {
connectionState = .failed(
error.localizedDescription
)
}
}
}
The model is reacting to events from the repository.
The SwiftUI view observes the resulting model state.
struct NextLaunchView: View {
let model: NextLaunchModel
var body: some View {
VStack(spacing: 16) {
switch model.connectionState {
case .connecting:
ProgressView("Connecting to mission control")
case .connected:
if let launch = model.launch {
Text(launch.name)
.font(.title)
Text(launch.launchDate.formatted())
} else {
Text("Waiting for launch information")
}
case .failed(let message):
Text(message)
}
}
.task {
await model.start()
}
}
}
This one feature uses several paradigms.
- The repository emits database events.
- The model responds using event-driven and imperative code.
- The model exposes observable state.
- SwiftUI reacts to changes read by the view.
- The view declaratively describes each screen state.
Actions Still Travel Back Into the Model
Declarative views do not mean the user interface becomes passive.
The user still performs actions.
Those actions are events.
Button("Refresh") {
Task {
await model.refresh()
}
}
The button declaration is part of the declarative view hierarchy.
The button press itself is an event.
The model then performs imperative work.
The resulting state change flows reactively back to the view.
User presses button
│
▼
Event handler runs
│
▼
Model performs operation
│
▼
Model state changes
│
▼
SwiftUI observes dependency
│
▼
View declaration is reevaluated
Declarative Code Still Contains Control Flow
Declarative does not mean the code contains no conditions or logic.
var body: some View {
if user.isSignedIn {
AccountView(user: user)
} else {
SignInView()
}
}
The if statement still describes control flow.
The difference is what the control flow produces.
It produces a description of the desired interface rather than manually mutating a previously constructed interface object.
Declarative Does Not Mean No Side Effects Anywhere
Side effects such as networking, saving files and changing database values still need to occur.
They generally should not be performed directly while calculating a view’s body.
The body should primarily describe the interface.
Effects can be initiated through actions and lifecycle modifiers.
Button("Launch") {
Task {
await model.launchRocket()
}
}
.task {
await model.load()
}
The model then owns the work and updates the state that drives the declaration.
Why Keep the Model Separate?
If networking, database observation and business rules are placed directly inside view declarations, the architecture becomes difficult to reason about.
The view becomes responsible for both:
- Determining what should be displayed.
- Operating the entire feature.
A separate model allows the responsibilities to remain clearer.
| Model | View |
|---|---|
| Performs feature operations | Describes the interface |
| Responds to external events | Reads observable state |
| Applies business rules | Sends user actions to the model |
| Coordinates repositories and services | Chooses a visual representation |
| Maintains feature state | Declares layout and styling |
SwiftUI Is Not Merely a Different Syntax for UIKit
SwiftUI changes the relationship between application code and interface updates.
UIKit commonly gives the application direct access to long-lived interface objects that are mutated over time.
SwiftUI asks the application to provide descriptions derived from state while the framework manages much of the persistence and updating.
The difference is architectural, not only syntactic.
UIKit and SwiftUI Can Coexist
UIKit and SwiftUI are not competitors that cannot appear in the same application.
A UIKit application can host SwiftUI views.
A SwiftUI application can wrap UIKit components.
Many real applications use both.
The choice can be made feature by feature.
A declarative SwiftUI screen can operate alongside imperative UIKit infrastructure.
Paradigms Are Tools, Not Identities
A developer should not become so attached to one paradigm that every problem is forced into the same shape.
Not every value transformation needs a class.
Not every feature needs a complicated reactive stream.
Not every side effect belongs inside a view.
Not every piece of state needs to be global and observable.
Not every function becomes better when rewritten using functional operators.
The goal is not to prove that one paradigm is superior.
The goal is to select an appropriate form of expression for each responsibility.
A More Senior Mental Model
A less experienced description may be:
“SwiftUI uses declarative programming instead of imperative programming.”
A more complete description is:
“SwiftUI provides a declarative view layer. The wider application may still be event-driven, imperative, object-oriented, protocol-oriented and functional. Observable state creates a reactive connection between the model and the view. These paradigms cooperate to form the complete feature.”
The second explanation recognises that a real application contains several kinds of work.
Another Useful Mental Model
The model says:
“What is happening in the application?”
The reactive layer says:
“Which declarations depend upon what changed?”
The SwiftUI view says:
“What should the interface look like now?”
The framework says:
“What updates are required to present that result?”
These responsibilities are related, but they are not identical.
Common Misunderstandings
SwiftUI Means All Code Is Declarative
No.
SwiftUI primarily provides declarative interface construction.
Models and services still commonly contain imperative and event-driven code.
Reactive and Declarative Mean the Same Thing
No.
Reactive programming describes how changes propagate.
Declarative programming describes the desired result for the current state.
Value-Type Views Contain All Persistent Screen State
No.
SwiftUI manages persistent state using mechanisms associated with view identity, and shared state can live in observable reference models.
SwiftUI Always Compares Two Views With ==
No.
SwiftUI performs framework-managed dependency tracking and reconciliation.
Ordinary view values do not generally need to conform to Equatable.
UIKit Is No Longer Relevant
No.
UIKit remains a major part of the iOS platform, and SwiftUI interoperates with it.
One Paradigm Should Be Used Everywhere
No.
Different layers benefit from different paradigms.
Interview Questions
What is a programming paradigm?
A programming paradigm is a general style or model for organising and expressing a solution in code.
Why is Swift called a multi-paradigm language?
Swift supports several programming styles, including imperative, object-oriented, protocol-oriented, functional, event-driven, reactive and declarative programming.
What is imperative programming?
Imperative programming describes a sequence of instructions that change program state or produce a result.
What is event-driven programming?
Event-driven programming organises behaviour around events such as user actions, network responses, notifications and database changes.
What is reactive programming?
Reactive programming describes relationships between changing values and the parts of the system that depend upon them.
What is declarative programming?
Declarative programming describes the desired result for the current inputs rather than explicitly listing every mutation required to construct that result.
Is all SwiftUI application code declarative?
No.
SwiftUI views are written declaratively, while models and services often remain imperative and event-driven.
How do reactive and declarative programming work together in SwiftUI?
Reactive observation identifies that state used by a view has changed.
The view is then reevaluated and declaratively describes the interface appropriate for the new state.
Why are SwiftUI views usually value types?
They act as lightweight descriptions of the desired interface rather than owning all long-lived rendered objects themselves.
Does SwiftUI recreate the entire screen whenever state changes?
Not necessarily.
SwiftUI reevaluates affected declarations and reconciles the resulting description with the interface it is already managing.
Does SwiftUI simply compare old and new views using equality?
Not generally.
SwiftUI uses identity, structure, dependency tracking and framework-managed reconciliation rather than requiring every view to support ordinary equality comparison.
Why is an iOS model often event-driven?
The model must respond to user actions, asynchronous requests, notifications and external database changes that occur over time.
Why might a model listen to database changes?
Listening keeps local application state aligned with changes made by servers, other users or other devices.
Why should business logic usually remain outside the view?
Separating business logic from presentation makes the feature easier to test, reuse, maintain and reason about.
Do programming paradigms compete with each other?
No.
They often complement one another by addressing different responsibilities within the same application.
Final Revision Note
A programming paradigm is a style of organising and expressing code.
Swift supports several paradigms because an iOS application contains several different kinds of problems.
The model may respond to user actions, network responses and database updates using event-driven and imperative code.
Protocols may describe service capabilities.
Functional operations may transform values.
Observable state creates a reactive relationship between the model and SwiftUI.
SwiftUI views then use a declarative style to describe what the interface should look like for the current state.
The framework reconciles that description with the interface it is already managing.
Remember
Event-driven code responds to what happened.
Reactive code propagates what changed.
Declarative code describes what should now exist.
These paradigms are not fighting for control of the application.
They are cooperating to build it.
