iOS Development with Xcode

Chapter 5
App Architecture and Lifecycle

Certificate in IOS Development
iOS Development with Xcode   •   Certificate

App Architecture and Lifecycle

Introduction

An iOS application is not simply a collection of screens. It is a software system that must respond to user actions, operating-system events, interruptions, memory pressure, background execution limits, and changing device conditions. A well-designed application therefore requires two related forms of structure:

  1. Lifecycle management, which determines how the application and its user interfaces respond as execution conditions change.
  2. Architecture, which determines how responsibilities, data, dependencies, and presentation logic are organized.

Modern iOS applications are commonly built with UIKit or SwiftUI, although the architectural principles in this chapter apply to both. UIKit applications traditionally use view controllers and scene delegates, while SwiftUI emphasizes state-driven views and observable models. In either framework, the central design challenge is the same: preserve correct state, keep responsibilities separate, and make the application understandable, testable, and maintainable.

Learning Objectives

By the end of this chapter, you should be able to:

  • distinguish application lifecycle events from scene lifecycle events;
  • explain the principal foreground, background, inactive, and termination states;
  • preserve and restore appropriate application state;
  • manage dependencies using explicit and testable techniques;
  • apply separation of concerns through MVC and MVVM;
  • identify when modular design is useful;
  • evaluate architectural decisions in a practical iOS scenario.

1. Application and Scene Lifecycles

1.1 Why lifecycle knowledge matters

The operating system controls when an application receives execution time. An app may be launched, moved to the background, suspended, resumed, or terminated. These transitions are not exceptional events; they are normal operating conditions.

Lifecycle code is used to:

  • initialize global services;
  • respond to transitions between foreground and background;
  • save user progress;
  • release resources that are no longer needed;
  • reconnect services when the application becomes active;
  • prepare the interface for interruptions such as phone calls or system dialogs.

A common error is treating lifecycle callbacks as a sequence that is guaranteed to occur in every situation. It is not. For example, an application can be terminated while suspended without receiving a final termination callback. Important state must therefore be saved before suspension or whenever meaningful changes occur.

1.2 Application lifecycle states

At the application level, iOS applications are commonly described using these states:

StateMeaningTypical responsibility
Not runningThe process is not executing or has not been launched.Perform initial setup when launched.
InactiveThe application is in the foreground but is not receiving events normally.Avoid starting important user interactions; complete brief transition work.
ActiveThe application is visible and receiving events.Perform normal interaction and presentation work.
BackgroundThe application is no longer normally visible.Save state, release resources, and perform only permitted work.
SuspendedThe process remains in memory but receives no execution time.Keep essential state restorable; do not assume callbacks will occur.

The inactive state may occur briefly during launch, when an interruption appears, or during a transition to another state. Background execution is limited by the operating system. An application should not assume that it can continue running indefinitely after leaving the foreground.

In UIKit, UIApplicationDelegate provides process-level callbacks such as launching and background transitions. The application delegate remains useful for responsibilities that belong to the application as a whole, such as configuring services or responding to process-level events.

1.3 Scene lifecycle

A scene represents an instance of an application’s user interface and its associated resources. Scene-based lifecycle management supports multiple windows, particularly on iPadOS and macOS through Mac Catalyst. One process may contain more than one scene, and each scene may have an independent user interface state.

The principal scene states are:

  • Unattached: the scene has not yet been connected to the application.
  • Foreground inactive: the scene is visible or about to become visible but is not currently receiving normal interaction.
  • Foreground active: the scene is visible and interactive.
  • Background: the scene is no longer visible, although its session may remain available.
  • Disconnected: the system has released the scene’s resources; the session may later be reconnected.

A scene delegate, represented in UIKit by UISceneDelegate, handles scene-specific events. For example, it may create a window, respond when the scene becomes active, save scene-specific user activity, or restore an interface configuration.

The distinction between application and scene responsibilities is important. A database service shared by all windows belongs at application or dependency-container scope. The selected document, navigation path, or draft text for one window belongs to that scene.

1.4 Lifecycle transitions in practice

A typical transition may proceed as follows:

  1. The system launches the process.
  2. The application configures shared services.
  3. A scene connects and constructs its user interface.
  4. The scene becomes foreground inactive and then foreground active.
  5. The user switches applications; the scene moves to the background.
  6. The application saves important state and releases nonessential resources.
  7. The scene may be suspended or disconnected.
  8. The system may later reconnect the scene or terminate the process.

This sequence is illustrative rather than guaranteed. Developers should design around state changes, not around assumptions that every callback will always execute in a particular order.

