November 22, 2025
18 min read

Senior Mobile Developer (iOS) Interview Questions: Complete Guide

interview
career-advice
job-search
Senior Mobile Developer (iOS) Interview Questions: Complete Guide
MB

Milad Bonakdar

Author

Master advanced iOS development with essential interview questions covering architecture patterns, performance optimization, concurrency, Core Data, and system design for senior developers.


Introduction

Senior iOS developers are expected to architect robust, scalable applications while maintaining high code quality and performance. This role requires deep knowledge of iOS frameworks, design patterns, memory management, and the ability to make informed architectural decisions.

This comprehensive guide covers essential interview questions for Senior iOS Developers, spanning advanced Swift concepts, architectural patterns, performance optimization, concurrency, and system design. Each question includes detailed answers, rarity assessment, and difficulty ratings.


Advanced Swift & Language Features (6 Questions)

1. Explain Swift's memory management and ARC (Automatic Reference Counting).

Answer: ARC automatically manages memory by tracking and managing references to class instances.

  • How it works: Each class instance has a reference count. When count reaches zero, the instance is deallocated.
  • Strong References: Default. Increases reference count.
  • Weak References: Don't increase reference count. Automatically become nil when the instance is deallocated.
  • Unowned References: Don't increase reference count but assume the instance always exists.
  • Retain Cycles: Occur when two objects hold strong references to each other, preventing deallocation.
class Person {
    var name: String
    var apartment: Apartment?
    
    init(name: String) { self.name = name }
    deinit { print("\(name) is being deallocated") }
}

class Apartment {
    var unit: String
    weak var tenant: Person?  // weak to break retain cycle
    
    init(unit: String) { self.unit = unit }
    deinit { print("Apartment \(unit) is being deallocated") }
}

Rarity: Very Common Difficulty: Hard


2. What are Generics in Swift and why are they useful?

Answer: Generics allow you to write flexible, reusable functions and types that can work with any type.

  • Benefits: Code reusability, type safety, performance (no runtime overhead)
  • Type Constraints: Restrict generic types to specific protocols or classes
  • Associated Types: Used in protocols to define placeholder types
// Generic function
func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

// Generic type
struct Stack<Element> {
    private var items: [Element] = []
    
    mutating func push(_ item: Element) {
        items.append(item)
    }
    
    mutating func pop() -> Element? {
        return items.popLast()
    }
}

// Type constraint
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

Rarity: Common Difficulty: Medium


3. Explain the difference between escaping and non-escaping closures.

Answer:

  • Non-escaping (default): The closure is executed before the function returns. Compiler can optimize better.
  • Escaping (@escaping): The closure outlives the function (stored in a property, called asynchronously). Must explicitly capture self.
class NetworkManager {
    var completionHandlers: [() -> Void] = []
    
    // Escaping closure - stored for later
    func fetchData(completion: @escaping (Data?) -> Void) {
        URLSession.shared.dataTask(with: url) { data, _, _ in
            completion(data)  // Called after function returns
        }.resume()
    }
    
    // Non-escaping closure - executed immediately
    func processData(_ data: Data, transform: (Data) -> String) -> String {
        return transform(data)  // Called before function returns
    }
}

Rarity: Common Difficulty: Medium


4. What is the difference between map, flatMap, and compactMap?

Answer: These are higher-order functions for transforming collections:

  • map: Transforms each element and returns an array of results
  • compactMap: Like map but filters out nil values
  • flatMap: Flattens nested arrays into a single array
let numbers = [1, 2, 3, 4, 5]

// map
let doubled = numbers.map { $0 * 2 }
// [2, 4, 6, 8, 10]

// compactMap - removes nil
let strings = ["1", "2", "three", "4"]
let validNumbers = strings.compactMap { Int($0) }
// [1, 2, 4]

// flatMap - flattens arrays
let nested = [[1, 2], [3, 4], [5]]
let flattened = nested.flatMap { $0 }
// [1, 2, 3, 4, 5]

Rarity: Common Difficulty: Easy


5. Explain Property Wrappers in Swift.

