top of page

Swift Study Notes

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.

  1. Create a variable.
  2. Loop through the prices.
  3. Add each price to the total.
  4. 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.

  1. The repository emits database events.
  2. The model responds using event-driven and imperative code.
  3. The model exposes observable state.
  4. SwiftUI reacts to changes read by the view.
  5. 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.

bottom of page