dicembre 21, 2025
19 min di lettura

Domande di colloquio per Senior Mobile Developer (iOS): Guida completa

interview
career-advice
job-search
Domande di colloquio per Senior Mobile Developer (iOS): Guida completa
MB

Milad Bonakdar

Autore

Padroneggia lo sviluppo iOS avanzato con domande di colloquio essenziali che coprono modelli architetturali, ottimizzazione delle prestazioni, concorrenza, Core Data e progettazione del sistema per sviluppatori senior.


Introduzione

Ci si aspetta che gli sviluppatori iOS senior progettino applicazioni robuste e scalabili mantenendo al contempo un'elevata qualità del codice e prestazioni. Questo ruolo richiede una profonda conoscenza dei framework iOS, dei modelli di progettazione, della gestione della memoria e la capacità di prendere decisioni architetturali informate.

Questa guida completa copre le domande essenziali per il colloquio per sviluppatori iOS senior, spaziando tra concetti avanzati di Swift, modelli architetturali, ottimizzazione delle prestazioni, concorrenza e progettazione del sistema. Ogni domanda include risposte dettagliate, valutazione della rarità e valutazione della difficoltà.


Swift avanzato e funzionalità del linguaggio (6 domande)

1. Spiega la gestione della memoria di Swift e ARC (Automatic Reference Counting).

Risposta: ARC gestisce automaticamente la memoria tracciando e gestendo i riferimenti alle istanze di classe.

  • Come funziona: ogni istanza di classe ha un conteggio dei riferimenti. Quando il conteggio raggiunge lo zero, l'istanza viene deallocata.
  • Riferimenti forti: predefinito. Aumenta il conteggio dei riferimenti.
  • Riferimenti deboli: non aumentano il conteggio dei riferimenti. Diventano automaticamente nil quando l'istanza viene deallocata.
  • Riferimenti non posseduti: non aumentano il conteggio dei riferimenti ma presuppongono che l'istanza esista sempre.
  • Cicli di conservazione: si verificano quando due oggetti detengono forti riferimenti l'uno all'altro, impedendo la deallocazione.
class Person {
    var name: String
    var apartment: Apartment?
    
    init(name: String) { self.name = name }
    deinit { print("\(name) viene deallocato") }
}

class Apartment {
    var unit: String
    weak var tenant: Person?  // debole per interrompere il ciclo di conservazione
    
    init(unit: String) { self.unit = unit }
    deinit { print("Appartamento \(unit) viene deallocato") }
}

Rarità: Molto comune Difficoltà: Difficile


2. Cosa sono i Generics in Swift e perché sono utili?

Risposta: I Generics ti consentono di scrivere funzioni e tipi flessibili e riutilizzabili che possono funzionare con qualsiasi tipo.

  • Vantaggi: riutilizzabilità del codice, sicurezza del tipo, prestazioni (nessun overhead di runtime)
  • Vincoli di tipo: limitano i tipi generici a protocolli o classi specifici
  • Tipi associati: utilizzati nei protocolli per definire tipi di segnaposto
// Funzione generica
func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

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

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

Rarità: Comune Difficoltà: Media


3. Spiega la differenza tra closure escaping e non-escaping.

Risposta:

  • Non-escaping (predefinito): la closure viene eseguita prima che la funzione ritorni. Il compilatore può ottimizzare meglio.
  • Escaping (@escaping): la closure sopravvive alla funzione (memorizzata in una proprietà, chiamata in modo asincrono). Deve acquisire esplicitamente self.
class NetworkManager {
    var completionHandlers: [() -> Void] = []
    
    // Closure di escaping - memorizzata per dopo
    func fetchData(completion: @escaping (Data?) -> Void) {
        URLSession.shared.dataTask(with: url) { data, _, _ in
            completion(data)  // Chiamata dopo che la funzione ritorna
        }.resume()
    }
    
    // Closure non-escaping - eseguita immediatamente
    func processData(_ data: Data, transform: (Data) -> String) -> String {
        return transform(data)  // Chiamata prima che la funzione ritorni
    }
}

Rarità: Comune Difficoltà: Media


4. Qual è la differenza tra map, flatMap e compactMap?

Risposta: Queste sono funzioni di ordine superiore per trasformare le raccolte:

  • map: trasforma ogni elemento e restituisce un array di risultati
  • compactMap: come map ma filtra i valori nil
  • flatMap: appiattisce gli array nidificati in un singolo array