Answer: Property wrappers add a layer of separation between code that manages how a property is stored and the code that defines a property.

  • Built-in Examples: @State, @Published, @AppStorage in SwiftUI
  • Custom Wrappers: Define reusable property behaviors
@propertyWrapper
struct Capitalized {
    private var value: String = ""
    
    var wrappedValue: String {
        get { value }
        set { value = newValue.capitalized }
    }
}

struct User {
    @Capitalized var firstName: String
    @Capitalized var lastName: String
}

var user = User()
user.firstName = "john"
print(user.firstName)  // "John"

// UserDefaults wrapper
@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T
    
    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
}

struct Settings {
    @UserDefault(key: "username", defaultValue: "Guest")
    static var username: String
}

Rarity: Medium Difficulty: Hard


6. What is the Result type and how is it used?

Answer: Result is an enum that represents either success or failure, making error handling more explicit.

  • Definition: enum Result<Success, Failure: Error>
  • Benefits: Type-safe error handling, clearer API contracts, better than throwing functions for async code
enum NetworkError: Error {
    case invalidURL
    case noData
    case decodingError
}

func fetchUser(id: Int, completion: @escaping (Result<User, NetworkError>) -> Void) {
    guard let url = URL(string: "https://api.example.com/users/\(id)") else {
        completion(.failure(.invalidURL))
        return
    }
    
    URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data else {
            completion(.failure(.noData))
            return
        }
        
        do {
            let user = try JSONDecoder().decode(User.self, from: data)
            completion(.success(user))
        } catch {
            completion(.failure(.decodingError))
        }
    }.resume()
}

// Usage
fetchUser(id: 1) { result in
    switch result {
    case .success(let user):
        print("User: \(user.name)")
    case .failure(let error):
        print("Error: \(error)")
    }
}

Rarity: Common Difficulty: Medium


Architecture Patterns (5 Questions)

7. Explain the MVVM (Model-View-ViewModel) pattern.

Answer: MVVM separates UI logic from business logic, making code more testable and maintainable.

Loading diagram...
  • Model: Data and business logic
  • View: UI (UIViewController, SwiftUI View)
  • ViewModel: Presentation logic, transforms model data for the view
  • Benefits: Testable (ViewModel has no UI dependencies), reusable ViewModels, clear separation of concerns
// Model
struct User {
    let id: Int
    let firstName: String
    let lastName: String
}

// ViewModel
class UserViewModel {
    private let user: User
    
    var fullName: String {
        "\(user.firstName) \(user.lastName)"
    }
    
    var displayText: String {
        "User #\(user.id): \(fullName)"
    }
    
    init(user: User) {
        self.user = user
    }
}

// View
class UserViewController: UIViewController {
    private let viewModel: UserViewModel
    private let label = UILabel()
    
    init(viewModel: UserViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        label.text = viewModel.displayText
    }
}

Rarity: Very Common Difficulty: Medium


8. What is the Coordinator pattern and why use it?

Answer: The Coordinator pattern separates navigation logic from view controllers.

  • Problem: Massive View Controllers with navigation logic mixed with UI logic
  • Solution: Coordinators handle navigation flow
  • Benefits: Reusable view controllers, testable navigation, clear app flow
protocol Coordinator {
    var navigationController: UINavigationController { get }
    func start()
}

class AppCoordinator: Coordinator {
    let navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        showLogin()
    }
    
    func showLogin() {
        let loginVC = LoginViewController()
        loginVC.coordinator = self
        navigationController.pushViewController(loginVC, animated: true)
    }
    
    func showHome() {
        let homeVC = HomeViewController()
        homeVC.coordinator = self
        navigationController.pushViewController(homeVC, animated: true)
    }
}

Rarity: Medium Difficulty: Hard


9. Explain Dependency Injection in iOS.

Answer: Dependency Injection is a design pattern where dependencies are provided to an object rather than created internally.

  • Benefits: Testability (inject mocks), flexibility, loose coupling
  • Types:
    • Constructor Injection: Pass dependencies via initializer (most common)
    • Property Injection: Set dependencies after initialization
    • Method Injection: Pass dependencies as method parameters