2. State Management and Restoration

2.1 Types of state

State is any information that affects future behavior or presentation. It is useful to classify it into four categories:

State categoryExamplesAppropriate handling
Ephemeral view stateA temporary animation or focus indicatorUsually recreate when needed.
Interface stateSelected tab, navigation path, scroll positionPreserve when restoration improves continuity.
User task stateUnsaved form data or draft documentSave reliably and restore deliberately.
Persistent domain dataAccounts, purchases, records, preferencesStore using appropriate durable storage and security controls.

Not all state should be saved. Saving unnecessary transient details can create complexity and privacy risks. The developer should ask whether the information is needed to continue a user task and whether retaining it is appropriate.

2.2 State preservation and restoration

State preservation means recording enough information to describe a user’s current context. Restoration means reconstructing that context after relaunch, scene reconnection, or memory release.

A robust strategy follows these principles:

  • save at meaningful points rather than only at final termination;
  • store a compact representation, such as identifiers and values, rather than entire view objects;
  • treat restored data as untrusted input and validate it;
  • separate durable domain data from temporary interface state;
  • design restoration to tolerate missing, outdated, or partially corrupt data;
  • avoid storing secrets in ordinary preferences or unprotected files.

For example, a shopping application might preserve a cart identifier, selected delivery address identifier, and draft note. It should not attempt to serialize a view controller hierarchy as its business-state model.

UIKit provides state-restoration facilities, while SwiftUI commonly uses property wrappers such as @State, @SceneStorage, and @AppStorage for suitable kinds of state. These mechanisms do not eliminate architectural responsibility: the developer must still choose what to preserve and define how it is interpreted.

2.3 Persistence choices

Common storage mechanisms include:

  • UserDefaults for small, non-sensitive preferences;
  • files for documents or structured local data;
  • Core Data for object graphs, querying, and persistence;
  • SwiftData for a modern persistence model in supported OS versions;
  • Keychain for credentials, tokens, and other sensitive secrets;
  • an external service for synchronized or authoritative server data.

A token stored in UserDefaults, for instance, may be exposed more easily than a token stored in the Keychain. Storage selection is therefore both an engineering and a security decision.

3. Dependencies and Dependency Management

A dependency is an object, service, or resource that another component requires. Examples include a network client, clock, database, analytics service, location manager, or authentication provider.

Poor dependency management often produces hidden global state. A view controller that directly creates a network client, database, date formatter, and analytics manager is difficult to test and difficult to reuse. It also combines presentation with infrastructure.

3.1 Dependency injection

Dependency injection supplies required objects from outside the component that uses them. Three common forms are:

  • Initializer injection: dependencies are supplied when an object is created. This is usually the clearest form because required dependencies cannot be forgotten easily.
  • Property injection: dependencies are assigned after creation. It is useful when frameworks construct an object, but it can leave the object temporarily invalid.
  • Method injection: a dependency is supplied only to a particular operation. This is suitable for occasional or operation-specific services.

A simplified Swift example is:

protocol BookService {
    func fetchBooks() async throws -> [Book]
}

final class LibraryViewModel {
    private let service: BookService

    init(service: BookService) {
        self.service = service
    }
}

LibraryViewModel depends on the abstraction BookService, not on a particular network implementation. A production composition may supply RemoteBookService, while a test can supply FakeBookService.

3.2 Composition roots

A composition root is the place where concrete implementations are assembled. In an iOS application it may be the application delegate, scene delegate, SwiftUI App structure, or a dedicated dependency container.

The composition root should answer questions such as:

  • Which network client is used?
  • Which persistence implementation is active?
  • Where is authentication configured?
  • Which services are shared across scenes?
  • Which services are scoped to one scene or one screen?

Centralizing these decisions prevents construction logic from spreading throughout the user interface.

3.3 Scope and ownership

Dependencies should have an appropriate lifetime:

  • Application scope: services shared by the whole process, such as a logging service or database coordinator.
  • Scene scope: services associated with one window or document session.
  • Feature scope: services used by one functional area.
  • Screen scope: short-lived models and presentation objects.

A singleton can be appropriate for a genuinely process-wide resource, but using singletons merely for convenience creates hidden coupling and complicates testing. “Global” should describe a real ownership requirement, not an easy access pattern.

4. Separation of Concerns and Architectural Models

Separation of concerns means assigning distinct responsibilities to distinct components. A view should present information and report user interaction. A model should represent data and domain rules. A service should perform infrastructure work. Clear boundaries reduce the likelihood that a change in one responsibility will damage another.

4.1 Model–View–Controller (MVC)