let numbers = [1, 2, 3, 4, 5]

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

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

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

Rarità: Comune Difficoltà: Facile


5. Spiega i Property Wrappers in Swift.

Risposta: I Property Wrappers aggiungono un livello di separazione tra il codice che gestisce come viene memorizzata una proprietà e il codice che definisce una proprietà.

  • Esempi integrati: @State, @Published, @AppStorage in SwiftUI
  • Wrapper personalizzati: definiscono comportamenti di proprietà riutilizzabili
@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"

// Wrapper UserDefaults
@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
}

Rarità: Media Difficoltà: Difficile


6. Cos'è il tipo Result e come viene utilizzato?

Risposta: Result è un enum che rappresenta il successo o il fallimento, rendendo più esplicita la gestione degli errori.

  • Definizione: enum Result<Success, Failure: Error>
  • Vantaggi: gestione degli errori type-safe, contratti API più chiari, meglio delle funzioni di lancio per il codice asincrono
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()
}

// Utilizzo
fetchUser(id: 1) { result in
    switch result {
    case .success(let user):
        print("Utente: \(user.name)")
    case .failure(let error):
        print("Errore: \(error)")
    }
}

Rarità: Comune Difficoltà: Media


Modelli di architettura (5 domande)

7. Spiega il modello MVVM (Model-View-ViewModel).

Risposta: MVVM separa la logica dell'interfaccia utente dalla logica di business, rendendo il codice più testabile e manutenibile.

Loading diagram...
  • Model: dati e logica di business
  • View: UI (UIViewController, SwiftUI View)
  • ViewModel: logica di presentazione, trasforma i dati del modello per la view
  • Vantaggi: testabile (ViewModel non ha dipendenze UI), ViewModel riutilizzabili, chiara separazione delle preoccupazioni
// Modello
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 {
        "Utente #\(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
    }
}

Rarità: Molto comune Difficoltà: Media


8. Cos'è il modello Coordinator e perché usarlo?

Risposta: Il modello Coordinator separa la logica di navigazione dai view controller.

  • Problema: view controller enormi con logica di navigazione mista alla logica dell'interfaccia utente
  • Soluzione: i Coordinator gestiscono il flusso di navigazione
  • Vantaggi: view controller riutilizzabili, navigazione testabile, flusso dell'app chiaro
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)
    }
}

Rarità: Media Difficoltà: Difficile


9. Spiega l'Dependency Injection in iOS.

Risposta: L'Dependency Injection è un modello di progettazione in cui le dipendenze vengono fornite a un oggetto anziché create internamente.

  • Vantaggi: testabilità (iniezione di mock), flessibilità, basso accoppiamento
  • Tipi:
    • Constructor Injection: passa le dipendenze tramite l'inizializzatore (il più comune)
    • Property Injection: imposta le dipendenze dopo l'inizializzazione
    • Method Injection: passa le dipendenze come parametri del metodo
// Protocollo per l'astrazione
protocol NetworkService {
    func fetchData(completion: @escaping (Result<Data, Error>) -> Void)
}

// Implementazione concreta
class URLSessionNetworkService: NetworkService {
    func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
        // Chiamata di rete reale
    }
}

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

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

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

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

Rarità: Comune Difficoltà: Media


10. Cos'è il modello Repository?

Risposta: Il modello Repository astrae la logica di accesso ai dati, fornendo un'API pulita per le operazioni sui dati.

  • Vantaggi: logica dei dati centralizzata, facile commutazione delle origini dati (API, database, cache), testabile
  • Implementazione: il repository coordina tra più origini dati
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 {
        // Controlla prima la cache
        if let cachedUser = try? await cacheService.getUser(id: id) {
            return cachedUser
        }
        
        // Recupera dall'API
        let user = try await apiService.fetchUser(id: id)
        
        // Aggiorna la cache
        try? await cacheService.saveUser(user)
        
        return user
    }
    
    func saveUser(_ user: User) async throws {
        try await apiService.updateUser(user)
        try await cacheService.saveUser(user)
    }
}

Rarità: Media Difficoltà: Media


11. Spiega le differenze tra MVC, MVP e MVVM.