// Protocol for abstraction
protocol NetworkService {
    func fetchData(completion: @escaping (Result<Data, Error>) -> Void)
}

// Concrete implementation
class URLSessionNetworkService: NetworkService {
    func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
        // Real network call
    }
}

// Mock for testing
class MockNetworkService: NetworkService {
    func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
        completion(.success(Data()))
    }
}

// ViewModel with dependency injection
class UserViewModel {
    private let networkService: NetworkService
    
    // Constructor injection
    init(networkService: NetworkService) {
        self.networkService = networkService
    }
    
    func loadUsers() {
        networkService.fetchData { result in
            // Handle result
        }
    }
}

// Usage
let viewModel = UserViewModel(networkService: URLSessionNetworkService())

// Testing
let testViewModel = UserViewModel(networkService: MockNetworkService())

Rarity: Common Difficulty: Medium


10. What is the Repository pattern?

Answer: The Repository pattern abstracts data access logic, providing a clean API for data operations.

  • Benefits: Centralized data logic, easy to switch data sources (API, database, cache), testable
  • Implementation: Repository coordinates between multiple data sources
protocol UserRepository {
    func getUser(id: Int) async throws -> User
    func saveUser(_ user: User) async throws
}

class UserRepositoryImpl: UserRepository {
    private let apiService: APIService
    private let cacheService: CacheService
    
    init(apiService: APIService, cacheService: CacheService) {
        self.apiService = apiService
        self.cacheService = cacheService
    }
    
    func getUser(id: Int) async throws -> User {
        // Check cache first
        if let cachedUser = try? await cacheService.getUser(id: id) {
            return cachedUser
        }
        
        // Fetch from API
        let user = try await apiService.fetchUser(id: id)
        
        // Update cache
        try? await cacheService.saveUser(user)
        
        return user
    }
    
    func saveUser(_ user: User) async throws {
        try await apiService.updateUser(user)
        try await cacheService.saveUser(user)
    }
}

Rarity: Medium Difficulty: Medium


11. Explain the differences between MVC, MVP, and MVVM.

Answer:

  • MVC (Model-View-Controller):
    • Apple's default pattern
    • Controller mediates between Model and View
    • Problem: Massive View Controllers
  • MVP (Model-View-Presenter):
    • Presenter handles all UI logic
    • View is passive (just displays data)
    • Better testability than MVC
  • MVVM (Model-View-ViewModel):
    • ViewModel exposes data streams
    • View binds to ViewModel
    • Best for reactive programming (Combine, RxSwift)

Rarity: Common Difficulty: Hard


Performance & Optimization (5 Questions)

12. How do you optimize table view and collection view performance?

Answer: Multiple strategies improve scrolling performance:

  1. Cell Reuse: Use dequeueReusableCell properly
  2. Avoid Heavy Operations: Don't perform expensive calculations in cellForRowAt
  3. Image Optimization:
    • Resize images to display size
    • Use background threads for image processing
    • Cache decoded images
  4. Prefetching: Implement UITableViewDataSourcePrefetching
  5. Height Caching: Cache calculated cell heights
  6. Avoid Transparency: Opaque views render faster
class ImageTableViewCell: UITableViewCell {
    static let identifier = "ImageCell"
    private let customImageView = UIImageView()
    
    override func prepareForReuse() {
        super.prepareForReuse()
        customImageView.image = nil  // Clear old image
    }
}

// In view controller
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: ImageTableViewCell.identifier, for: indexPath) as! ImageTableViewCell
    
    // Load image asynchronously
    DispatchQueue.global(qos: .userInitiated).async {
        let image = self.loadAndResizeImage(at: indexPath)
        DispatchQueue.main.async {
            cell.imageView?.image = image
        }
    }
    
    return cell
}

Rarity: Very Common Difficulty: Medium


13. Explain instruments and how you use them for performance profiling.

Answer: Instruments is Xcode's performance analysis tool.

  • Common Instruments:
    • Time Profiler: Identifies CPU-intensive code
    • Allocations: Tracks memory allocations and leaks
    • Leaks: Detects memory leaks
    • Network: Monitors network activity
    • Energy Log: Analyzes battery usage
  • Workflow:
    1. Profile app (Cmd+I)
    2. Choose instrument
    3. Record and interact with app
    4. Analyze call tree and timeline
    5. Identify bottlenecks

