Le 25 domande più frequenti nei colloqui Swift per sviluppatori iOS

Preparazione ai colloqui iOS con le 25 domande Swift più comuni: optionals, closures, ARC, protocolli, async/await e pattern avanzati.

Domande di colloquio Swift per sviluppatori iOS

I colloqui tecnici per posizioni iOS verificano a fondo le basi di Swift e i concetti avanzati. Queste 25 domande coprono gli argomenti più frequentemente richiesti dai responsabili delle assunzioni, dalle basi del linguaggio ai pattern di concurrency moderni.

Come utilizzare questa guida

Ogni domanda include una risposta dettagliata con esempi di codice. Le domande sono organizzate per difficoltà crescente, dai fondamenti ai concetti avanzati.

Fondamenti di Swift

1. Qual è la differenza tra let e var?

let dichiara una costante il cui valore non può essere modificato dopo l'inizializzazione, mentre var dichiara una variabile mutabile. Per i tipi di riferimento (classi), let impedisce la riassegnazione del riferimento ma non la modifica dell'oggetto stesso.

Constants.swiftswift
// let = constant, immutable value
let maximumAttempts = 3
// maximumAttempts = 5  // ❌ Compile error

// var = variable, mutable value
var currentAttempt = 0
currentAttempt += 1  // ✅ OK

// Careful with reference types
class User {
    var name: String
    init(name: String) { self.name = name }
}

let user = User(name: "Alice")
user.name = "Bob"  // ✅ OK - modifying object, not reference
// user = User(name: "Charlie")  // ❌ Error - reassignment forbidden

Best practice: utilizzare let come impostazione predefinita e passare a var solo quando la mutazione è necessaria. Questo rende il codice più prevedibile e più facile da comprendere.

2. Cosa sono gli Optionals in Swift?

Gli optionals rappresentano la possibile assenza di un valore. Un optional può contenere un valore del tipo specificato oppure nil. Swift utilizza gli optionals per garantire la sicurezza a tempo di compilazione, obbligando a gestire esplicitamente i casi in cui un valore potrebbe mancare.

Optionals.swiftswift
// Declaring an optional with ?
var username: String? = nil  // Can hold a String or nil

// Optional binding with if let (safe unwrapping)
if let name = username {
    print("Hello, \(name)")  // Only executes if username != nil
} else {
    print("Anonymous user")
}

// Guard let for early return
func greet(user: String?) {
    guard let name = user else {
        print("No user provided")
        return  // Early exit if nil
    }
    print("Hello, \(name)")  // name is guaranteed non-nil here
}

// Nil-coalescing operator (??) for default value
let displayName = username ?? "Anonymous"

// Force unwrapping (!) - DANGEROUS, avoid this
// let forced = username!  // Crashes if nil

3. Qual è la differenza tra struct e class?

Le struct sono tipi di valore (copiati all'assegnazione) mentre le class sono tipi di riferimento (condividono la stessa istanza). Questa distinzione fondamentale influisce sulle prestazioni, sulla gestione della memoria e sul comportamento del codice.

ValueVsReference.swiftswift
// Struct = value type (copy)
struct Point {
    var x: Int
    var y: Int
}

var p1 = Point(x: 10, y: 20)
var p2 = p1      // Independent copy
p2.x = 100       // Only modifies p2
print(p1.x)      // 10 - p1 unchanged

// Class = reference type (shared)
class Rectangle {
    var width: Int
    var height: Int
    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }
}

let r1 = Rectangle(width: 10, height: 20)
let r2 = r1      // Same shared instance
r2.width = 100   // Modifies the shared instance
print(r1.width)  // 100 - r1 also modified!

Quando usare quale:

  • Struct: dati semplici, valori immutabili, nessuna ereditarietà necessaria
  • Class: l'identità è importante, ereditarietà richiesta, comportamento condiviso

4. Come funziona il pattern matching con switch?

Lo switch di Swift è potente ed esaustivo: deve coprire tutti i casi possibili. Supporta il pattern matching su tipi, intervalli, tuple e condizioni aggiuntive con where.

PatternMatching.swiftswift
enum NetworkError: Error {
    case timeout
    case serverError(code: Int)
    case noConnection
}