Risposta:

  • MVC (Model-View-Controller):
    • Modello predefinito di Apple
    • Il controller media tra Model e View
    • Problema: view controller enormi
  • MVP (Model-View-Presenter):
    • Il presenter gestisce tutta la logica dell'interfaccia utente
    • La view è passiva (visualizza solo i dati)
    • Migliore testabilità rispetto a MVC
  • MVVM (Model-View-ViewModel):
    • ViewModel espone flussi di dati
    • La view si lega al ViewModel
    • Ottimo per la programmazione reattiva (Combine, RxSwift)

Rarità: Comune Difficoltà: Difficile


Prestazioni e ottimizzazione (5 domande)

12. Come si ottimizzano le prestazioni di table view e collection view?

Risposta: Molteplici strategie migliorano le prestazioni di scorrimento:

  1. Riutilizzo delle celle: usa correttamente dequeueReusableCell
  2. Evita operazioni pesanti: non eseguire calcoli costosi in cellForRowAt
  3. Ottimizzazione delle immagini:
    • Ridimensiona le immagini alla dimensione di visualizzazione
    • Usa thread in background per l'elaborazione delle immagini
    • Memorizza nella cache le immagini decodificate
  4. Prefetching: implementa UITableViewDataSourcePrefetching
  5. Caching dell'altezza: memorizza nella cache le altezze delle celle calcolate
  6. Evita la trasparenza: le view opache vengono renderizzate più velocemente
class ImageTableViewCell: UITableViewCell {
    static let identifier = "ImageCell"
    private let customImageView = UIImageView()
    
    override func prepareForReuse() {
        super.prepareForReuse()
        customImageView.image = nil  // Cancella la vecchia immagine
    }
}

// Nel view controller
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: ImageTableViewCell.identifier, for: indexPath) as! ImageTableViewCell
    
    // Carica l'immagine in modo asincrono
    DispatchQueue.global(qos: .userInitiated).async {
        let image = self.loadAndResizeImage(at: indexPath)
        DispatchQueue.main.async {
            cell.imageView?.image = image
        }
    }
    
    return cell
}

Rarità: Molto comune Difficoltà: Media


13. Spiega gli strumenti e come li usi per la profilazione delle prestazioni.

Risposta: Instruments è lo strumento di analisi delle prestazioni di Xcode.

  • Strumenti comuni:
    • Time Profiler: identifica il codice ad alta intensità di CPU
    • Allocations: tiene traccia delle allocazioni di memoria e delle perdite
    • Leaks: rileva le perdite di memoria
    • Network: monitora l'attività di rete
    • Energy Log: analizza l'utilizzo della batteria
  • Flusso di lavoro:
    1. Profila l'app (Cmd+I)
    2. Scegli lo strumento
    3. Registra e interagisci con l'app
    4. Analizza l'albero delle chiamate e la timeline
    5. Identifica i colli di bottiglia

Rarità: Comune Difficoltà: Media


14. Come si rilevano e si correggono le perdite di memoria?

Risposta: Le perdite di memoria si verificano quando gli oggetti non vengono deallocati quando non sono più necessari.

  • Cause comuni:
    • Cicli di conservazione (cicli di riferimento forti)
    • Closure che acquisiscono self fortemente
    • Delegati non contrassegnati come weak
  • Rilevamento:
    • Strumento Instruments Leaks
    • Debugger del grafico di memoria in Xcode
    • Osserva l'aumento dell'utilizzo della memoria
  • Correzioni:
    • Usa weak o unowned per i delegati
    • Usa [weak self] o [unowned self] nelle closure
    • Interrompi i cicli di conservazione
class ViewController: UIViewController {
    var closure: (() -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // MALE - ciclo di conservazione
        closure = {
            self.view.backgroundColor = .red
        }
        
        // BENE - self debole
        closure = { [weak self] in
            self?.view.backgroundColor = .red
        }
        
        // BENE - self non posseduto (se self esiste sempre)
        closure = { [unowned self] in
            self.view.backgroundColor = .red
        }
    }
}

Rarità: Molto comune Difficoltà: Media


15. Quali tecniche usi per l'ottimizzazione dell'avvio dell'app?

