App Architecture and LifecycleIntroductionAn 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: - Lifecycle management, which determines how the application and its user interfaces respond as execution conditions change.
- 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 ObjectivesBy 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 Lifecycles1.1 Why lifecycle knowledge mattersThe 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 statesAt the application level, iOS applications are commonly described using these states: | State | Meaning | Typical responsibility |
|---|
| Not running | The process is not executing or has not been launched. | Perform initial setup when launched. | | Inactive | The application is in the foreground but is not receiving events normally. | Avoid starting important user interactions; complete brief transition work. | | Active | The application is visible and receiving events. | Perform normal interaction and presentation work. | | Background | The application is no longer normally visible. | Save state, release resources, and perform only permitted work. | | Suspended | The 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 lifecycleA 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 practiceA typical transition may proceed as follows: - The system launches the process.
- The application configures shared services.
- A scene connects and constructs its user interface.
- The scene becomes foreground inactive and then foreground active.
- The user switches applications; the scene moves to the background.
- The application saves important state and releases nonessential resources.
- The scene may be suspended or disconnected.
- 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 Restoration2.1 Types of stateState is any information that affects future behavior or presentation. It is useful to classify it into four categories: | State category | Examples | Appropriate handling |
|---|
| Ephemeral view state | A temporary animation or focus indicator | Usually recreate when needed. | | Interface state | Selected tab, navigation path, scroll position | Preserve when restoration improves continuity. | | User task state | Unsaved form data or draft document | Save reliably and restore deliberately. | | Persistent domain data | Accounts, purchases, records, preferences | Store 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 restorationState 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 choicesCommon 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 ManagementA 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 injectionDependency 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 rootsA 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 ownershipDependencies 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 ModelsSeparation 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: - The view sends an intent, such as “load books” or “select book.”
- The view model calls a service or use case.
- The service returns data or an error.
- The view model transforms the result into presentation state.
- 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| Criterion | MVC | MVVM |
|---|
| Main coordinator | View controller | View model plus view | | Typical strength | Direct fit with UIKit | Testable presentation state and SwiftUI alignment | | Common risk | Massive view controller | Massive view model | | Testing focus | Controller coordination | View-model state and transformations | | Best practice | Keep services and domain logic outside controller | Keep 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 DesignA 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 couplingCohesion 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 ApplicationFor a certificate-level application, a balanced structure might be: Application
├── AppComposition
├── SharedServices
│ ├── NetworkClient
│ ├── PersistenceController
│ └── AuthenticationService
└── Features
├── Catalog
│ ├── CatalogView
│ ├── CatalogViewModel
│ └── CatalogService
└── Profile
├── ProfileView
└── ProfileViewModelThe 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 ApplicationBackgroundConsider 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. ProblemThe 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. AnalysisThe 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 solutionThe 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. OutcomeThe 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- Application-wide services and scene-specific state require different ownership scopes.
- Saving state at meaningful points is safer than depending on termination.
- Dependency injection makes testing and replacement practical.
- MVVM helps organize presentation logic but does not replace domain and service boundaries.
- Restoration must handle missing or invalid data gracefully.
8. Challenges, Limitations, and Best PracticesCommon 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 GovernanceLifecycle 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 ConsiderationsSwift 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 SummaryAn 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 TermsApplication 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 QuestionsShort Answer Questions- What is the difference between an application lifecycle and a scene lifecycle?
- Why should important state not be saved only when an application terminates?
- Define dependency injection and state one benefit of using it.
- What is the principal responsibility of a view model in MVVM?
- Explain the difference between cohesion and coupling.
Long Answer Questions- Describe the principal iOS application and scene states and explain suitable developer responsibilities in each.
- Compare MVC and MVVM, including one strength and one limitation of each.
- Explain how state should be classified, stored, validated, and restored in an iOS application.
- Describe how a composition root and dependency scopes improve maintainability and testing.
- Explain the advantages and possible costs of modular design in a growing iOS project.
Analytical / Application Questions- 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.
- 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.
- 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 AnswersQuestion 1Which 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 2What 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 3Why 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 4Which 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 5Which 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 6In 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 7What 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 8Which 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 9What 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 10Which 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
|