func handleError(_ error: NetworkError) {
    switch error {
    case .timeout:
        print("Request timed out")

    case .serverError(let code) where code >= 500:
        print("Critical server error: \(code)")

    case .serverError(let code):
        print("Server error: \(code)")

    case .noConnection:
        print("No connection")
    }
    // No default needed: all cases covered
}

// Pattern matching on ranges and tuples
let point = (x: 5, y: 10)
switch point {
case (0, 0):
    print("Origin")
case (let x, 0):
    print("On X axis at \(x)")
case (0...10, 0...10):
    print("In the 10x10 square")
default:
    print("Elsewhere")
}

5. Le closures e la loro sintassi

Le closures sono blocchi di codice autonomi che catturano e conservano riferimenti alle variabili del contesto circostante. Corrispondono alle lambda o alle funzioni anonime in altri linguaggi.

Closures.swiftswift
// Full syntax
let add: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
    return a + b
}

// Shorthand syntax (inferred types, implicit return)
let multiply: (Int, Int) -> Int = { $0 * $1 }

// Trailing closure syntax
let numbers = [3, 1, 4, 1, 5]
let sorted = numbers.sorted { $0 > $1 }  // [5, 4, 3, 1, 1]

// Closure capturing a variable
func makeCounter() -> () -> Int {
    var count = 0  // Captured variable
    return {
        count += 1  // Closure "closes over" count
        return count
    }
}

let counter = makeCounter()
print(counter())  // 1
print(counter())  // 2 - count remembered between calls
Semantica di cattura

Le closures catturano le variabili per riferimento per impostazione predefinita. Per catturare per valore, si utilizza una capture list: { [count] in ... }.

Gestione della memoria e ARC

6. Come funziona ARC (Automatic Reference Counting)?

ARC gestisce automaticamente la memoria contando i riferimenti forti a ogni istanza di classe. Quando il conteggio scende a zero, l'istanza viene deallocata. A differenza della garbage collection, ARC è deterministico e prevedibile.

ARC.swiftswift
class Person {
    let name: String

    init(name: String) {
        self.name = name
        print("\(name) is initialized")
    }

    deinit {
        print("\(name) is deallocated")
    }
}

// Demonstrating lifecycle
var person1: Person? = Person(name: "Alice")  // refCount = 1
var person2 = person1                          // refCount = 2
person1 = nil                                  // refCount = 1 (not deallocated)
person2 = nil                                  // refCount = 0 → deinit called
// Output: "Alice is deallocated"

7. Cos'è un retain cycle e come si previene?

Un retain cycle si verifica quando due oggetti mantengono riferimenti forti l'uno verso l'altro, impedendone la deallocazione. Le parole chiave weak e unowned interrompono questi cicli.

RetainCycle.swiftswift
class Department {
    let name: String
    var manager: Employee?  // Strong reference

    init(name: String) { self.name = name }
    deinit { print("Department \(name) deallocated") }
}

class Employee {
    let name: String
    // weak prevents retain cycle - can become nil
    weak var department: Department?

    init(name: String) { self.name = name }
    deinit { print("Employee \(name) deallocated") }
}

// Without weak: retain cycle → memory leak
// With weak: proper deallocation
var dept: Department? = Department(name: "Engineering")
var emp: Employee? = Employee(name: "Bob")
dept?.manager = emp
emp?.department = dept

dept = nil  // ✅ Deallocated thanks to weak
emp = nil   // ✅ Deallocated

8. Qual è la differenza tra weak e unowned?

Entrambi interrompono i retain cycle, ma con garanzie diverse. weak è opzionale e diventa nil se l'oggetto referenziato viene deallocato. unowned presuppone che l'oggetto esista sempre e causa un crash se vi si accede dopo la deallocazione.

WeakVsUnowned.swiftswift
class Customer {
    let name: String
    var card: CreditCard?

    init(name: String) { self.name = name }
}

class CreditCard {
    let number: String
    // unowned because a card always exists with its customer
    unowned let customer: Customer

    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }
}

// Card cannot exist without customer
let customer = Customer(name: "Alice")
customer.card = CreditCard(number: "1234", customer: customer)
// If customer is deallocated, accessing card.customer would crash

Regola generale: usare weak come impostazione predefinita. Usare unowned solo quando la durata di vita dell'oggetto referenziato è garantita essere uguale o superiore.

Pronto a superare i tuoi colloqui su iOS?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Protocolli e Generics

9. I protocolli in Swift