Rarity: Common Difficulty: Medium


14. How do you detect and fix memory leaks?

Answer: Memory leaks occur when objects aren't deallocated when no longer needed.

  • Common Causes:
    • Retain cycles (strong reference cycles)
    • Closures capturing self strongly
    • Delegates not marked as weak
  • Detection:
    • Instruments Leaks tool
    • Memory Graph Debugger in Xcode
    • Watch for increasing memory usage
  • Fixes:
    • Use weak or unowned for delegates
    • Use [weak self] or [unowned self] in closures
    • Break retain cycles
class ViewController: UIViewController {
    var closure: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // BAD - retain cycle
        closure = {
            self.view.backgroundColor = .red
        }
        
        // GOOD - weak self
        closure = { [weak self] in
            self?.view.backgroundColor = .red
        }
        
        // GOOD - unowned self (if self always exists)
        closure = { [unowned self] in
            self.view.backgroundColor = .red
        }
    }
}

Rarity: Very Common Difficulty: Medium


15. What techniques do you use for app startup optimization?

Answer: Faster app launch improves user experience:

  1. Lazy Loading: Initialize objects only when needed
  2. Reduce Dylib Loading: Minimize dynamic libraries
  3. Optimize application:didFinishLaunching:
    • Move non-critical work to background
    • Defer heavy initialization
  4. Binary Size: Smaller binary loads faster
  5. Avoid Heavy Operations: Don't block main thread
  6. Measure: Use Instruments' App Launch template
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // Critical: Setup window and root view controller
    setupWindow()
    
    // Defer non-critical work
    DispatchQueue.main.async {
        self.setupAnalytics()
        self.setupCrashReporting()
    }
    
    // Background work
    DispatchQueue.global(qos: .background).async {
        self.preloadData()
    }
    
    return true
}

Rarity: Common Difficulty: Medium


16. How do you handle image caching and loading?

Answer: Efficient image handling is crucial for performance:

  • Strategies:
    • Memory Cache: Fast access, limited size
    • Disk Cache: Persistent, larger capacity
    • Download: Fetch from network
  • Libraries: SDWebImage, Kingfisher (handle caching automatically)
  • Custom Implementation:
class ImageCache {
    static let shared = ImageCache()
    
    private let memoryCache = NSCache<NSString, UIImage>()
    private let fileManager = FileManager.default
    private let cacheDirectory: URL
    
    private init() {
        cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
            .appendingPathComponent("ImageCache")
        try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true)
    }
    
    func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
        let key = url.absoluteString as NSString
        
        // Check memory cache
        if let cachedImage = memoryCache.object(forKey: key) {
            completion(cachedImage)
            return
        }
        
        // Check disk cache
        let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
        if let diskImage = UIImage(contentsOfFile: fileURL.path) {
            memoryCache.setObject(diskImage, forKey: key)
            completion(diskImage)
            return
        }
        
        // Download
        URLSession.shared.dataTask(with: url) { data, _, _ in
            guard let data = data, let image = UIImage(data: data) else {
                completion(nil)
                return
            }
            
            // Save to caches
            self.memoryCache.setObject(image, forKey: key)
            try? data.write(to: fileURL)
            
            DispatchQueue.main.async {
                completion(image)
            }
        }.resume()
    }
}

Rarity: Common Difficulty: Hard


Concurrency & Async Programming (4 Questions)

17. Explain async/await in Swift.

Answer: Swift's modern concurrency model introduced in Swift 5.5.

  • Benefits: Cleaner syntax than completion handlers, easier error handling, compiler-enforced thread safety
  • Keywords:
    • async: Marks a function that can suspend
    • await: Marks a suspension point
    • Task: Creates a new asynchronous context
    • actor: Thread-safe reference type
// Old way with completion handlers
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, response, error in
        // Handle result
    }.resume()
}