Risposta: Un avvio dell'app più veloce migliora l'esperienza utente:

  1. Caricamento pigro: inizializza gli oggetti solo quando necessario
  2. Riduci il caricamento di Dylib: riduci al minimo le librerie dinamiche
  3. Ottimizza application:didFinishLaunching:
    • Sposta il lavoro non critico in background
    • Rimanda l'inizializzazione pesante
  4. Dimensione binaria: un binario più piccolo si carica più velocemente
  5. Evita operazioni pesanti: non bloccare il thread principale
  6. Misura: usa il modello App Launch di Instruments
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    // Critico: imposta la finestra e il view controller radice
    setupWindow()
    
    // Rimanda il lavoro non critico
    DispatchQueue.main.async {
        self.setupAnalytics()
        self.setupCrashReporting()
    }
    
    // Lavoro in background
    DispatchQueue.global(qos: .background).async {
        self.preloadData()
    }
    
    return true
}

Rarità: Comune Difficoltà: Media


16. Come gestisci la memorizzazione nella cache e il caricamento delle immagini?

Risposta: La gestione efficiente delle immagini è fondamentale per le prestazioni:

  • Strategie:
    • Cache di memoria: accesso rapido, dimensioni limitate
    • Cache del disco: persistente, maggiore capacità
    • Download: recupera dalla rete
  • Librerie: SDWebImage, Kingfisher (gestiscono automaticamente la memorizzazione nella cache)
  • Implementazione personalizzata:
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
        
        // Controlla la cache di memoria
        if let cachedImage = memoryCache.object(forKey: key) {
            completion(cachedImage)
            return
        }
        
        // Controlla la cache del disco
        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
            }
            
            // Salva nelle cache
            self.memoryCache.setObject(image, forKey: key)
            try? data.write(to: fileURL)
            
            DispatchQueue.main.async {
                completion(image)
            }
        }.resume()
    }
}

Rarità: Comune Difficoltà: Difficile


Concorrenza e programmazione asincrona (4 domande)

17. Spiega async/await in Swift.

Risposta: Il moderno modello di concorrenza di Swift introdotto in Swift 5.5.

  • Vantaggi: sintassi più pulita rispetto ai completion handler, gestione degli errori più semplice, sicurezza dei thread applicata dal compilatore
  • Parole chiave:
    • async: contrassegna una funzione che può sospendere
    • await: contrassegna un punto di sospensione
    • Task: crea un nuovo contesto asincrono
    • actor: tipo di riferimento thread-safe
// Vecchio modo con i completion handler
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, response, error in
        // Gestisci il risultato
    }.resume()
}

// Nuovo modo con 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
}

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

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

Rarità: Molto comune Difficoltà: Difficile


18. Cosa sono gli Actor in Swift?

Risposta: Gli Actor sono tipi di riferimento che proteggono il loro stato mutabile dalle race condition.

  • Sicurezza dei thread: solo un'attività può accedere allo stato mutabile dell'actor alla volta
  • Sincronizzazione automatica: il compilatore applica l'accesso sicuro
  • Main Actor: actor speciale per gli aggiornamenti dell'interfaccia utente
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
    }
}

// Utilizzo
let account = BankAccount()

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

// Main Actor per gli aggiornamenti dell'interfaccia utente
@MainActor
class ViewModel: ObservableObject {
    @Published var data: [String] = []
    
    func loadData() async {
        let newData = await fetchDataFromAPI()
        // Automaticamente sul thread principale
        self.data = newData
    }
}

Rarità: Media Difficoltà: Difficile


19. Spiega il framework Combine.

Risposta: Combine è il framework di programmazione reattiva di Apple.

  • Concetti fondamentali:
    • Publisher: emette valori nel tempo
    • Subscriber: riceve valori
    • Operator: trasforma i valori
  • Vantaggi: dichiarativo, componibile, operatori integrati
  • Casi d'uso: networking, gestione dell'input utente, 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> {
        // Restituisce il publisher
        URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [User].self, decoder: JSONDecoder())
            .replaceError(with: [])
            .eraseToAnyPublisher()
    }
}

Rarità: Comune Difficoltà: Difficile


20. Qual è la differenza tra code seriali e concorrenti?

Risposta: Le code di dispatch eseguono attività in serie o in modo concorrente:

  • Coda seriale: esegue un'attività alla volta in ordine FIFO. Le attività attendono il completamento dell'attività precedente.
  • Coda concorrente: esegue più attività contemporaneamente. Le attività iniziano in ordine FIFO ma possono terminare in qualsiasi ordine.
  • Coda principale: coda seriale speciale per gli aggiornamenti dell'interfaccia utente