In Apple’s UIKit tradition:

  • Model: domain data and rules;
  • View: visual objects and user-interface rendering;
  • Controller: coordinates the model and view, handles events, and manages presentation.

MVC is approachable and supported directly by UIKit. Its weakness is that view controllers can become “massive” when networking, persistence, validation, formatting, navigation, and business rules accumulate in one class.

MVC remains effective when the controller coordinates rather than owns every responsibility. A controller should delegate network work to services, domain decisions to model or use-case objects, and formatting to dedicated presenters or formatters.

4.2 Model–View–ViewModel (MVVM)

MVVM introduces a view model between the view and the domain model. The view model prepares display-ready data, exposes interaction methods, manages loading and error states, and coordinates services.

A typical flow is:

  1. The view sends an intent, such as “load books” or “select book.”
  2. The view model calls a service or use case.
  3. The service returns data or an error.
  4. The view model transforms the result into presentation state.
  5. The view observes the state and updates its appearance.

MVVM can reduce view-controller complexity and is especially compatible with SwiftUI’s observation model. However, a view model can become a “massive view model” if it absorbs domain rules, persistence, navigation, and unrelated features. MVVM is not a substitute for boundaries; it is a way of placing presentation logic more deliberately.

4.3 Comparing MVC and MVVM

CriterionMVCMVVM
Main coordinatorView controllerView model plus view
Typical strengthDirect fit with UIKitTestable presentation state and SwiftUI alignment
Common riskMassive view controllerMassive view model
Testing focusController coordinationView-model state and transformations
Best practiceKeep services and domain logic outside controllerKeep domain rules outside view model when they belong elsewhere

The choice should follow project needs, team familiarity, framework constraints, and expected change. Neither pattern guarantees good architecture automatically.

5. Modular Design

A module is a cohesive unit of code with a defined public interface. In iOS, modules may be represented by Swift packages, frameworks, or clearly separated targets.

A useful module has:

  • a focused purpose;
  • a small public API;
  • limited knowledge of other modules;
  • explicit ownership of its resources;
  • tests that can run independently where practical.

Possible modules include Networking, Authentication, DesignSystem, Persistence, and feature modules such as Catalog or Checkout.

5.1 Cohesion and coupling

Cohesion describes how closely related the responsibilities inside a module are. Coupling describes how strongly modules depend on one another. Good modular design seeks high cohesion and controlled coupling.

A feature module might expose CatalogView and CatalogViewModel while hiding URL construction, decoding details, and database mappings. Other modules depend on the feature’s intended interface rather than its implementation.

Modularity has costs. Excessive fragmentation can increase build configuration work, navigation complexity, and cognitive overhead. A module should be introduced when it provides a meaningful boundary, such as independent ownership, reuse, testing value, or a stable feature boundary.

6. A Practical Architecture for a Small iOS Application

For a certificate-level application, a balanced structure might be:

Application
 ├── AppComposition
 ├── SharedServices
 │    ├── NetworkClient
 │    ├── PersistenceController
 │    └── AuthenticationService
 └── Features
      ├── Catalog
      │    ├── CatalogView
      │    ├── CatalogViewModel
      │    └── CatalogService
      └── Profile
           ├── ProfileView
           └── ProfileViewModel

The application composition layer creates shared services and supplies them to features. A feature view displays state. Its view model coordinates presentation behavior. Services perform network or persistence operations. Domain types represent meaningful application concepts rather than raw response dictionaries.

A good implementation also defines explicit states. For example:

enum LoadState<Value> {
    case idle
    case loading
    case loaded(Value)
    case failed(Error)
}

The view can then render each state deliberately instead of relying on scattered Boolean properties such as isLoading, hasError, and hasData, whose combinations may become inconsistent.

7. Illustrative Case: A Scene-Based Library Application

Background

Consider an illustrative application called Campus Library. Students can search books, open a book record, and save notes. The application supports multiple windows on iPadOS, allowing one scene to display search results while another displays a selected book.

Problem

The first prototype places URL requests in a view controller, stores the selected book in a singleton, and saves notes only when the application terminates. Users lose draft notes when the system suspends or terminates the application. Tests are slow because every screen contacts the live server.

Analysis

The prototype has several architectural problems:

  • a view controller owns presentation, networking, and persistence;
  • a singleton incorrectly represents scene-specific selection;
  • termination is treated as guaranteed;
  • the live server is a hidden dependency;
  • state is not separated into scene state and durable user data.

Approach and solution

The team introduces an MVVM-oriented feature structure. LibraryViewModel receives a BookService through initializer injection. The production composition root supplies a remote service, while tests supply a fake service.