// New way with async/await
func fetchUser(id: Int) async throws -> User {
    let (data, _) = try await URLSession.shared.data(from: url)
    let user = try JSONDecoder().decode(User.self, from: data)
    return user
}

// Usage
Task {
    do {
        let user = try await fetchUser(id: 1)
        print(user.name)
    } catch {
        print("Error: \(error)")
    }
}

// Parallel execution
async let user1 = fetchUser(id: 1)
async let user2 = fetchUser(id: 2)
let users = try await [user1, user2]

Rarity: Very Common Difficulty: Hard


18. What are Actors in Swift?

Answer: Actors are reference types that protect their mutable state from data races.

  • Thread Safety: Only one task can access actor's mutable state at a time
  • Automatic Synchronization: Compiler enforces safe access
  • Main Actor: Special actor for UI updates
actor BankAccount {
    private var balance: Double = 0
    
    func deposit(amount: Double) {
        balance += amount
    }
    
    func withdraw(amount: Double) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }
    
    func getBalance() -> Double {
        return balance
    }
}

// Usage
let account = BankAccount()

Task {
    await account.deposit(amount: 100)
    let balance = await account.getBalance()
    print("Balance: \(balance)")
}

// Main Actor for UI updates
@MainActor
class ViewModel: ObservableObject {
    @Published var data: [String] = []
    
    func loadData() async {
        let newData = await fetchDataFromAPI()
        // Automatically on main thread
        self.data = newData
    }
}

Rarity: Medium Difficulty: Hard


19. Explain the Combine framework.

Answer: Combine is Apple's reactive programming framework.

  • Core Concepts:
    • Publisher: Emits values over time
    • Subscriber: Receives values
    • Operator: Transforms values
  • Benefits: Declarative, composable, built-in operators
  • Use Cases: Networking, user input handling, data binding
import Combine

class UserViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var users: [User] = []
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        $searchText
            .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
            .removeDuplicates()
            .filter { !$0.isEmpty }
            .flatMap { query in
                self.searchUsers(query: query)
            }
            .receive(on: DispatchQueue.main)
            .sink { [weak self] users in
                self?.users = users
            }
            .store(in: &cancellables)
    }
    
    func searchUsers(query: String) -> AnyPublisher<[User], Never> {
        // Return publisher
        URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [User].self, decoder: JSONDecoder())
            .replaceError(with: [])
            .eraseToAnyPublisher()
    }
}

Rarity: Common Difficulty: Hard


20. What is the difference between Serial and Concurrent queues?

Answer: Dispatch queues execute tasks either serially or concurrently:

  • Serial Queue: Executes one task at a time in FIFO order. Tasks wait for previous task to complete.
  • Concurrent Queue: Executes multiple tasks simultaneously. Tasks start in FIFO order but can finish in any order.
  • Main Queue: Special serial queue for UI updates
// Serial queue
let serialQueue = DispatchQueue(label: "com.app.serial")
serialQueue.async { print("Task 1") }
serialQueue.async { print("Task 2") }
// Output: Task 1, Task 2 (always in order)

// Concurrent queue
let concurrentQueue = DispatchQueue(label: "com.app.concurrent", attributes: .concurrent)
concurrentQueue.async { print("Task A") }
concurrentQueue.async { print("Task B") }
// Output: Task A, Task B OR Task B, Task A (order not guaranteed)

// Barrier for write operations
concurrentQueue.async(flags: .barrier) {
    // Exclusive access - no other tasks run simultaneously
    print("Write operation")
}

Rarity: Common Difficulty: Medium


Core Data & Persistence (3 Questions)

21. Explain Core Data architecture and its main components.

Answer: Core Data is Apple's object graph and persistence framework.

Loading diagram...
  • NSManagedObjectModel: Schema definition (entities, attributes, relationships)
  • NSPersistentStoreCoordinator: Coordinates between context and store
  • NSManagedObjectContext: Working memory for objects (like a scratch pad)
  • NSPersistentStore: Actual storage (SQLite, binary, in-memory)

Rarity: Common Difficulty: Medium


22. How do you handle concurrency in Core Data?