// Coda seriale
let serialQueue = DispatchQueue(label: "com.app.serial")
serialQueue.async { print("Attività 1") }
serialQueue.async { print("Attività 2") }
// Output: Attività 1, Attività 2 (sempre in ordine)

// Coda concorrente
let concurrentQueue = DispatchQueue(label: "com.app.concurrent", attributes: .concurrent)
concurrentQueue.async { print("Attività A") }
concurrentQueue.async { print("Attività B") }
// Output: Attività A, Attività B OPPURE Attività B, Attività A (ordine non garantito)

// Barriera per le operazioni di scrittura
concurrentQueue.async(flags: .barrier) {
    // Accesso esclusivo: nessun'altra attività viene eseguita contemporaneamente
    print("Operazione di scrittura")
}

Rarità: Comune Difficoltà: Media


Core Data e persistenza (3 domande)

21. Spiega l'architettura di Core Data e i suoi componenti principali.

Risposta: Core Data è il framework di Apple per grafi di oggetti e persistenza.

Loading diagram...
  • NSManagedObjectModel: definizione dello schema (entità, attributi, relazioni)
  • NSPersistentStoreCoordinator: coordina tra contesto e store
  • NSManagedObjectContext: memoria di lavoro per gli oggetti (come un blocco note)
  • NSPersistentStore: archiviazione effettiva (SQLite, binario, in memoria)

Rarità: Comune Difficoltà: Media


22. Come gestisci la concorrenza in Core Data?

Risposta: I contesti di Core Data non sono thread-safe. Usa i modelli di concorrenza corretti:

  • Tipi di contesto:
    • Contesto della coda principale: per le operazioni dell'interfaccia utente
    • Contesto della coda privata: per le operazioni in background
  • Best practice:
    • Non passare mai oggetti gestiti tra thread
    • Usa perform o performAndWait per le operazioni di contesto
    • Passa gli ID oggetto tra i contesti
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()
            }
        }
    }
}

// Utilizzo
coreDataManager.performBackgroundTask { context in
    let user = User(context: context)
    user.name = "John"
    // Il contesto si salva automaticamente
}

Rarità: Media Difficoltà: Difficile


23. Cos'è NSFetchedResultsController e quando lo usi?

Risposta: NSFetchedResultsController gestisce in modo efficiente i risultati di Core Data per table/collection view.

  • Vantaggi:
    • Tracciamento automatico delle modifiche
    • Efficienza della memoria (batching)
    • Gestione delle sezioni
    • Aggiornamenti automatici dell'interfaccia utente
  • Caso d'uso: visualizzazione di oggetti Core Data in table/collection view
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()
    }
}

Rarità: Media Difficoltà: Media


Progettazione del sistema e best practice (2 domande)

24. Come progetteresti un'app mobile offline-first?

Risposta: Le app offline-first funzionano senza Internet e si sincronizzano quando sono connesse.

  • Architettura:
    • Database locale come fonte di verità (Core Data, Realm)
    • Livello di sincronizzazione per la riconciliazione con il server
    • Strategia di risoluzione dei conflitti
  • Strategie:
    • Interfaccia utente ottimistica: mostra le modifiche immediatamente, sincronizza in background
    • Accoda operazioni: archivia le richieste non riuscite, riprova quando sei online
    • Timestamp/Versione: tiene traccia della freschezza dei dati
  • Sfide:
    • Risoluzione dei conflitti (last-write-wins, merge, scelta dell'utente)
    • Coerenza dei dati
    • Limiti di archiviazione

Rarità: Media Difficoltà: Difficile


25. Quali sono le tue strategie per la sicurezza delle app in iOS?

Risposta: Molteplici livelli prote

Newsletter subscription

Consigli di carriera settimanali che funzionano davvero

Ricevi le ultime idee direttamente nella tua casella di posta

Decorative doodle

Crea un Curriculum che Ti Faccia Assumere il 60% Più Velocemente

In pochi minuti, crea un curriculum personalizzato e compatibile con ATS che ha dimostrato di ottenere 6 volte più colloqui.

Crea un curriculum migliore

Condividi questo post

Vieni Assunto il 50% Più Velocemente

Le persone in cerca di lavoro che utilizzano curriculum professionali migliorati dall'IA trovano ruoli in una media di 5 settimane rispetto alle 10 standard. Smetti di aspettare e inizia a fare colloqui.