Skip to content
All question banks

Mobile Development

Swift Questions

Deep-dive into Swift core fundamentals, Optional safety, and ARC memory management. Essential for junior to senior level iOS roles.

350 of 350 questions

Swift Fundamentals28

Swift is a general-purpose, compiled programming language developed by Apple for its ecosystems: iOS, macOS, watchOS, and tvOS. It is designed to be safe, fast, and modern, offering features like type safety, optionals, and automatic memory management to replace Objective-C as the primary language for Apple platform development.

The fundamental difference is that classes are 'Reference Types' (stored in the heap, shared via pointers) while structs are 'Value Types' (stored in the stack, copied when passed). Consequently, changing a class instance affects all references, whereas modifying a struct only affects that specific copy.

Classes support inheritance (allowing one class to inherit from another), type casting at runtime, deinitializers (code run before an instance is deallocated), and reference counting (multiple references to the same instance). Structs are simpler and do not support these features to ensure data safety and performance.

'let' is used to declare constants whose values cannot be changed after being set, promoting immutability and thread safety. 'var' is used for variables whose values can be modified throughout the application lifecycle. It is best practice to use 'let' by default unless a change is explicitly required.

Value types (Structs, Enums, Tuples) are copied when assigned to a new variable, ensuring each instance has independent state. Reference types (Classes, Functions) share a single instance; assigning them to a new variable creates another pointer to the same memory address, meaning changes are visible globally across all references.

Tuples are compound types that group multiple values into a single value. They are useful for returning multiple related values from a function without defining a custom struct. For example, a function fetching a user could return (name: String, age: Int) as a temporary, lightweight container.

An Array is an ordered collection that allows duplicate elements, ideal for lists where sequence matters. A Set is an unordered collection of unique elements. Sets use hashing, making 'contains' checks significantly faster (O(1)) compared to Arrays (O(n)), which is useful for filtering unique data.

An Array stores values in a linear, index-based sequence starting from zero. A Dictionary stores key-value pairs where each value is associated with a unique, hashable key. Dictionaries are optimized for fast lookups via keys rather than sequential access, similar to a map or hash table.

Float is a 32-bit floating-point number, Double is a 64-bit number (more precise and preferred in Swift). CGFloat is a platform-dependent type used in Core Graphics that adjusts its precision based on the architecture (32-bit or 64-bit), ensuring compatibility across different Apple hardware components.

In Swift, a String is a collection of Characters. This means you can iterate over a string using a 'for-in' loop, use higher-order functions like map() or filter(), and access indices. Unlike C-style strings, they handle complex Unicode grapheme clusters correctly as individual elements.

A UUID (Universally Unique Identifier) is a 128-bit value used to uniquely identify resources without a central authority. In iOS, you use it for database primary keys, identifying unique device installations, or distinguishing items in a collection view to ensure no two items share the same ID.

One-sided ranges (e.g., 'array[2...]' or 'array[...5]') allow you to specify a range that continues as far as possible in one direction. They are commonly used when slicing arrays from a certain index to the end or when checking if a value falls above or below a threshold.

Immutability prevents side effects by ensuring that data cannot be changed unexpectedly. This leads to safer, more predictable code, especially in multi-threaded environments where multiple threads might attempt to read and write the same data simultaneously. It simplifies debugging and improves application stability.