Answer: Core Data contexts are not thread-safe. Use proper concurrency patterns:

  • Context Types:
    • Main Queue Context: For UI operations
    • Private Queue Context: For background operations
  • Best Practices:
    • Never pass managed objects between threads
    • Use perform or performAndWait for context operations
    • Pass object IDs between contexts
class CoreDataManager {
    let persistentContainer: NSPersistentContainer
    
    var viewContext: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
    
    func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
        persistentContainer.performBackgroundTask { context in
            block(context)
            
            if context.hasChanges {
                try? context.save()
            }
        }
    }
}

// Usage
coreDataManager.performBackgroundTask { context in
    let user = User(context: context)
    user.name = "John"
    // Context saves automatically
}

Rarity: Medium Difficulty: Hard


23. What is NSFetchedResultsController and when do you use it?

Answer: NSFetchedResultsController efficiently manages Core Data results for table/collection views.

  • Benefits:
    • Automatic change tracking
    • Memory efficient (batching)
    • Section management
    • Automatic UI updates
  • Use Case: Displaying Core Data objects in table/collection views
class UsersViewController: UITableViewController, NSFetchedResultsControllerDelegate {
    var fetchedResultsController: NSFetchedResultsController<User>!
    
    func setupFetchedResultsController() {
        let fetchRequest: NSFetchRequest<User> = User.fetchRequest()
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
        
        fetchedResultsController = NSFetchedResultsController(
            fetchRequest: fetchRequest,
            managedObjectContext: context,
            sectionNameKeyPath: nil,
            cacheName: nil
        )
        
        fetchedResultsController.delegate = self
        try? fetchedResultsController.performFetch()
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return fetchedResultsController.sections?[section].numberOfObjects ?? 0
    }
    
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.reloadData()
    }
}

Rarity: Medium Difficulty: Medium


System Design & Best Practices (2 Questions)

24. How would you design an offline-first mobile app?

Answer: Offline-first apps work without internet and sync when connected.

  • Architecture:
    • Local database as source of truth (Core Data, Realm)
    • Sync layer to reconcile with server
    • Conflict resolution strategy
  • Strategies:
    • Optimistic UI: Show changes immediately, sync in background
    • Queue Operations: Store failed requests, retry when online
    • Timestamp/Version: Track data freshness
  • Challenges:
    • Conflict resolution (last-write-wins, merge, user choice)
    • Data consistency
    • Storage limits

Rarity: Medium Difficulty: Hard


25. What are your strategies for app security in iOS?

Answer: Multiple layers protect app and user data:

  1. Data Protection:
    • Keychain for sensitive data (passwords, tokens)
    • Encrypt data at rest
    • Use Data Protection API
  2. Network Security:
    • HTTPS only (App Transport Security)
    • Certificate pinning for critical APIs
    • Validate SSL certificates
  3. Code Security:
    • Obfuscation for sensitive logic
    • Jailbreak detection
    • No hardcoded secrets
  4. Authentication:
    • Biometric authentication (Face ID, Touch ID)
    • Secure token storage
    • Token refresh mechanism
  5. Input Validation:
    • Sanitize user input
    • Prevent injection attacks
// Keychain storage
class KeychainManager {
    func save(token: String) {
        let data = token.data(using: .utf8)!
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: "authToken",
            kSecValueData as String: data
        ]
        SecItemAdd(query as CFDictionary, nil)
    }
}

// Biometric authentication
import LocalAuthentication

func authenticateUser(completion: @escaping (Bool) -> Void) {
    let context = LAContext()
    var error: NSError?
    
    if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
        context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Authenticate to access app") { success, error in
            completion(success)
        }
    }
}

Rarity: Common Difficulty: Hard


Newsletter subscription

Weekly career tips that actually work

Get the latest insights delivered straight to your inbox

Decorative doodle

Stop Applying. Start Getting Hired.

Transform your resume into an interview magnet with AI-powered optimization trusted by job seekers worldwide.

Get started free

Share this post

Beat the 75% ATS Rejection Rate

3 out of 4 resumes never reach a human eye. Our keyword optimization increases your pass rate by up to 80%, ensuring recruiters actually see your potential.