I protocolli definiscono un contratto (proprietà e metodi richiesti) che i tipi conformi devono implementare. Abilitano il polimorfismo e sono alla base della programmazione orientata ai protocolli (POP) in Swift.

Protocols.swiftswift
// Protocol definition
protocol Drawable {
    var color: String { get set }  // Required property (read/write)
    func draw()                     // Required method
}

// Protocol extension with default implementation
extension Drawable {
    func draw() {
        print("Default drawing in \(color)")
    }
}

// Protocol conformance
struct Circle: Drawable {
    var color: String
    var radius: Double

    // draw() inherits default implementation
}

struct Square: Drawable {
    var color: String
    var side: Double

    // Override default implementation
    func draw() {
        print("Square \(color) with side \(side)")
    }
}

// Polymorphic usage
let shapes: [Drawable] = [Circle(color: "red", radius: 5), Square(color: "blue", side: 10)]
shapes.forEach { $0.draw() }

10. Cos'è un Associated Type?

Gli associated types permettono ai protocolli di definire tipi generici che saranno specificati dai tipi conformi. Questo meccanismo rende protocolli come Collection estremamente flessibili.

AssociatedTypes.swiftswift
// Protocol with associated type
protocol Container {
    associatedtype Item  // Type defined by conformant
    var items: [Item] { get set }
    mutating func add(_ item: Item)
    func count() -> Int
}

// Implementation with Item = String
struct StringBox: Container {
    typealias Item = String  // Optional, Swift can infer
    var items: [String] = []

    mutating func add(_ item: String) {
        items.append(item)
    }

    func count() -> Int { items.count }
}

// Implementation with Item = Int
struct IntStack: Container {
    var items: [Int] = []

    mutating func add(_ item: Int) {
        items.append(item)
    }

    func count() -> Int { items.count }
}

11. Come funzionano i Generics?

I generics permettono di scrivere codice flessibile e riutilizzabile che funziona con qualsiasi tipo. Evitano la duplicazione del codice mantenendo la sicurezza dei tipi a tempo di compilazione.

Generics.swiftswift
// Generic function
func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

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

// Generic struct
struct Queue<Element> {
    private var elements: [Element] = []

    mutating func enqueue(_ element: Element) {
        elements.append(element)
    }

    mutating func dequeue() -> Element? {
        guard !elements.isEmpty else { return nil }
        return elements.removeFirst()
    }
}

var intQueue = Queue<Int>()
intQueue.enqueue(1)
intQueue.enqueue(2)
print(intQueue.dequeue())  // Optional(1)

12. Il protocollo Codable

Codable (alias per Encodable & Decodable) consente la serializzazione automatica dei tipi Swift in e da formati come JSON. Il compilatore genera l'implementazione se tutte le proprietà sono a loro volta Codable.

Codable.swiftswift
struct User: Codable {
    let id: Int
    let name: String
    let email: String
    let createdAt: Date

    // CodingKeys to map different JSON names
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case email
        case createdAt = "created_at"  // snake_case → camelCase
    }
}