Raw strings in Swift (using #"string"#) allow you to include backslashes and quotes without escaping them. This is particularly useful when working with regular expressions or JSON strings, as it makes the code much cleaner and more readable by avoiding 'escape character soup'.

map() transforms every element in an array and returns a new array of the same length. compactMap() also transforms elements but automatically removes any 'nil' results and unwraps the remaining values, making it the standard choice for transforming collections that might contain invalid or missing data.

A variadic function accepts zero or more values of a specified type. You define it by adding three dots (...) after the parameter's type. Within the function, these values are treated as an array. A classic example is the print() function, which can take any number of arguments.

An Array is an ordered list that allows duplicate values. A Set is an unordered collection of unique values. Sets use hashing, making 'contains' checks extremely fast (O(1)) compared to arrays (O(n)), which is useful when you only care about presence and uniqueness.

One-sided ranges (e.g., `5...` or `...10`) are ranges that extend as far as possible in one direction. They are commonly used when slicing arrays to say 'take everything from index 5 to the end' or 'take everything from the start up to index 10'.

Value types (Structs, Enums) are copied when assigned or passed; changing one doesn't affect the other. Reference types (Classes) share the same instance; multiple variables can point to the same object, and changing it in one place affects all references.

A variadic function accepts zero or more values of a specific type. You define it by adding three dots (...) after the parameter's type. Inside the function, the parameter is accessible as an array of those values. Example: 'print(_ items: Any...)'.

'map()' transforms every element in a collection. 'compactMap()' transforms the elements but also removes any 'nil' values and unwraps the resulting optionals, making it highly useful for filtering out invalid data during transformations.

Raw strings (using #"string"#) allow you to include backslashes and quotes without escaping them. This is extremely useful for regular expressions or JSON strings where backslashes are common, making the code much more readable.

A UUID (Universally Unique Identifier) is a 128-bit value used to uniquely identify information. In iOS, you use it for identifying database records, unique device identifiers, or any scenario where you need a collision-resistant unique ID.

A Float is a 32-bit floating-point number, while a Double is a 64-bit number. Double has much higher precision (about 15 decimal digits compared to Float's 6). Double is the default floating-point type in Swift.

One-sided ranges (like '5...' or '...10') are ranges where only one boundary is specified. They are used for slicing arrays from a specific index to the end or from the start to a specific index.

Classes are reference types (stored on the heap, shared via pointers) and support inheritance. Structs are value types (stored on the stack, copied when passed) and do not support inheritance. Structs are generally safer and faster.

Classes are reference types (stored on the heap) and support inheritance. Structs are value types (stored on the stack) and do not support inheritance. Structs are safer and faster for most data models.

`map` transforms every item. `compactMap` transforms every item and removes `nil` results, making it ideal for filtering out invalid data while converting.

Optionals & Error Handling16

Optionals represent a type that can either hold a value or 'nil'. They are a safety feature that forces developers to explicitly handle the absence of data. By requiring unwrapping before access, Swift prevents runtime 'null pointer' crashes that are common in many other programming languages.

String? is a standard optional that must be safely unwrapped before use. String! is an 'Implicitly Unwrapped Optional' that is technically an optional but can be used like a non-optional. It will crash if accessed while nil; it is typically used for IBOutlets where the value is guaranteed after initialization.

Safe unwrapping is achieved through Optional Binding ('if let' or 'guard let'), Optional Chaining ('?.'), or using the Nil Coalescing operator ('??') to provide a default value. These methods ensure the code only executes if a value exists, preventing crashes associated with forced unwrapping ('!').

'if let' unwraps a value and makes it available only within the local scope of the 'if' block. 'guard let' unwraps the value and keeps it available for the entire remaining scope of the function. 'guard' also requires an early exit (return/throw), making the 'happy path' code much cleaner.

Optional chaining allows you to call properties, methods, and subscripts on an optional that might currently be nil. If the optional is nil, the entire chain fails gracefully and returns nil rather than triggering a runtime crash, significantly simplifying code that deals with nested optional objects.

Use 'guard' when you have a requirement that must be met for a function to continue execution. It is perfect for validating inputs, checking for optional values, and handling 'early exits'. It improves readability by keeping the main logic at the top-most level rather than deeply nested in 'if' statements.

The nil coalescing operator (??) unwraps an optional if it contains a value, or returns a default 'fallback' value if the optional is nil. It is a concise shorthand for ternary operations and is the cleanest way to provide defaults for optional strings, numbers, or UI elements.

'try' is used with 'do-catch' for full error handling. 'try?' converts the result into an optional, returning 'nil' if an error occurs. 'try!' is a forced try that crashes the app if an error is thrown. Use 'try?' for optional results and avoid 'try!' in production code.

Errors are handled using the 'throws' keyword in function signatures to propagate errors. At the call site, you use 'do-catch' blocks combined with 'try'. The 'catch' block allows you to pattern match against specific error types and respond accordingly, such as showing a user alert.

The Result type is used primarily for asynchronous operations where a task can either succeed with a value or fail with an error. It captures both states in an enum (success/failure), allowing you to handle the outcome safely at a later time, which is much cleaner than passing two optional parameters.

Optional chaining (`?.`) allows you to call properties or methods on an optional object without explicitly unwrapping it. If the object is nil, the entire chain fails gracefully and returns nil instead of crashing the app, making code much more concise and safe.

`try` is used in a do-catch block for full error handling. `try?` converts the result to an optional; it returns nil if an error is thrown. `try!` is a forced try that crashes the app if an error is thrown—it should be avoided unless success is 100% certain.

Optional binding is the process of checking if an optional contains a value and, if so, making that value available as a temporary constant (using `if let` or `guard let`). It is the safest and most common way to unwrap optionals in Swift.

'try' is used with 'do-catch' for full error handling. 'try?' converts the result into an optional, returning 'nil' if an error occurs. 'try!' is a forced unwrap that crashes the app if an error is thrown; it should only be used if success is guaranteed.

The 'Result' type is an enum that represents either a success or a failure. It is the standard way to return values from asynchronous operations, as it captures both the returned data and the potential error in a single object for safer handling.

`try` is used for full error handling. `try?` returns nil on error. `try!` crashes on error. You should use `try?` for optional results and `try` for critical logic that must handle failure states.

Memory Management22

ARC is a memory management feature that automatically tracks and manages an app's memory usage. It tracks the number of 'strong references' to each class instance. When the reference count drops to zero, ARC deallocates the instance to free up memory, ensuring efficient resource utilization without manual intervention.

Think of ARC as a counter. Every time you point a variable to a class object, the counter goes up by 1. When that variable goes out of scope or is set to nil, the counter goes down by 1. If the counter hits zero, the object is 'deleted' to save memory. It's like a library book: if no one is reading it, it goes back to the shelf.

Swift uses ARC for reference types (classes) and simple stack allocation for value types (structs). For reference types, it requires developers to manage 'reference cycles' using 'weak' or 'unowned' keywords. This ensures that two objects don't keep each other alive indefinitely, which would cause a memory leak.

Strong references increase the retain count and prevent deallocation. Weak references do not increase count and become 'nil' when the object is deallocated (must be optional). Unowned references also don't increase count but are non-optional; they assume the object will always exist as long as the reference does.

A 'weak' reference is always an optional because it can become 'nil' at runtime once the object is destroyed. An 'unowned' reference is non-optional and expects the object to outlive the reference. Accessing an unowned reference after the object is deallocated will cause a runtime crash, unlike weak which returns nil.

Identify cycles using Xcode's 'Memory Graph Debugger' to visualize object relationships. Resolve them by changing one of the strong references in the cycle to 'weak' or 'unowned'. This is most common in delegate patterns (where the delegate should be weak) and in closure capture lists ([weak self]).

Handling leaks involves a three-step process: 1. Regular profiling with the 'Instruments' tool (Leaks template); 2. Using the Memory Graph Debugger for quick checks; and 3. Auditing code for common 'strong' capture cycles in closures and delegates, then converting them to weak references to break the cycle.

I first run the app using the 'Leaks' instrument to find leaked blocks. Then, I use the 'Cycles' view in the Memory Graph Debugger to find which objects are pinning each other. Finally, I apply '[weak self]' in closures or mark delegate variables as 'weak' to allow ARC to deallocate the objects properly.

'deinit' is a special method called immediately before a class instance is deallocated. It is used to perform cleanup, such as closing file handles, removing observers from NotificationCenter, or invalidating timers. It ensures that any external resources held by the object are released properly.

Both break retain cycles by not increasing the retain count. Weak is used when the referenced object can become nil (it's always an optional). Unowned is used when the referenced object is guaranteed to exist as long as the reference does. Accessing a deallocated unowned reference results in a crash, while weak returns nil.

`deinit` is a deinitializer called immediately before a class instance is deallocated. It is the final opportunity to perform cleanup, such as invalidating timers, removing `NotificationCenter` observers, or closing open file handles to ensure the app doesn't leak external resources.

ARC (Automatic Reference Counting) is the system that manages your app's memory. Every time you create a strong reference to a class, its 'count' goes up. When you set it to nil or it goes out of scope, the count goes down. When the count hits zero, the memory is instantly freed.

`deinit` is called just before a class instance is deallocated. It is used to perform final cleanup, such as removing observers, invalidating timers, or closing open files to ensure no resources are leaked.

ARC is Apple's memory management system. It automatically tracks the number of strong references to a class instance. When the count hits zero, it deallocates the instance. It doesn't work for structs or enums as they are value types.

Both break retain cycles. `weak` is always an optional and becomes `nil` when the object is deallocated. `unowned` is non-optional and assumes the object will always exist; if it is accessed after deallocation, the app will crash.

'deinit' is a deinitializer called immediately before a class instance is deallocated. It is used to perform final cleanup, such as removing observers, invalidating timers, or closing files.

'weak' references are always optional and become nil when the object is deallocated. 'unowned' references are non-optional and assume the object will always exist; accessing an unowned reference after deallocation will cause a crash.

You identify retain cycles using the 'Memory Graph Debugger' in Xcode. It visualizes which objects are holding references to each other. A cycle appears when two or more objects have strong references to each other, preventing ARC from freeing them.

Use the Memory Graph Debugger to look for retain cycles or Instruments (Leaks) to find objects that remain in memory but have no strong references. Sudden spikes in memory usage are also a key indicator.

Weak is optional and becomes nil on deallocation. Unowned is non-optional and crashes if accessed after deallocation. Use unowned only when the reference is guaranteed to exist.

A deinitializer called right before a class instance is destroyed. It is used to perform final cleanup like invalidating timers or closing database connections.

Automatic Reference Counting. It tracks the number of strong references to class instances and deallocates them when the count reaches zero to manage memory automatically.

Closures & Functions12

Closures are self-contained blocks of functionality that can be passed around and used in your code. They are similar to blocks in C or lambdas in other languages. Closures can 'capture' and store references to variables and constants from the context in which they are defined, which is called 'closing over' variables.

A non-escaping closure is executed within the function it's passed to and cannot outlive it (default). An 'escaping' closure (@escaping) can be stored in a variable or executed after the function returns, such as in an asynchronous network callback. Escaping closures require explicit 'self' references and can cause retain cycles.

@escaping tells the compiler that the closure will be called after the function finishes execution. It's used for asynchronous tasks. For example, in a network request, the closure is 'escaping' because it waits for the server response while the main function has already returned.

@autoclosure is a decorator that automatically wraps an argument passed to a function into a closure. It's used to delay execution of a potentially expensive operation until it's actually needed. A common example is the 'assert' function, where the message is only evaluated if the condition fails.

A closure is a self-contained block of code that can be passed around and used. They are similar to 'functions without names' or blocks in other languages. Closures can 'capture' variables from the context they are created in, making them very powerful for asynchronous callbacks.

A non-escaping closure is executed before the function it's passed to returns. An escaping closure (`@escaping`) outlives the function—for example, it's stored to be called later when a network request finishes. Escaping closures require careful management to avoid retain cycles.

A closure is a self-contained block of functionality that can be passed around and used in your code. They can capture and store references to variables and constants from the context in which they are defined.

A non-escaping closure (default) is executed within the function it is passed to. An escaping closure (`@escaping`) outlives the function, often being called later—for example, in a network completion handler.

An escaping closure (@escaping) is a closure that is passed to a function but is executed after the function returns. This is common in asynchronous tasks like network requests where the completion handler is called once the data is fetched later.

A capture list [weak self] is used inside a closure to prevent retain cycles. It specifies how variables from the surrounding scope should be captured—usually as 'weak' or 'unowned'—to ensure the closure doesn't keep an object alive unnecessarily.

An `@escaping` closure is one that is called after the function it was passed to returns. This is standard for asynchronous work like network requests that take time to complete.

A list like `[weak self]` used to specify how variables should be captured by the closure to prevent retain cycles between the closure and the object that owns it.

Object-Oriented & Protocol-Oriented Programming14

POP is a design paradigm that favors protocols over class inheritance. By using protocol extensions, you can provide default implementations to multiple types (structs, enums, and classes) simultaneously. This leads to more modular, reusable, and testable code while avoiding the rigid hierarchies of traditional OOP.

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task. It doesn't provide implementation itself (unless extended). Types that 'conform' to the protocol must implement those requirements, allowing for polymorphism where different types can be treated as the same protocol type.

Types that conform to CaseIterable (usually enums) automatically generate a 'allCases' property. This collection contains all the cases of the enum in the order they were defined, making it easy to count cases, iterate over them for a picker, or perform bulk operations on enum values.

Codable is a type alias for the Encodable and Decodable protocols. It enables automated serialization and deserialization of data types to and from external formats like JSON or Property Lists. It significantly reduces boilerplate code required for data persistence and network communication logic.

Key decoding strategies (like .convertFromSnakeCase) allow you to map JSON keys (e.g., 'user_id') to Swift-style camelCase properties ('userId') automatically. This ensures your Swift code follows language conventions while still being compatible with external APIs that use different naming standards.

An extension adds new functionality to an existing class, struct, enum, or protocol type. This includes adding computed properties, new methods, initializers, or even making an existing type conform to a new protocol. Extensions allow you to organize code and add features to types you don't own (like UIKit).

A standard extension adds functionality to a specific type. A protocol extension adds functionality to *any* type that conforms to that protocol. Protocol extensions are powerful because they allow you to provide 'default implementations', reducing code duplication across many different unrelated types.

Conditional conformance allows a generic type to conform to a protocol only when its generic arguments also conform to that protocol. For example, an `Array` only conforms to `Equatable` if the items inside the array are also `Equatable`. This makes the type system more flexible and expressive.

It automatically generates an `allCases` property for an Enum. This allows you to easily count how many cases an Enum has or iterate over all of its values in a loop, which is perfect for populating Picker views or menus.

`Codable` is a combination of `Encodable` and `Decodable`. It allows you to convert Swift objects to and from external formats like JSON with very little code. It's the standard way to handle data from web APIs or save data to disk.

Think of a protocol as a 'to-do list' or a contract. It defines a list of methods or properties that a class or struct *must* have if it wants to conform. It doesn't do the work itself; it just ensures that any object following the protocol behaves in a certain way.

Codable makes it easy to convert data types to and from external formats like JSON or Property Lists. It is a combination of the `Encodable` and `Decodable` protocols.

Extensions add new functionality to an existing class, struct, enum, or protocol. They can add methods, computed properties, and protocol conformances, even to types you don't have the source code for (like `String` or `UIView`).

CodingKeys allow you to map JSON keys that don't match your Swift naming conventions (e.g., `user_id` to `userId`). This ensures your Swift code stays clean and follows camelCase standards.

Advanced Swift Concepts41

Generics allow you to write flexible, reusable functions and types that can work with any type, subject to requirements you define. They avoid code duplication and provide type safety. For example, Swift's Array is a generic collection that can store Ints, Strings, or custom objects safely.

Property observers respond to changes in a property's value. 'willSet' is called just before the value is stored (with 'newValue'), and 'didSet' is called immediately after the new value is stored (with 'oldValue'). They are commonly used to update UI elements when a model property changes.

The 'final' keyword prevents a class from being subclassed or a method from being overridden. Using 'final' can provide a performance boost because the compiler can use 'static dispatch' instead of 'dynamic dispatch', and it clearly signals that a class is intended to be used as-is.

'defer' defines a block of code that is guaranteed to execute just before the current scope (like a function or loop) exits, regardless of how it exits (return, throw, etc.). It is ideal for cleanup tasks like closing database connections or freeing manually allocated memory.

The `@objc` attribute makes Swift code accessible to the Objective-C runtime. This is necessary when using older APIs that rely on 'Selectors' (like `Timer` or `NotificationCenter`), when overriding methods from an Objective-C class, or when implementing protocols that need to be visible to Objective-C code.

Opaque return types (using the `some` keyword) allow a function to return a value without exposing its concrete type to the caller. This is central to SwiftUI, where a view's body returns `some View`. It preserves type identity for the compiler while allowing the developer to work with an abstract interface.

Lowercase `self` refers to the current instance of a class or struct (like 'this' in other languages). Uppercase `Self` (capital S) refers to the type itself. It is most commonly used in protocols to refer to the type that will eventually conform to the protocol.

The `@main` attribute marks the entry point for an application. It tells the compiler which structure or class contains the static `main()` method required to launch the app. In modern Swift apps, this is usually applied to the `App` struct (SwiftUI) or the `AppDelegate` (UIKit).

Result builders (like `@ViewBuilder`) are a Swift feature that allows you to collect multiple partial results and combine them into a single return value using a DSL-like syntax. This is what allows SwiftUI to list multiple views inside a stack without using commas or explicit return statements.

`canImport()` is a compilation-time check used to determine if a specific framework is available to be imported. This is essential for writing cross-platform code (e.g., only importing `UIKit` if the target is iOS, or `AppKit` if the target is macOS).

A phantom type is a generic type parameter that appears in the declaration of a type but is not used in any of its properties or methods. They are used to add extra information at compile-time to enforce state safety. For example, you can use them to distinguish between an 'Authenticated' and 'Guest' request object to prevent unauthorized API calls before they even run.

Custom property wrappers allow you to extract common logic from properties. A common use case is a `@Trimmed` wrapper that automatically removes whitespace from a String, or a `@UserDefault` wrapper that abstracts the boilerplate of reading and writing to `UserDefaults`, making the call site much cleaner and declarative.

Introduced in Swift 5.3, multi-pattern catch clauses allow a single `catch` block to handle multiple error cases separated by commas. This reduces code duplication when different errors require the same recovery logic, such as showing the same 'Connection Error' alert for both timeout and network loss errors.

This is a compiler directive used to write code that only runs on specific environments, most commonly `targetEnvironment(simulator)`. It allows you to mock certain hardware features (like Camera or FaceID) that are unavailable in the Xcode simulator, ensuring the app doesn't crash during testing.

Key paths are a way to refer to a property of a type without actually accessing its value. Think of them as 'typed pointers' to a specific property (e.g., `User.name`). They are highly useful for sorting arrays, creating generic data bindings, or observing changes in a specific property of an object.

`defer` defines a block of code that is guaranteed to run just before the current scope (like a function) exits, regardless of how it exits (return or error). It is perfect for cleanup tasks like closing a file or dismissing a loading spinner so you don't forget to call them in every return path.

It exposes a Swift method or property to the Objective-C runtime. This is mandatory when using older APIs like `Timer` or `NotificationCenter` selectors, or when you need to interface with Objective-C code in a mixed-language project.

Generics allow you to write functions and types that work with any type while maintaining type safety. This avoids code duplication. For example, Swift's `Array` is generic; you can have an `Array<Int>` or `Array<String>` using the same underlying logic for both.

The `@main` attribute marks the starting point of your application's execution. It tells the compiler which struct or class contains the 'main' function that launches the app. In modern SwiftUI, it is usually applied to the `App` struct.

The `final` keyword prevents a class from being subclassed or a method from being overridden. It improves performance because the compiler can use 'static dispatch' instead of 'dynamic dispatch', and it clearly communicates that a class is intended to be used as-is.

Lower-case `self` refers to the current instance of an object (like 'this' in other languages). Upper-case `Self` refers to the type itself. You'll often see `Self` in protocols when a method needs to return the specific type that conforms to the protocol.

Using the `some` keyword, opaque return types allow a function to return a specific type without exposing what that type is to the caller. This is vital in SwiftUI, where a view's `body` returns `some View`, allowing the complex underlying view hierarchy to remain internal.

Opaque return types (using the 'some' keyword) allow a function or property to return a specific type that conforms to a protocol without exposing the exact concrete type to the caller. This is crucial in SwiftUI, where 'body' returns 'some View'. It preserves type identity for the compiler while allowing the developer to work with an abstract interface.

The 'guard' statement is used for 'early exits' in functions. It checks a condition; if it fails, the 'else' block executes and must exit the scope (return, break, or throw). This avoids deep nesting of 'if' statements and ensures that required data (like an unwrapped optional) is available for the rest of the function.

Result builders (like @ViewBuilder) are a Swift feature that allows you to collect multiple partial results and combine them into a single return value. This is the technology that powers the DSL-like syntax of SwiftUI, allowing you to list multiple views in a stack without commas.

Lowercase 'self' refers to the specific instance of a class or struct. Uppercase 'Self' refers to the type itself. 'Self' is commonly used in protocols to indicate that a method should return the type that conforms to the protocol.

Property observers observe and respond to changes in a property's value. 'willSet' is called just before the value is stored (with 'newValue'), and 'didSet' is called immediately after the value is stored (with 'oldValue'). They are not called during initial initialization.

The @objc attribute makes a Swift method or property available to the Objective-C runtime. This is necessary when using older APIs that rely on selectors (like NotificationCenter or Timers) or when you need to interface with Objective-C code in a mixed project.

It is used for conditional checks to ensure code only runs on specific OS versions. This allows you to use new APIs while maintaining backward compatibility for older iOS versions. Example: 'if #available(iOS 17, *) { ... }'.

A phantom type is a generic type parameter that appears in the declaration of a type but is not used in any of its properties or methods. It is used to enforce compile-time constraints and state management, such as distinguishing between 'validated' and 'unvalidated' data types.

The 'final' keyword prevents a class from being subclassed or a method from being overridden. It also provides a performance benefit because the compiler can use 'static dispatch' instead of 'dynamic dispatch' for method calls.

The 'defer' block contains code that is guaranteed to execute just before the current scope (like a function) exits, regardless of how it exits (return or error). It is perfect for cleanup tasks like closing file handles or dismissing a loading state.

Generic constraints allow you to restrict the types that can be used with a generic function or type. For example, you can require that a generic type must conform to the 'Equatable' or 'Codable' protocol to use specific functionality inside the generic block.

The '@main' attribute identifies the entry point for the application. It tells the compiler which struct or class to launch first. In SwiftUI, it is usually applied to the 'App' struct.

Raw values are pre-defined, constant values (like Int or String) assigned to enum cases. Associated values allow you to store different types of data with each instance of a case, providing much more flexibility for state management (e.g., 'Success(data)' vs 'Error(code)').

It is used for OS-version checking. It ensures that specific code only runs on devices that support a certain iOS version, preventing crashes when using new APIs on older devices.

They observe changes to a property's value. `willSet` is called just before storage, and `didSet` immediately after. They are perfect for updating the UI or synchronizing state whenever a value is updated.

`defer` ensures a block of code runs just before the current scope exits. It is essential for cleanup tasks like closing file handles or dismissing spinners, guaranteeing they run regardless of errors or early returns.

It is a Swift attribute that designates the entry point of an application, replacing the old `main.swift` file for launching UIKit or SwiftUI apps.

An attribute that makes Swift code visible to the Objective-C runtime, required for selectors, older APIs, and mixed-language projects.

Generics allow you to write reusable, type-safe code that works with any type. This reduces code duplication across different data models.

iOS Basics17

An iOS app can be in one of five states: 1. Not Running (app has not started or was killed), 2. Inactive (running in foreground but not receiving events, e.g., a phone call interruption), 3. Active (running in foreground and receiving events), 4. Background (not visible but executing code), and 5. Suspended (in memory but not executing code).

The AppDelegate is the root object of a UIKit app. It handles app-wide lifecycle events such as finishing launching, entering the background, or terminating. Since iOS 13, its role has been split; it now handles process-level events and session configurations, while the SceneDelegate handles UI-specific lifecycle events like window activation.

The Responder Chain is a hierarchy of objects (UIResponder subclasses like UIView, UIViewController, and UIApplication) that can handle events. If a view cannot handle a specific event (like a touch), it passes the event to its 'next' responder (usually its superview or view controller) until someone handles it or it reaches the app delegate.

Info.plist (Information Property List) is a key-value store containing essential configuration data for the app. It defines things like the app's display name, bundle identifier, supported orientations, and mandatory privacy descriptions for accessing the camera, location, or photo library.

iOS provides several storage options: 1. UserDefaults (small settings/preferences), 2. Keychain (sensitive data like passwords/tokens), 3. FileManager (direct file storage), 4. Core Data / SwiftData (complex relational databases), and 5. SQLite / Realm (third-party database alternatives).

Swift has transitioned from a language with unstable ABI to a highly mature one. Major changes include ABI Stability (Swift 5), the introduction of SwiftUI and Combine, Structured Concurrency (async/await in Swift 5.5), and the recent shift toward Data Isolation and Actors for safer multi-threading.

Crashes are handled using a combination of Xcode's Crash Organizer (for local development) and tools like Firebase Crashlytics for production. These tools provide a 'stack trace' that shows exactly which line of code caused the crash and what the app state was at that moment.

The `Info.plist` is a configuration file that contains metadata about the app. It includes the app's name, version, and critically, the permissions the app needs (like access to the camera or location) and why it needs them.

It is a configuration file (Property List) containing key app settings like the bundle name, version, and the list of permissions the app needs (like Camera or Photo Library access).

It is the main entry point for app-level events. It handles app launching, termination, and process-level events. Since iOS 13, UI-specific lifecycle events are often handled by the `SceneDelegate`.

It is a hierarchy of objects that can handle events. If a view cannot handle a touch event, it passes it to its superview, then to the view controller, and eventually to the app delegate until it is handled.

The Responder Chain is a hierarchy of objects (UIResponder subclasses) that can handle events. If a view cannot handle a touch, it passes it to its superview, then its view controller, and eventually the app delegate until it is handled.

The AppDelegate is the main entry point for app-level events. It handles app launching, termination, and background tasks. Since iOS 13, UI-specific lifecycle events are often handled by the SceneDelegate.

The Info.plist is a configuration file containing metadata about the app, such as its version number, bundle identifier, and the permissions it requires (like Camera or Location access).

It is a configuration property list for the app. It defines the app's bundle ID, version, and the Privacy Strings required to access sensitive user data like the camera or location.

A hierarchy of objects that can handle events. If a view doesn't handle a touch, it passes it up to its superview, then its view controller, and finally the app delegate.

A configuration file for the app that stores metadata like version numbers, bundle ID, and user permission descriptions.

View Controllers & Lifecycle11

The lifecycle methods are called in a specific sequence: 1. init(), 2. loadView() (creates the view), 3. viewDidLoad() (one-time setup), 4. viewWillAppear() (called before view becomes visible), 5. viewDidAppear(), 6. viewWillDisappear(), 7. viewDidDisappear(), and 8. deinit().

viewDidLoad() is called only once when the view is loaded into memory, making it ideal for static setup (e.g., UI styling). viewWillAppear() is called every single time the view is about to appear on screen, making it the correct place to refresh data or start animations that should trigger every time a user returns to the screen.

This method is a warning from the system that the app is consuming too much memory. You should use it to release non-essential resources, such as cached images or large data arrays that can be easily recreated. Ignoring this warning often leads to the system terminating your app forcefully.

A UIView is a rectangular area on the screen that handles drawing and touch events (the 'View'). A UIViewController manages the view's lifecycle, responds to user interactions, and acts as the 'Controller' that coordinates between the view and the data model.

Orientation changes are handled by the system through Size Classes. You can override `viewWillTransition(to:with:)` in a View Controller to perform custom layout adjustments. In Auto Layout, you can use 'Vary for Traits' in Storyboards or use conditional constraints in code based on horizontal/vertical size classes.

The initializer is called when the struct is being created by its parent, which can happen many times even if the view isn't displayed yet. onAppear() is called only when the view is physically added to the rendered UI. You should perform side effects like network calls in `onAppear()` or `.task()`, not the initializer.

`viewDidLoad` is called once when the view is first loaded into memory (ideal for setup). `viewWillAppear` is called every time the view is about to appear on screen (ideal for refreshing data or starting animations).

iOS uses 'Size Classes'. You can also override `viewWillTransition(to:with:)` in UIKit or use `GeometryReader` and `@Environment(\ .verticalSizeClass)` in SwiftUI to adjust layouts when the device rotates.

`viewDidLoad` is called once when the view is first loaded (good for setup). `viewWillAppear` is called every time the view is about to appear on screen (good for refreshing data).

`viewDidLoad` happens once when the view controller is initialized in memory. `viewDidAppear` happens every time the view is visible on screen. Use `viewDidLoad` for setup and `viewDidAppear` to start animations or analytics tracking.

By using Size Classes in Auto Layout or `viewWillTransition` in UIKit. In SwiftUI, the layout adapts automatically, but you can use `@Environment` to react to specific size class changes.

Design Patterns & Architecture14

In MVC, the Model manages the data and business logic, the View displays the UI, and the Controller acts as the intermediary. The Controller updates the model based on user input from the View and refreshes the View whenever the Model's data changes.

MVVM introduces a ViewModel to sit between the View and Model. The ViewModel transforms Model data into values that the View can easily display and handles UI logic. This removes 'Massive View Controller' issues by making the View Controller's only job to bind the UI to the ViewModel's properties.

The Coordinator pattern removes navigation logic from View Controllers and places it in a dedicated 'Coordinator' class. This makes View Controllers more reusable (since they don't need to know who the next screen is) and provides a centralized place to handle complex flow logic and dependency injection.

A Singleton ensures that a class has only one instance and provides a global point of access to it. It is commonly used for shared resources like `UserDefaults.standard`, `UIApplication.shared`, or custom network managers. However, it should be used sparingly as it can make unit testing and dependency tracking difficult.

Dependency Injection (DI) is the practice of 'injecting' required objects into a class rather than the class creating them internally. This makes code more modular, easier to test (via mocking), and more flexible, as you can easily swap implementations (e.g., swapping a real API service for a mock one in tests).

Delegation is a pattern where one object (the delegator) passes off responsibility for a task to another object (the delegate). It is used heavily in UIKit (e.g., `UITableViewDelegate`). The delegator defines a protocol, and the delegate implements it to respond to specific events or provide data.

Delegation is a communication pattern where one object acts on behalf of another. The delegator (e.g., a TableView) notifies its delegate (the Controller) about events (like row selection). It decouples the UI component from the business logic, allowing the component to be reusable in different contexts.

KVO is an Objective-C mechanism that allows an object to be notified when a property of another object changes. It is the foundation for many older Cocoa APIs. In modern Swift, it's largely replaced by Combine or SwiftUI State, but it's still needed for observing certain UIKit properties like `contentOffset` in a ScrollView.

A Singleton is a design pattern that limits a class to exactly one instance. It's used for shared resources that the whole app needs to access, like a `NetworkManager` or `DatabaseHandler`. While useful, overusing them can make testing difficult.

Delegation is a pattern where one object (the delegator) passes control or data to another object (the delegate). For example, a `UITableView` asks its delegate how to handle a row tap, allowing the UI component to remain reusable.

It is a design pattern that removes navigation logic from View Controllers and places it into dedicated Coordinator classes. This makes View Controllers more reusable and easier to test.

In iOS, dependency injection containers (like Swinject) or custom systems help manage object lifecycles. They map protocols to concrete implementations, allowing you to 'resolve' dependencies at runtime. This facilitates modularity and makes unit testing easier by allowing you to swap real services for mocks.

An InjectionToken is a unique identifier used in dependency injection to resolve dependencies that are not class types (like strings or configurations). It ensures that the DI container can distinguish between different dependencies of the same type.

MVC couples the View and Controller tightly. MVVM uses a ViewModel to decouple business logic from the UI, making code more testable and easier to manage in large SwiftUI or UIKit projects.

UIKit Components21

The frame of a view is its position and size in its *superview's* coordinate system, used for placing the view. The bounds is its position and size in its *own* coordinate system (usually origin 0,0), used for drawing inside the view or placing its subviews.

UITableView is a specialized view for displaying a vertically scrolling list of data. It uses a DataSource to provide the data/cells and a Delegate to handle user interactions and row styling. It is highly optimized through 'cell reuse' to handle thousands of items with minimal memory.

Reuse identifiers are used to improve performance via cell recycling. Instead of creating a new cell for every row, the table view maintains a pool of off-screen cells. When a new row comes into view, it 'dequeues' an existing cell from the pool using the identifier, updating only its content.

Aspect Fit scales the image to fit entirely within the view while maintaining its ratio (leaving empty space). Aspect Fill scales the image to fill the entire view while maintaining ratio, which usually results in parts of the image being cropped.

Intrinsic content size is the natural size a view wants to be based on its content (e.g., a label's size based on its text). Auto Layout uses this as a default constraint, allowing developers to omit explicit width or height constraints for components like buttons and labels.

The frame defines the origin (top-left) and size of a view in its superview's coordinates. The center property defines the middle point of that view in the superview's coordinates. Changing the center is often used for animations or snapping views to a specific point without affecting their size.

The Frame is the view's location/size in the coordinate system of its superview. The Bounds is the view's location/size in its own coordinate system. If you rotate a view, its frame changes to encompass the new bounding box, but its internal bounds remain the same.

It is a standard spinner used to show that a task is in progress. Best practice is to set `hidesWhenStopped = true` so it automatically disappears from the UI when the `stopAnimating()` method is called, ensuring the user is not confused by a static spinner.

`UIView` is a high-level object that handles layouts and touch events. Every `UIView` is backed by a `CALayer`, which is a lower-level object that handles the actual rendering and animations via the GPU. You use `CALayer` for advanced effects like shadows, borders, or custom masking.

Shadows are added using the view's layer properties: `layer.shadowColor`, `layer.shadowOpacity`, `layer.shadowOffset`, and `layer.shadowRadius`. For performance, you should always set the `layer.shadowPath` so the system doesn't have to calculate the shadow's shape on every frame.

Reuse identifiers allow the `UITableView` to recycle off-screen cells instead of creating thousands of new objects. When a row scrolls out of view, its cell is put in a 'reuse pool' and retrieved for a new row, significantly reducing memory usage and ensuring smooth scrolling.

Frame is the view's location and size in its superview's coordinate system (useful for positioning). Bounds is the view's location and size in its own coordinate system (useful for internal drawing).

They allow the system to recycle off-screen cells. Instead of creating 100 cells for 100 rows, the system creates ~10 and reuses them as the user scrolls, which saves memory and ensures smooth scrolling.

Aspect fit scales an image to fit inside the view without cropping (may leave empty space). Aspect fill scales an image to fill the entire view (may crop parts of the image).

It is used to display alerts or action sheets to the user. You can add actions (buttons) and text fields to gather user input or provide warnings.

UIView is a high-level object that handles layouts, touch events, and accessibility. Every UIView is backed by a CALayer, which is a lower-level object that manages the actual visual content and animations. Layers do not handle user interaction; they only handle the 'display' side of the UI.

Frame is the view's location/size in its superview's coordinates. Bounds is the view's location/size in its own coordinates. If you rotate a view, its frame changes to fit the new orientation, but its bounds remain the same.

Intrinsic content size is the natural size a view wants to be based on its content (like a label's text or a button's title). Auto Layout uses this to set default constraints if the developer hasn't provided explicit dimensions.

Aspect fit scales an image to fit inside the view without cropping (may leave empty space). Aspect fill scales an image to fill the entire view, which usually results in the image being cropped.

Reuse identifiers allow the TableView to recycle cells that have scrolled off-screen. This significantly reduces memory usage and improves performance by avoiding the constant creation and destruction of cell objects.

Frame is the position in the superview's coordinate system. Bounds is the position in the view's own coordinate system. Rotating a view changes its frame but not its bounds.

Navigation & UI Flow9

pushViewController() adds a view controller to a `UINavigationController` stack (sliding from right to left). present() displays a view controller modally (usually sliding up from the bottom), representing a temporary break in the current flow.

A Segue is a visual transition between two view controllers defined in a Storyboard. It can be triggered programmatically or via UI actions (like a button tap). Developers use `prepare(for:sender:)` to pass data from the current view controller to the next one before the transition occurs.

Deep linking is handled by parsing the URL in `SceneDelegate` or via SwiftUI’s `.onOpenURL`. For complex stacks, a Coordinator Pattern is ideal; the coordinator receives the deep link and decides which specific view controllers to instantiate and push onto the stack to reach the target destination correctly.

A segue is a visual object in Storyboards that represents a transition between two View Controllers. It handles the instantiation of the destination controller. Developers use the `prepare(for:sender:)` method to pass data to the next controller before it is displayed.

Child view controllers allow you to break a complex screen into smaller, manageable, and reusable modules. This follows the Single Responsibility Principle. For example, a dashboard app might have separate child controllers for the header, a list, and a chart, each managing its own logic and lifecycle.

A pushed view is part of a navigation stack, sliding in from the side and maintaining a 'Back' button. A modal view covers the current context (usually sliding up from the bottom) and represents a self-contained task that must be completed or dismissed before returning to the previous flow.

It is a string name assigned to a View Controller in a Storyboard. It allows you to programmatically instantiate that specific View Controller using `instantiateViewController(withIdentifier:)`.

'Push' slides a new view controller onto a navigation stack (usually from right to left). 'Present' displays a view controller modally (usually from bottom to top) and interrupts the current flow until dismissed.

A visual transition between view controllers in a Storyboard, used to define the flow of the application UI.

Auto Layout & UI Creation10

Auto Layout is a constraint-based layout system that calculates the size and position of views dynamically. It allows developers to create adaptive interfaces that work across different screen sizes, orientations, and languages (right-to-left support) without manual coordinate calculation.

Anchors (NSLayoutAnchor) provide a type-safe, programmatic way to create Auto Layout constraints. They represent physical attributes like `topAnchor`, `leadingAnchor`, and `widthAnchor`. Using them makes the code more readable and prevents logical errors (e.g., trying to constrain a top anchor to a leading anchor).

A Storyboard manages the visual layout of multiple view controllers and the transitions (segues) between them. A XIB (XML Interface Builder) is typically used for a single, reusable view or a single view controller. Storyboards help visualize flow, while XIBs are better for modular, reusable UI components.

NSLayoutAnchor provides a type-safe way to create constraints programmatically. Instead of using the old `NSLayoutConstraint` initializer with many parameters, you can write `view.topAnchor.constraint(equalTo: otherView.topAnchor).isActive = true`, which prevents invalid constraints (like top to leading) at compile time.

Size Classes are an abstraction of screen dimensions. Instead of focusing on specific pixel counts, you design for Compact or Regular widths and heights. This allows a single storyboard or view to adapt automatically between an iPhone (Compact width) and an iPad (Regular width).

Size classes (Compact/Regular) are a system that categorizes device screen sizes. They allow you to create adaptive layouts that change depending on whether an app is running on an iPhone or an iPad.

It is a constraint-based layout system that allows you to create adaptive user interfaces by defining relationships between UI elements rather than hard-coding fixed pixel coordinates.

Size classes are a system used in Auto Layout to categorize screen sizes based on their relative space (Compact or Regular). They allow developers to create a single UI that adapts to different devices; for example, showing a grid on iPad (Regular) and a list on iPhone (Compact).

Safe area insets ensure that content is not obscured by system UI elements like the notch, status bar, or home indicator. In UIKit, you use 'safeAreaLayoutGuide'. In SwiftUI, views respect the safe area by default, but you can use '.ignoresSafeArea()' if you want content to bleed into those areas.

Size classes (Compact and Regular) help categorize different screen sizes. They allow developers to create adaptive layouts that look different on iPhone vs iPad using a single storyboard or view configuration.

Concurrency & Async Programming17

A synchronous task blocks the current thread until the task is complete, preventing any other code from running. An asynchronous task returns control to the caller immediately, executing the work in the background. In iOS, you should never run long-running synchronous tasks on the main thread, as it freezes the UI.

GCD is a low-level C-based API for managing concurrent operations. It uses dispatch queues to execute tasks. It abstracts thread management, allowing developers to focus on defining tasks and assigning them to either serial or concurrent queues while the system handles the hardware-level threading.

DispatchQueue.main is a serial queue that executes tasks on the main thread; it must be used for all UI updates. DispatchQueue.global provides concurrent background queues with different Quality of Service (QoS) levels (like .userInitiated or .utility) for non-UI tasks like data processing or networking.

Swift 5.5 introduced structured concurrency with 'async' and 'await'. 'async' marks a function as asynchronous, and 'await' pauses execution until the result is ready without blocking the thread. This eliminates 'callback hell' and makes asynchronous code read like synchronous code, improving safety and readability.

NSOperationQueue is a high-level wrapper over GCD. It allows for more complex task management, such as setting the maximum number of concurrent operations, adding dependencies between tasks (Task B starts only after Task A finishes), and the ability to cancel tasks that are already in the queue.

Background tasks are managed via the 'Background Tasks' framework. You register a task identifier in Info.plist and use `BGTaskScheduler` to submit requests. The system decides when to run these tasks based on battery life and usage patterns. For immediate background work, use `beginBackgroundTask(expirationHandler:)` to finish a task before the app is suspended.

A Synchronous operation blocks the calling thread until it finishes, which can freeze the UI if run on the main thread. An Asynchronous operation returns immediately and performs the work in the background, calling a completion handler or using `await` when the result is ready.

A Synchronous task blocks the current thread until it's done; if called on the main thread, the app becomes unresponsive. An Asynchronous task runs in the background and calls back when finished, allowing the main thread to keep the UI smooth and interactive.

GCD is a low-level API for managing concurrent operations. It abstracts thread management by using dispatch queues. You submit tasks to these queues, and GCD decides which thread to execute them on based on system resources, allowing for smooth multitasking without the complexity of manual thread creation.

DispatchQueue.main is a serial queue that runs on the main thread and is reserved for UI updates. DispatchQueue.global provides concurrent background queues with different Quality of Service (QoS) levels, such as .userInitiated for immediate tasks or .utility for long-running background work like data syncing.

A serial queue executes one task at a time in the order they were added (FIFO). A concurrent queue can start multiple tasks simultaneously; while they still start in order, they can finish in any order depending on the task's duration and system resources.

A race condition occurs when multiple threads attempt to access and modify shared data at the same time, leading to unpredictable results. You prevent it using 'Thread Safety' techniques like serial queues, locks, or modern Swift Actors to ensure only one thread can modify the data at a time.

Async/await is part of 'Structured Concurrency'. It allows asynchronous code to be written linearly, making it easier to read and maintain. It eliminates 'callback hell' by pausing a function's execution without blocking the thread until a result is returned.

A Task is a unit of asynchronous work. It creates a bridge between synchronous and asynchronous code. Tasks can be prioritized, cancelled, and can run child tasks. They provide a safe environment for executing code that uses await/async.

An Actor is a reference type (like a class) that provides safe access to its state by ensuring only one task can access its mutable state at a time. This automatically prevents data races, making it the preferred way to manage shared state in multi-threaded Swift apps.

A synchronous task blocks the current thread until it finishes, while an asynchronous task returns control immediately and runs in the background. In iOS, long-running sync tasks on the main thread cause 'Application Not Responding' (ANR) issues or UI freezes.

Use the BackgroundTasks framework for long-running work. For short tasks, use `beginBackgroundTask`. Always respect battery life by keeping these tasks efficient and registering them in the system scheduler.

Networking & Data9

URLSession is the primary API for downloading and uploading data over HTTP/HTTPS. It coordinates a group of related network data-transfer tasks. It supports background downloads when the app is suspended and handles authentication, cookies, and caching policies automatically.

NSURLSessionDelegate is a protocol that allows an app to handle session-level events, such as authentication challenges, server-side certificate validation, and tracking the progress of background downloads. It provides fine-grained control over the network request lifecycle.

ATS is a security feature enforced by Apple that requires all network connections made by an app to use HTTPS with TLS 1.2 or higher. It prevents the app from accidentally leaking user data over unencrypted connections. Any exceptions must be explicitly declared in the Info.plist file.

URLSession is the primary API for making network requests. It manages a group of related data-transfer tasks. It supports data tasks (for small JSON/data), download tasks (to save files to disk), and upload tasks. It handles background transfers even when the app is suspended.

The delegate is used to handle session-level events such as authentication challenges, server-side certificate validation, and tracking the progress of long-running downloads or uploads that require background execution.

ATS is a security feature that forces apps to use secure connections (HTTPS with TLS 1.2+). It blocks unencrypted HTTP connections by default. If your app must connect to an insecure server, you must explicitly declare an exception in the Info.plist file.

1. Define a URL. 2. Create a URLRequest (optional for headers/methods). 3. Create a URLSessionTask using the session. 4. Call `resume()` to start the task. 5. Handle the result in a completion handler or via `await`.

You define a Swift struct that conforms to the `Codable` (specifically `Decodable`) protocol. Then, you use `JSONDecoder()` to convert the raw data from the server into your Swift object, handling potential errors with a do-catch block.

`URLSession` is the API for network data transfer. It handles HTTP requests, file downloads, and uploads, and supports background execution and automated caching policies.

Data Persistence17

Core Data is an object-graph and persistence framework provided by Apple. It is not a database itself but can use SQLite as its persistent store. You use it to manage complex data models, relationships, and data persistence with features like undo/redo, change tracking, and lazy loading.

NSManagedObject represents a single record or data entry in your model. NSManagedObjectContext is a 'scratchpad' or temporary area where you create, delete, or edit those objects. Changes in the context aren't saved to the disk until you explicitly call the save() method.

Core Data is for local data persistence on the device. CloudKit is a framework for moving data between your app and iCloud. While they can be used together (via NSPersistentCloudKitContainer), CloudKit is focused on syncing data across a user's devices and providing a cloud-based backend.

UserDefaults is excellent for storing small pieces of data like user preferences, flags (e.g., 'isLoggedIn'), and simple settings. It is NOT good for storing large datasets, sensitive information (like passwords), or complex objects, as it is unencrypted and loaded entirely into memory at startup.

Keychain is a secure, encrypted storage container for sensitive data like passwords, API keys, and biometric tokens. Unlike UserDefaults or Core Data, Keychain data persists even after the app is uninstalled and can be shared across multiple apps from the same developer.

For large apps, use background contexts for data processing to avoid blocking the main thread. Implement 'Parent-Child' contexts or use `performBackgroundTask` for thread safety. Additionally, use 'Batch Updates' or 'Batch Deletes' for high-volume changes, and leverage 'Fetched Results Controller' (NSFetchedResultsController) to efficiently manage memory and UI synchronization.

Core Data is a first-party framework that uses SQLite as a backend, offering deep integration with the Apple ecosystem but with a steeper learning curve. Realm is a third-party, object-oriented database that is often faster, easier to use, and works cross-platform (iOS/Android), though it adds extra binary size to your app.

The most efficient way is using NSCache. Unlike a standard Dictionary, `NSCache` is thread-safe and, most importantly, automatically removes items from the cache if the system runs low on memory. This prevents the app from being terminated due to memory pressure while still keeping data fast to access.

Core Data is an object-graph management and persistence framework. Use it when you need to store large amounts of structured data, manage complex relationships between objects (like a user having many posts), and need features like undo/redo or data migration.

NSManagedObject is a single 'row' or data entry. NSManagedObjectContext is a 'scratchpad' where you perform work on those objects (create, delete, update). Changes in the context aren't saved to the actual database until you call `context.save()`.

1. Use background contexts for data processing to keep the main thread free. 2. Use Batch Updates for high-volume changes. 3. Use `NSFetchedResultsController` to sync data with Table/Collection views efficiently. 4. Set persistent history tracking for multi-context syncing.

Core Data is for local storage on the device. CloudKit is for storing data in Apple’s servers to sync across a user's devices. While they are separate, `NSPersistentCloudKitContainer` allows you to use them together for a local database that automatically syncs to the cloud.

It is not suitable for large datasets, sensitive information (like passwords), or complex data structures. It is unencrypted and is loaded entirely into memory when the app starts, so it should only be used for simple user settings and flags.

Keychain is a secure, encrypted storage system for sensitive data like passwords, API keys, and biometric tokens. Unlike other storage methods, Keychain data persists even if the app is deleted, ensuring a user doesn't have to log in again after a reinstall.

It provides a 'Secure Enclave' access for items. You can set accessibility rules, such as requiring the device to be unlocked or requiring biometric (FaceID/TouchID) authentication before the data can be retrieved by the app.

Core Data is a persistence and object-graph framework. Use it for managing large, complex datasets with relationships and features like undo/redo and change tracking.

A secure, encrypted container for sensitive data like passwords and tokens. It persists even if the app is deleted and can be shared between apps in the same developer group.

Notifications5

There are two main types: 1. Local Notifications, which are triggered by the app on the device based on time or location. 2. Remote (Push) Notifications, which are sent from a server via Apple Push Notification service (APNs) to the user's device even if the app is not running.

NotificationCenter (formerly NSNotificationCenter) is a dispatch mechanism that enables the broadcast of information to registered observers. It facilitates the 'Observer' design pattern, allowing unrelated parts of the app to communicate (e.g., notifying the UI that the battery level has changed).

1. Local Notifications: Scheduled by the app on the device (e.g., a reminder). 2. Push Notifications: Sent from a server via Apple Push Notification service (APNs) to the user's device (e.g., a new message alert).

1. Register the app for remote notifications. 2. Obtain a device token. 3. Send the token to your server. 4. Use the server to send payloads to APNs. 5. Handle the incoming notification in `didReceiveRemoteNotification`.

It is a central hub for broadcasting information within an app (Observer pattern). It allows different parts of an app to communicate without being tightly coupled. For example, an 'AuthManager' can notify the 'ProfileView' that a user has logged out.

Testing & Debugging6

You create a subclass of XCTestCase and write methods that start with the word 'test'. Inside these methods, you use assertion functions like XCTAssertEqual() or XCTAssertTrue() to verify that your code behaves as expected. You then run these tests using Cmd+U in Xcode.

Unit tests verify the logic of specific functions or methods in isolation. UI tests simulate user interactions (like tapping buttons and entering text) to ensure the interface and flow of the app work correctly. UI tests are slower but provide high-level confidence in the user experience.

Xcode provides the 'Instruments' tool. Common templates include 'Time Profiler' (to find CPU bottlenecks), 'Leaks' (to find memory leaks), 'Animations' (to check FPS/smoothness), and 'Energy Log' (to identify tasks draining the battery).

You create a subclass of `XCTestCase`. Inside, write functions starting with the word 'test'. Use assertions like `XCTAssertEqual` or `XCTAssertNotNil` to verify that your code's output matches your expectations. Run tests using Cmd+U in Xcode.

1. Using breakpoints to pause execution. 2. The LLDB console for inspecting variables. 3. The Memory Graph Debugger to find retain cycles. 4. View Hierarchy Debugger to fix layout issues. 5. 'Instruments' for profiling performance and leaks.

Using the XCTest framework. You write methods starting with `test`, perform actions, and use `XCTAssert` functions to verify the results match the expected output.

Performance Optimization4

Optimization involves several steps: 1. Reuse table/collection view cells. 2. Downsample images before displaying. 3. Use 'OnPush' or lazy loading. 4. Move heavy tasks off the main thread using GCD. 5. Cache network results locally. 6. Use the 'Instruments' tool to find specific bottlenecks.

Lazy loading is a technique where an object or data is not initialized or fetched until the moment it is actually needed. This saves memory and CPU during app startup. In Swift, you use the 'lazy' keyword for properties, ensuring they are only created when accessed for the first time.

Using `NSCache`. Unlike a standard Dictionary, `NSCache` is thread-safe and automatically removes items if the system runs low on memory, preventing your app from being terminated due to high memory usage.

1. Use reuse identifiers for cells. 2. Offload heavy work to background threads. 3. Lazy-load views and data. 4. Downsample images. 5. Use the 'Instruments' tool to identify and fix memory leaks or CPU spikes.

SwiftUI15

SwiftUI is a declarative framework for building UIs, while UIKit is imperative. In SwiftUI, you describe *what* the UI should look like for a given state, and the framework handles the updates. In UIKit, you must manually manage the UI transitions and state changes through code.

SwiftUI views are structs because they are incredibly lightweight and fast to create. Since structs are value types, they don't have the overhead of reference counting. When state changes, SwiftUI simply recreates the view tree and efficiently updates only the parts of the screen that changed.

@State is used to manage local private state within a single view. When the value of a @State property changes, SwiftUI automatically re-renders the view to reflect the new state. It should only be used for simple properties like toggles, text input, or counters.

@StateObject is used to *create* and own an observable object; it ensures the object is not destroyed during view re-renders. @ObservedObject is used to *pass* an already created object into a view. Using @ObservedObject to create an object can lead to bugs where the object is re-initialized unexpectedly.

GeometryReader is used when a view needs to know the size and coordinate space of its parent container. It allows you to create responsive layouts that adjust based on the available screen real estate, such as making a view exactly half the width of its parent.

In SwiftUI, `ButtonStyle` allows you to create a reusable visual identity for buttons. By implementing the `makeBody(configuration:)` method, you can define how a button looks and behaves when pressed (using `configuration.isPressed`), enabling consistent custom designs across an entire app.

`@Published` is used within an `ObservableObject` to automatically announce changes to any observing SwiftUI views. When a property marked with `@Published` is modified, it triggers an object change notification, causing the UI to re-render with the latest data.

`@StateObject` is used to create and manage an instance of an `ObservableObject` within a view. Unlike `@ObservedObject`, it ensures that the object is only created once and is not destroyed when the view re-renders, making it the right choice for 'owning' the data.

It allows a SwiftUI view to store a piece of data that it can change. When the `@State` value changes, SwiftUI knows the view needs to be updated and automatically re-renders it to show the new information.

In SwiftUI, `@State` allows a view to store and modify data. When the value of a property marked with `@State` changes, SwiftUI automatically re-renders the view to reflect the updated data.

`@StateObject` is used when a view *creates* and owns the object. `@ObservedObject` is used when a view *receives* an object created elsewhere. Using `@StateObject` ensures the object isn't destroyed when the view is redrawn.

'@Published' is a property wrapper that automatically notifies any observers when its value changes. It is commonly used inside an 'ObservableObject' to trigger UI updates in SwiftUI views.

@State is used for private data owned by a single view. @Binding creates a two-way connection to a state property owned by another view, allowing a child view to update data stored in a parent view.

`@State` allows a view to hold mutable state. When it changes, SwiftUI re-renders the view. It should be used for simple, local state like toggles or text field content.

It is used in SwiftUI to create and maintain an instance of an `ObservableObject`, ensuring it isn't recreated when the view updates.

Graphics & Animation10

In UIKit, you typically use `UIView.animate(withDuration:)` for simple property changes like alpha or frame. For more complex, granular control, you use Core Animation (`CABasicAnimation` or `CAKeyframeAnimation`). In SwiftUI, animations are declared using the `.animation()` modifier or wrapped in `withAnimation {}` blocks to animate state changes.

Core Animation is a high-performance graphics rendering and animation infrastructure. It operates on 'Layers' (CALayer) rather than Views. It offloads the heavy lifting of rendering to the GPU, ensuring smooth animations without taxing the CPU. It is the foundation upon which UIKit animations are built.

Core Graphics (Quartz 2D) is a low-level, drawing-based framework. It is used for path-based drawing, transformations, color management, and rendering PDFs. Developers use it when they need to draw custom shapes, gradients, or text directly into a graphics context, usually by overriding a view's `draw(_:)` method.

Common subclasses include: 1. CAShapeLayer (for drawing cubic Bezier paths), 2. CAGradientLayer (for creating color gradients), 3. CATextLayer (for high-performance text rendering), and 4. CAScrollLayer (for displaying portions of a layer).

CGAffineTransform is used to apply 2D geometric transformations to a view or layer. This includes rotating, scaling, or translating (moving) an object. It is a mathematical matrix that changes how a coordinate system is mapped without changing the underlying data of the view.

CADisplayLink is a timer object that allows your app to synchronize its drawing to the refresh rate of the display (e.g., 60Hz or 120Hz). It is much more accurate than a standard `Timer` for creating smooth, frame-by-frame custom animations or game loops.

Core Animation is a low-level graphics rendering and animation infrastructure. It operates on CALayer objects rather than UIViews. It is highly efficient because it offloads rendering work to the GPU. You use it for advanced animations that require precise control over timing, keyframes, or 3D transformations.

Core Graphics (Quartz 2D) is a low-level, path-based drawing framework. You use it when you need to draw custom shapes, gradients, or complex patterns directly into a graphics context (overriding the 'draw' method). It is CPU-based, unlike the GPU-driven Core Animation.

CADisplayLink is a timer object that allows your application to synchronize its drawing to the refresh rate of the display. It is ideal for custom animations or game loops where you need to execute code every time the screen updates (usually 60 or 120 times per second).

CGAffineTransform is used to apply 2D geometric transformations to a view or layer. This includes translation (moving), scaling (resizing), and rotation. It is a 3x3 matrix that modifies the coordinate system of the view without changing its underlying data.

Additional Frameworks8

MapKit is used to display maps, satellite imagery, and point-of-interest information directly within an app. It allows for adding annotations (pins), overlays (routes/shapes), and performing geocoding (converting addresses to coordinates) or reverse-geocoding.

SpriteKit is a high-performance framework optimized for 2D games and animations, focusing on sprites and textures. SceneKit is a high-level descriptive API for 3D graphics, handling 3D geometry, lighting, and cameras. Both include physics engines for simulating real-world movement.

In-app purchases are managed through the StoreKit framework. You define products in App Store Connect, fetch them using `Product.products(for:)` (in StoreKit 2), and handle transactions. You must implement a listener to handle different transaction states (purchased, failed, restored) and securely verify receipts.

There are three main ways: 1. WKWebView (for full control and custom interaction within the app), 2. SFSafariViewController (for a standard Safari experience including shared cookies and autofill), and 3. UIApplication.shared.open() (to leave the app and open the system Safari browser).

In Swift, `FileManager` is the renamed, modernized version of Objective-C's `NSFileManager`. It provides a convenient interface for interacting with the file system, allowing you to create, move, delete, and list files and directories.

CoreBluetooth allows apps to communicate with Bluetooth Low Energy (BLE) devices. It uses a Central (the iPhone) and Peripheral (the device) model. The Central scans for peripherals, connects to them, and discovers 'Services' and 'Characteristics' to read, write, or subscribe to data updates.

The Intents framework (SiriKit) allows your app to integrate with Siri and the Shortcuts app. By defining custom 'Intents', you enable users to perform actions within your app using voice commands or automated workflows, even when your app is not currently open.

This controller provides a standard interface for users to select documents from their device, iCloud Drive, or third-party providers like Dropbox. You implement the `UIDocumentPickerDelegate` to receive the URLs of the selected files and request permission to access them via 'security-scoped' resources.

Security8

Security is handled through: 1. Data Protection API (encrypting files on disk), 2. Keychain (for credentials), 3. App Sandbox (isolating app data), and 4. Biometric Authentication (FaceID/TouchID). Additionally, SSL Pinning is often used to secure network communication.

Major concerns include insecure data storage (avoiding plain text in UserDefaults), Man-in-the-Middle (MitM) attacks on networks, sensitive data appearing in the app switcher (snapshots), and leaking information through logs (`print` vs `os_log`).

In iOS, the modern way to calculate hashes is using the CryptoKit framework. You use `SHA256.hash(data: someData)` which returns a digest. This is much safer and more performant than older CommonCrypto implementations and is used for verifying data integrity or creating unique identifiers for cached files.

1. Use Keychain for passwords/keys. 2. Use Data Protection API for files. 3. Use SSL Pinning for network requests. 4. Use Biometrics for app access. 5. Never store sensitive data in UserDefaults.

User data is secured using the Data Protection API for file-level encryption and the Keychain for sensitive credentials. Developers should implement SSL Pinning to prevent Man-in-the-Middle attacks, use App Sandbox boundaries, and ensure that no sensitive data is leaked through system snapshots or logs.

Major concerns include insecure local storage, network eavesdropping, and binary tampering. Mitigations include using CryptoKit for hashing, requiring Biometric Authentication for sensitive actions, and enabling App Transport Security (ATS) to enforce encrypted network connections.

Using Apple's CryptoKit framework, you can calculate a hash by calling `SHA256.hash(data:)`. This returns a digest that is mathematically unique to the input data, used for verifying file integrity or creating secure tokens without storing the raw data.

Implementation involves the LocalAuthentication framework. You first check `canEvaluatePolicy` to see if biometrics are available, then call `evaluatePolicy` to trigger the system prompt. If successful, you gain access to a secret in the Keychain or permit the user into the app.

Touch & Gestures3

Touch events can be handled in two ways: 1. Gesture Recognizers (high-level, e.g., `UITapGestureRecognizer`), which are preferred for common interactions. 2. UIResponder methods (`touchesBegan`, `touchesMoved`), which offer low-level control over every touch point and movement.

Touch events are handled through the Responder Chain. Low-level handling is done via `touchesBegan`, `touchesMoved`, etc. High-level handling uses UIGestureRecognizers (tap, swipe, pinch), which are generally preferred for better abstraction and multi-touch management.

Common recognizers include `UITapGestureRecognizer` (clicks), `UIPinchGestureRecognizer` (zoom), `UIRotationGestureRecognizer` (rotate), `UISwipeGestureRecognizer` (directional), and `UIPanGestureRecognizer` (dragging/scrolling).

App Store & Distribution9

The process includes: 1. Creating an App ID and Distribution Certificate. 2. Creating an App Store Connect record. 3. Archiving the app in Xcode. 4. Uploading the build. 5. Filling out metadata (screenshots, description). 6. Submitting for Review.

These are a set of rules apps must follow to be published. They cover five categories: Safety, Performance, Business, Design, and Legal. Common reasons for rejection include crashes, hidden features, and missing privacy policies.

1. Create an App ID and Distribution Certificate. 2. Configure the app in App Store Connect. 3. Archive the app in Xcode and upload the build. 4. Complete metadata, privacy info, and screenshots. 5. Submit for Review and wait for Apple's approval.

A set of rules covering Safety, Performance, Business, Design, and Legal. Apps are rejected if they crash, use private APIs, lack a clear privacy policy, or attempt to bypass Apple's in-app purchase system for digital goods.

Versioning uses a combination of Version Number (e.g., 1.2.0, seen by users) and Build Number (e.g., 42, used by the store). Following Semantic Versioning (SemVer) ensures that updates are clearly categorized as major, minor, or patch releases.

Backward compatibility is maintained by including the version in the URL (e.g., `/api/v2/`) and ensuring the app can parse older JSON formats. On the client side, use optional keys in Codable models to prevent crashes if the server omits new fields.

The `UIApplication` class provides a centralized point of control and coordination. It manages the app's event loop, handles the initial routing of events, and maintains the list of open windows. It also manages app-level states and badges.

A `UIWindow` is a container that provides the backdrop for the app's UI and dispatches events to the views. Most apps have only one window, but external displays or complex iPad multitasking may require multiple `UIWindow` instances.

A sensible target is typically N-1 or N-2 versions (e.g., if iOS 18 is current, support iOS 16+). This covers >90% of active users while allowing developers to use modern Swift features and frameworks like SwiftUI or async/await.

Advanced Topics15

Combine is a declarative Swift API for processing values over time. It uses Publishers (to emit values), Operators (to transform values), and Subscribers (to receive values). It is Apple's first-party answer to reactive programming, similar to RxSwift.

`alloc` (allocate) is a class method that sets aside enough memory to hold the object and zeros it out. `init` (initialize) is an instance method that sets up the object's initial state. In modern Swift, these are combined into a single initializer call.

SPM is Apple's native tool for managing code dependencies. It is integrated into Xcode, allowing developers to add, remove, and update third-party libraries using their GitHub URLs. It is generally preferred over CocoaPods today because it doesn't require a workspace file or Ruby environment.

App Clips are lightweight versions of your app that allow users to perform a task quickly without downloading the full app. You create them by adding an 'App Clip' target to your Xcode project, keeping the binary size under 10MB, and using 'Associated Domains' to trigger them via NFC tags, QR codes, or Safari links.

SPM is integrated directly into Xcode, making it the easiest way to manage third-party libraries. It doesn't require extra files like a Podfile or a workspace, and because it's built by Apple, it is the most stable and forward-compatible way to handle dependencies.

Combine is Apple’s unified declarative framework for processing values over time. It uses Publishers (emitters), Operators (transformers), and Subscribers (receivers). It improves reactive programming by providing a native, type-safe alternative to RxSwift, allowing developers to handle asynchronous events like API responses, user input, and state changes in a streamlined, pipeline-based manner.

Combine is Apple's framework for handling asynchronous events via Publishers and Subscribers. It improves programming by providing a native, type-safe pipeline for data transformations, replacing fragmented patterns like KVO, delegates, and notification centers.

App Clips are lightweight versions of an app (<10MB) that perform one task. You implement them by adding an App Clip target to your project and defining Associated Domains so the clip can be triggered via QR codes, NFC, or Safari links.

`alloc` allocates memory for the object and initializes its retain count to 1. `init` sets the initial state of the object. In Swift, these steps are combined into the initializer, but understanding them is crucial for maintaining legacy Obj-C codebases.

This behavioral answer depends on the candidate. Generally, familiarity with header files (.h), implementation files (.m), and the interoperability between Swift and Objective-C via Bridging Headers is essential for professional iOS roles.

Apple's Multiplatform approach (SwiftUI) allows sharing logic. However, watchOS requires extreme memory optimization, tvOS focuses on focus-engine navigation, and macOS requires handling window management and menu bars.

SPM is Apple's tool for managing dependencies directly in Xcode. It is superior to CocoaPods for modern projects because it is native, doesn't require a workspace file, and integrates perfectly with Swift Evolution.

Best practices include using Swift Package Manager primarily, using CocoaPods only for legacy libraries, and ensuring all dependencies are version-locked to prevent 'breaking changes' during automatic updates in a CI/CD pipeline.

Integration is done via SPM, CocoaPods, or manually adding frameworks. You should always wrap third-party SDKs in a Wrapper/Adapter to decouple your app from the library, making it easier to swap or remove in the future.

`guard` provides an early exit from a function if a condition is not met. It keeps the 'happy path' code at the top level of the function rather than deeply nested in `if` statements, significantly improving readability and safety.

Behavioral & General Questions9

Standard practices include watching WWDC sessions, following the Swift Evolution forums, reading blogs like Swift by Sundell or Hacking with Swift, and experimenting with new features in Xcode Playgrounds immediately after beta releases.

Behavioral question. Focus on a specific technical challenge: e.g., 'I implemented a custom caching layer using NSCache' or 'I optimized a scrolling list using pre-fetching' to demonstrate problem-solving skills.

Mention sources like WWDC, Swift Evolution GitHub, Swift by Sundell, and Hacking with Swift. Emphasize the importance of following the beta release notes for new iOS versions.

Subjective question. A good answer focuses on developer pain points, like 'Improving SwiftUI's NavigationStack' or 'Modernizing the legacy Core Data API to be more Swift-friendly.'

Common recommendations: *Swift Programming: The Big Nerd Ranch Guide*, *App Development with Swift* (Apple Books), or *Combine: Asynchronous Programming with Swift* (Ray Wenderlich).

Discuss apps like Airbnb (for typography), Duolingo (for gamification/animations), or Things 3 (for clean UX and gesture integration).

Behavioral question. Even if you haven't contributed to major libraries, mention personal GitHub projects or local community tools to demonstrate passion for the developer ecosystem.

A good process involves checking for architectural consistency, memory safety (retain cycles), proper test coverage, and adherence to the team's style guide while maintaining a positive, constructive tone.

Mention using the Feedback Assistant. Briefly describe a scenario where you found a bug in a beta API or documentation and provided a 'sysdiagnose' to help them resolve it.

Related