Each scene receives its own navigation and selected-book state. The shared persistence controller stores notes, while a scene-specific model stores the current navigation path and selected record identifier. Draft notes are saved after meaningful edits, not only during termination callbacks.

The scene delegate restores the scene’s configuration when the scene reconnects. It validates the stored book identifier and displays a recovery message if the record is no longer available. The application-level composition root creates the shared database and network services once.

Outcome

The application now supports independent windows without cross-scene selection errors. View-model tests run without network access. Draft notes survive backgrounding and relaunch. The user interface can change from UIKit to SwiftUI without rewriting the service and persistence layers.

Learning points

  1. Application-wide services and scene-specific state require different ownership scopes.
  2. Saving state at meaningful points is safer than depending on termination.
  3. Dependency injection makes testing and replacement practical.
  4. MVVM helps organize presentation logic but does not replace domain and service boundaries.
  5. Restoration must handle missing or invalid data gracefully.

8. Challenges, Limitations, and Best Practices

Common challenges

  • Unexpected termination: suspended applications may be terminated without a final callback.
  • Concurrency: asynchronous tasks may finish after a scene has disappeared. Tasks should be cancelled or their results ignored when no longer relevant.
  • Memory pressure: large images, caches, and in-memory objects may need to be released and recreated.
  • Multiple scenes: shared mutable state can cause one window to affect another unintentionally.
  • Partial failure: network, storage, and authentication operations may fail independently.
  • Framework coupling: placing UIKit types in domain code makes future migration harder.

Best practices

  • Keep lifecycle callbacks short and purposeful.
  • Save important user work incrementally.
  • Treat background execution as limited and uncertain.
  • Prefer immutable value types for data transferred between layers when appropriate.
  • Inject abstractions rather than constructing infrastructure inside views.
  • Keep navigation, domain rules, persistence, and presentation distinguishable.
  • Use structured concurrency and cancel tasks that no longer represent active work.
  • Log lifecycle and restoration failures without recording sensitive user data.
  • Test launch, backgrounding, scene reconnection, restoration, offline use, and memory pressure.
  • Document ownership: identify which object creates, retains, and releases each service.

9. Security, Privacy, and Governance

Lifecycle and architecture decisions affect privacy. A restoration mechanism that stores a medical draft, financial detail, or authentication token may expose sensitive information on a shared device or through backups. Developers should minimize stored data, use Keychain for secrets, apply suitable file-protection settings, and respect user consent and data-retention requirements.

Analytics services should not be initialized merely because the application launches if doing so collects data before consent is obtained. Logs should avoid passwords, access tokens, health information, and unnecessary identifiers. A clean architecture makes these controls easier because data collection and storage are located in explicit services rather than scattered through views.

10. Contemporary Development Considerations

Swift concurrency (async/await, tasks, actors, and cancellation) provides clearer ways to coordinate asynchronous operations, but it does not remove lifecycle concerns. A task belongs to some scope, and its result must still be relevant when it completes. SwiftUI further emphasizes state-driven rendering: a view should derive its appearance from observable state rather than manually issuing many imperative updates.

Modern applications may also use Swift Package Manager to distribute internal modules, SwiftData or Core Data for persistence, and automated tests and continuous integration to verify multiple lifecycle paths. These tools are valuable only when their boundaries remain understandable. Technology should support architecture, not obscure it.

Chapter Summary

An iOS application operates within an operating-system-controlled lifecycle. Application-level events concern the process as a whole, while scene-level events concern individual windows or user-interface sessions. Developers must design for active, inactive, background, suspended, disconnected, and terminated conditions without assuming that a final termination callback will occur.

State should be classified as transient interface state, user task state, or durable domain data. Important state should be saved incrementally, restored through validated representations, and stored using mechanisms appropriate to its sensitivity. Dependencies should be explicit, preferably supplied through injection and assembled at a composition root.

MVC provides a practical UIKit-oriented structure, while MVVM separates presentation state and behavior into a view model. Both patterns require discipline to avoid oversized coordinating objects. Modular design improves ownership, testing, reuse, and change management when modules have cohesive purposes and controlled interfaces. Finally, lifecycle, architecture, security, privacy, and testability are interconnected engineering concerns rather than isolated topics.

Key Terms

Application lifecycle: The states and transitions governing an application process.

Scene: An instance of an application’s user interface and associated session resources.

Scene delegate: An object that responds to lifecycle events for a particular scene.

Active: A state in which the application or scene is visible and receives normal interaction.