// JSON decoding
let json = """
{
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com",
    "created_at": "2026-01-15T10:30:00Z"
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

do {
    let user = try decoder.decode(User.self, from: json)
    print(user.name)  // "Alice"
} catch {
    print("Decoding error: \(error)")
}

// Encoding to JSON
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(user)

Concurrency e async/await

13. Come funziona async/await in Swift?

async/await semplifica il codice asincrono permettendo di scrivere operazioni non bloccanti in modo sequenziale. Una funzione async può essere sospesa senza bloccare il thread, consentendo l'esecuzione di altri task.

AsyncAwait.swiftswift
// Asynchronous function
func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!

    // await suspends execution until response
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse,
          httpResponse.statusCode == 200 else {
        throw NetworkError.invalidResponse
    }

    return try JSONDecoder().decode(User.self, from: data)
}

// Calling from async context
func loadUserProfile() async {
    do {
        let user = try await fetchUser(id: 42)
        print("User: \(user.name)")
    } catch {
        print("Error: \(error)")
    }
}

// Calling from synchronous context with Task
func buttonTapped() {
    Task {
        await loadUserProfile()
    }
}

14. Cos'è un Actor?

Gli actor sono tipi di riferimento che proteggono il proprio stato interno dall'accesso concorrente. Garantiscono che solo un task alla volta possa accedere alle loro proprietà mutabili, eliminando le data race.

Actors.swiftswift
// Actor protects its state automatically
actor BankAccount {
    private var balance: Double = 0

    func deposit(_ amount: Double) {
        balance += amount  // Automatic thread-safe access
    }

    func withdraw(_ amount: Double) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }

    func getBalance() -> Double {
        return balance
    }
}

// Usage - await required to access actor
let account = BankAccount()

Task {
    await account.deposit(100)
    let success = await account.withdraw(30)
    let balance = await account.getBalance()
    print("Balance: \(balance)")  // 70
}

15. Task e TaskGroup

Task crea un'unità di lavoro asincrono. TaskGroup permette di eseguire più task in parallelo e raccogliere i risultati.

TaskGroup.swiftswift
// Simple Task
let task = Task {
    return await fetchUser(id: 1)
}
let user = try await task.value

// TaskGroup for parallelization
func fetchMultipleUsers(ids: [Int]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        // Launch all requests in parallel
        for id in ids {
            group.addTask {
                try await fetchUser(id: id)
            }
        }

        // Collect results as they complete
        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

// All 3 requests run in parallel
let users = try await fetchMultipleUsers(ids: [1, 2, 3])

16. Come funziona @MainActor?

@MainActor garantisce che il codice venga eseguito sul thread principale. Questo è essenziale per gli aggiornamenti dell'interfaccia utente, che devono sempre avvenire sul thread principale.

MainActor.swiftswift
// UI class annotated with @MainActor
@MainActor
class UserViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var error: String?

    func loadUser() async {
        isLoading = true  // ✅ On main thread automatically

        do {
            // Network operation on background thread
            user = try await fetchUser(id: 42)
        } catch {
            self.error = error.localizedDescription
        }

        isLoading = false  // ✅ Automatic return to main thread
    }
}

// Or for a specific function
func updateUI() async {
    await MainActor.run {
        // This block runs on main thread
        label.text = "Updated"
    }
}

Pattern e architettura

17. Il pattern Delegate

Il pattern Delegate permette a un oggetto di delegare determinate responsabilità a un altro oggetto. È onnipresente in UIKit (UITableViewDelegate, UITextFieldDelegate ecc.).

DelegatePattern.swiftswift
// 1. Define the delegate protocol
protocol DownloadManagerDelegate: AnyObject {
    func downloadDidStart()
    func downloadDidProgress(_ progress: Double)
    func downloadDidComplete(data: Data)
    func downloadDidFail(error: Error)
}

// 2. Class that uses the delegate
class DownloadManager {
    // weak to avoid retain cycles
    weak var delegate: DownloadManagerDelegate?

    func startDownload(url: URL) {
        delegate?.downloadDidStart()

        // Simulated download
        Task {
            for progress in stride(from: 0.0, to: 1.0, by: 0.1) {
                try await Task.sleep(nanoseconds: 100_000_000)
                delegate?.downloadDidProgress(progress)
            }
            delegate?.downloadDidComplete(data: Data())
        }
    }
}

// 3. Class that implements the delegate
class ViewController: UIViewController, DownloadManagerDelegate {
    let manager = DownloadManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        manager.delegate = self  // Register as delegate
    }

    func downloadDidStart() { print("Started") }
    func downloadDidProgress(_ progress: Double) { print("\(progress * 100)%") }
    func downloadDidComplete(data: Data) { print("Complete") }
    func downloadDidFail(error: Error) { print("Error: \(error)") }
}

18. Cos'è il pattern MVVM?

MVVM (Model-View-ViewModel) separa la logica di presentazione dalla view. Il ViewModel espone dati osservabili che la View visualizza, senza conoscere i dettagli della View.

MVVM.swiftswift
// Model
struct Article: Identifiable {
    let id: UUID
    let title: String
    let content: String
    let publishedAt: Date
}

// ViewModel
@MainActor
class ArticleListViewModel: ObservableObject {
    @Published private(set) var articles: [Article] = []
    @Published private(set) var isLoading = false
    @Published var errorMessage: String?

    private let repository: ArticleRepository

    init(repository: ArticleRepository = .shared) {
        self.repository = repository
    }

    func loadArticles() async {
        isLoading = true
        errorMessage = nil

        do {
            articles = try await repository.fetchArticles()
        } catch {
            errorMessage = "Failed to load articles"
        }

        isLoading = false
    }
}

// View (SwiftUI)
struct ArticleListView: View {
    @StateObject private var viewModel = ArticleListViewModel()

    var body: some View {
        Group {
            if viewModel.isLoading {
                ProgressView()
            } else {
                List(viewModel.articles) { article in
                    Text(article.title)
                }
            }
        }
        .task { await viewModel.loadArticles() }
    }
}

Pronto a superare i tuoi colloqui su iOS?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

19. La Dependency Injection

La dependency injection consiste nel fornire le dipendenze di un oggetto dall'esterno, anziché crearle internamente. Questo migliora la testabilità e il disaccoppiamento.

DependencyInjection.swiftswift
// Protocol for abstraction
protocol UserServiceProtocol {
    func fetchUser(id: Int) async throws -> User
}

// Real implementation
class UserService: UserServiceProtocol {
    func fetchUser(id: Int) async throws -> User {
        // Real API call
        let url = URL(string: "https://api.example.com/users/\(id)")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(User.self, from: data)
    }
}

// ViewModel with injection
class ProfileViewModel: ObservableObject {
    private let userService: UserServiceProtocol

    // Constructor injection
    init(userService: UserServiceProtocol = UserService()) {
        self.userService = userService
    }

    func loadProfile(id: Int) async {
        // Uses injected service
    }
}

// Mock for testing
class MockUserService: UserServiceProtocol {
    func fetchUser(id: Int) async throws -> User {
        return User(id: id, name: "Test User", email: "test@test.com")
    }
}

// In tests
let viewModel = ProfileViewModel(userService: MockUserService())

20. Come implementare il pattern Singleton?

Il Singleton assicura che una classe abbia una sola istanza accessibile globalmente. In Swift si utilizza una proprietà statica con un inizializzatore privato.

Singleton.swiftswift
class NetworkManager {
    // Single globally accessible instance
    static let shared = NetworkManager()

    // Private initializer prevents creating other instances
    private init() {
        // Initial configuration
    }

    private let session = URLSession.shared

    func request<T: Decodable>(_ url: URL) async throws -> T {
        let (data, _) = try await session.data(from: url)
        return try JSONDecoder().decode(T.self, from: data)
    }
}

// Usage
let user: User = try await NetworkManager.shared.request(url)
Attenzione ai Singleton

I singleton creano stato globale che complica il testing e il disaccoppiamento. Dove possibile, è preferibile la dependency injection.

Concetti avanzati

21. @escaping per le closures

Una closure è @escaping quando può essere chiamata dopo il ritorno della funzione che la riceve. Questo è comune nei callback asincroni e nell'archiviazione di closures.

Escaping.swiftswift
class DataLoader {
    // Storage of completion handlers
    private var completionHandlers: [() -> Void] = []

    // @escaping because closure is stored and called later
    func loadData(completion: @escaping () -> Void) {
        completionHandlers.append(completion)

        DispatchQueue.global().async {
            // Async work...
            Thread.sleep(forTimeInterval: 1)

            DispatchQueue.main.async {
                // Closure called after loadData returns
                completion()
            }
        }
    }

    // Non-escaping by default: closure called before return
    func transform(data: Data, using transformer: (Data) -> String) -> String {
        return transformer(data)  // Called immediately
    }
}

// With @escaping, watch for retain cycles
class ViewController {
    var loader = DataLoader()
    var data: String?

    func load() {
        loader.loadData { [weak self] in  // [weak self] avoids retain cycle
            self?.data = "Loaded"
        }
    }
}

22. Cos'è @propertyWrapper?

I property wrapper incapsulano la logica di archiviazione e accesso di una proprietà. Permettono di riutilizzare pattern come validazione, logging o persistenza.

PropertyWrapper.swiftswift
// Property wrapper for positive values only
@propertyWrapper
struct Positive {
    private var value: Int = 0

    var wrappedValue: Int {
        get { value }
        set { value = max(0, newValue) }  // Force positive
    }

    // Projected value accessible via $
    var projectedValue: Bool {
        value > 0
    }

    init(wrappedValue: Int) {
        self.wrappedValue = wrappedValue
    }
}

// Usage
struct Player {
    @Positive var score: Int = 0
    @Positive var health: Int = 100
}

var player = Player()
player.score = -50   // Becomes 0 (clamped)
print(player.score)  // 0
print(player.$score) // false (projectedValue)

player.score = 100
print(player.$score) // true

23. I Result Builder

I result builder permettono di costruire valori complessi con sintassi dichiarativa. Questo meccanismo è alla base della sintassi DSL di SwiftUI.

ResultBuilder.swiftswift
// Result builder definition
@resultBuilder
struct StringBuilder {
    static func buildBlock(_ components: String...) -> String {
        components.joined(separator: " ")
    }

    static func buildOptional(_ component: String?) -> String {
        component ?? ""
    }

    static func buildEither(first component: String) -> String {
        component
    }

    static func buildEither(second component: String) -> String {
        component
    }
}

// Function using the builder
func buildGreeting(@StringBuilder _ content: () -> String) -> String {
    content()
}

// Usage with declarative syntax
let greeting = buildGreeting {
    "Hello"
    "and"
    "welcome"
    if Bool.random() {
        "!"
    } else {
        "."
    }
}
print(greeting)  // "Hello and welcome !" or "Hello and welcome ."

24. Come funzionano some e gli Opaque Types?

some dichiara un tipo opaco: il tipo esatto è noto al compilatore ma nascosto al chiamante. Questo è essenziale per i protocolli con associated types e abilita ottimizzazioni.

OpaqueTypes.swiftswift
// Without some: error because Collection has associated type
// func makeCollection() -> Collection { ... }  // ❌ Error

// With some: exact type is hidden but consistent
func makeArray() -> some Collection {
    return [1, 2, 3]  // Always returns the same concrete type
}

// Used in SwiftUI for body
struct ContentView: View {
    var body: some View {  // Exact type inferred but hidden
        VStack {
            Text("Hello")
            Text("World")
        }
    }
}

// Difference with any (existential)
func processAny(_ collection: any Collection) {
    // Can accept different types, runtime overhead
}

func processSome(_ collection: some Collection) {
    // Type fixed at compile time, no overhead
}

25. Le macro in Swift

Le macro (Swift 5.9+) generano codice a tempo di compilazione. Riducono il codice boilerplate mantenendo la sicurezza dei tipi e la possibilità di debug.

Macros.swiftswift
// Freestanding macro: generates an expression
let (x, y) = #unwrap(optionalX, optionalY)
// Expands to: guard let x = optionalX, let y = optionalY else { ... }

// Attached macro: modifies a declaration
@Observable  // Macro that generates Observable boilerplate
class UserModel {
    var name: String = ""
    var email: String = ""
}
// Automatically generates @ObservationTracked, ObservationRegistrar, etc.

// Macro for Codable with customization
@Codable
struct Product {
    let id: Int
    @CodableKey("product_name") let name: String  // Renames JSON key
    @CodableIgnored var cache: Data?              // Excludes from coding
}

// Creating a custom macro
@attached(member, names: named(init))
public macro AutoInit() = #externalMacro(module: "MyMacros", type: "AutoInitMacro")

@AutoInit
struct Point {
    let x: Int
    let y: Int
    // init(x: Int, y: Int) generated automatically
}
Debug delle macro

In Xcode, un clic destro su una macro e "Expand Macro" mostra il codice generato. Utile per comprendere e fare il debug.

Conclusione

Queste 25 domande coprono i fondamenti che ogni sviluppatore Swift deve padroneggiare per superare i colloqui iOS. Dalla gestione della memoria con ARC ai pattern di concurrency moderni, ogni concetto si integra nell'ecosistema Swift.

Checklist di ripasso

  • Padroneggiare gli optionals e i vari metodi di unwrapping
  • Comprendere la differenza tra tipi di valore e tipi di riferimento
  • Saper identificare e risolvere i retain cycle con weak/unowned
  • Utilizzare async/await e actor per la concurrency
  • Implementare i pattern Delegate, MVVM e Dependency Injection
  • Conoscere protocolli, generics e Codable
  • Comprendere i concetti avanzati: property wrapper, result builder, macro

Risorse aggiuntive

Per un approfondimento, la documentazione ufficiale di Swift rimane il riferimento principale. La pratica regolare con progetti personali ed esercizi di programmazione aiuta a consolidare questi concetti.

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Tag

#swift
#ios
#interview
#career
#apple

Condividi

Articoli correlati