Inactive: A temporary state in which the interface is visible but not receiving normal events.

Background: A state in which the application or scene is no longer normally visible.

Suspended: A state in which the process remains in memory but receives no execution time.

State restoration: Reconstructing relevant user or interface context after interruption or relaunch.

Persistence: Storing data so that it survives process termination.

Dependency: A service or object required by another component.

Dependency injection: Supplying a dependency from outside the component that uses it.

Composition root: The location where concrete dependencies are assembled.

Separation of concerns: Dividing responsibilities among components with focused purposes.

MVC: Model–View–Controller, an architecture separating data, presentation, and coordination.

MVVM: Model–View–ViewModel, an architecture using a view model to manage presentation state and behavior.

Modularity: Organizing software into cohesive units with explicit interfaces.

Cohesion: The degree to which responsibilities within a component belong together.

Coupling: The degree of dependency between components.

Scene-specific state: State belonging to one window or user-interface session.

Composition: Construction and configuration of collaborating application objects.

Review Questions

Short Answer Questions

  1. What is the difference between an application lifecycle and a scene lifecycle?
  2. Why should important state not be saved only when an application terminates?
  3. Define dependency injection and state one benefit of using it.
  4. What is the principal responsibility of a view model in MVVM?
  5. Explain the difference between cohesion and coupling.

Long Answer Questions

  1. Describe the principal iOS application and scene states and explain suitable developer responsibilities in each.
  2. Compare MVC and MVVM, including one strength and one limitation of each.
  3. Explain how state should be classified, stored, validated, and restored in an iOS application.
  4. Describe how a composition root and dependency scopes improve maintainability and testing.
  5. Explain the advantages and possible costs of modular design in a growing iOS project.

Analytical / Application Questions

  1. A two-window iPad application displays the same selected document in both windows even though users selected different documents. Identify the likely architectural cause and propose a correction.
  2. A view controller directly creates a network client, decodes JSON, updates labels, saves records, and reports analytics. Redesign its responsibilities using MVC or MVVM and justify your allocation.
  3. An application loses an unsaved form whenever the operating system terminates it in the background. Propose a lifecycle-aware state strategy, including storage choice, save timing, and restoration validation.

Multiple Choice Questions with Answers

Question 1

Which state indicates that an iOS application is visible but not currently receiving normal user events?

A. Active

B. Inactive

C. Suspended

D. Not running

Correct Answer: B. Inactive

Question 2

What is the principal responsibility of a scene delegate?

A. Managing lifecycle events for a particular scene

B. Replacing the operating system scheduler

C. Storing all application passwords

D. Compiling Swift source code

Correct Answer: A. Managing lifecycle events for a particular scene

Question 3

Why is saving important state only in a termination callback unreliable?

A. Termination callbacks are always called too early

B. The system may terminate a suspended application without that callback

C. Termination callbacks can save only images

D. The callback runs only when the device is online

Correct Answer: B. The system may terminate a suspended application without that callback

Question 4

Which storage mechanism is most appropriate for authentication secrets?

A. UserDefaults

B. A view controller property

C. Keychain

D. A temporary label

Correct Answer: C. Keychain

Question 5

Which technique supplies an object’s required service from outside the object?

A. Dependency injection

B. View rendering

C. State suspension

D. Serialization avoidance

Correct Answer: A. Dependency injection

Question 6

In MVVM, which component commonly transforms service results into presentation-ready state?

A. View model

B. Scene session

C. Application icon

D. Operating-system kernel

Correct Answer: A. View model

Question 7

What does high cohesion mean in modular design?

A. A module contains closely related responsibilities

B. Every module depends on every other module

C. A module has no public interface

D. All code is placed in one class

Correct Answer: A. A module contains closely related responsibilities

Question 8

Which responsibility most appropriately belongs to an application-wide composition root?

A. Drawing every button manually

B. Choosing and assembling concrete service implementations

C. Storing a single scene’s scroll position

D. Replacing all domain models

Correct Answer: B. Choosing and assembling concrete service implementations

Question 9

What is a major risk of placing networking, persistence, validation, and navigation in one view controller?

A. The controller becomes difficult to test and maintain

B. The application can never launch

C. The device automatically deletes the app

D. The screen becomes independent of all services

Correct Answer: A. The controller becomes difficult to test and maintain

Question 10

Which statement best describes scene-specific state?

A. Data shared identically by every process on the device

B. State belonging to one window or user-interface session

C. Only information stored in the Keychain

D. Compiler configuration data

Correct Answer: B. State belonging to one window or user-interface session

iOS Development with Xcode
Chapter 5 of 15