Las 25 preguntas de entrevista Swift más importantes para desarrolladores iOS

Preparación para entrevistas técnicas iOS con las 25 preguntas Swift más frecuentes: optionals, closures, ARC, protocolos, async/await y patrones avanzados.

Preguntas de entrevista Swift para desarrolladores iOS

Las entrevistas técnicas para iOS evalúan a fondo los fundamentos y conceptos avanzados de Swift. Estas 25 preguntas cubren los temas más solicitados por los responsables de contratación, desde los conceptos básicos del lenguaje hasta los patrones modernos de concurrencia.

Cómo usar esta guía

Cada pregunta incluye una respuesta detallada con ejemplos de código. Las preguntas están organizadas por nivel de dificultad creciente, desde fundamentos hasta conceptos avanzados.

Fundamentos de Swift

1. ¿Cuál es la diferencia entre let y var?

let declara una constante cuyo valor no puede cambiar después de la inicialización, mientras que var declara una variable mutable. Para tipos por referencia (clases), let impide reasignar la referencia, pero no modificar el objeto en sí.

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

Buena práctica: se recomienda usar let por defecto y solo cambiar a var cuando la mutación sea necesaria. Esto hace que el código sea más predecible y fácil de razonar.

2. ¿Qué son los optionals en Swift?

Los optionals representan la posible ausencia de un valor. Un optional puede contener un valor del tipo especificado o nil. Swift utiliza optionals para garantizar seguridad en tiempo de compilación, forzando el manejo explícito de casos donde un valor podría no existir.

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. ¿Cuál es la diferencia entre struct y class?

struct son tipos por valor (se copian al asignar) mientras que class son tipos por referencia (comparten la misma instancia). Esta distinción fundamental impacta el rendimiento, la gestión de memoria y el comportamiento del código.

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!

Cuándo usar cada uno:

  • Struct: datos simples, valores inmutables, sin necesidad de herencia
  • Class: cuando la identidad importa, se requiere herencia o comportamiento compartido

4. ¿Cómo funciona el pattern matching con switch?

El switch de Swift es exhaustivo: debe cubrir todos los casos posibles. Soporta pattern matching sobre tipos, rangos, tuplas y condiciones adicionales 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. ¿Qué son los closures y cuál es su sintaxis?

Los closures son bloques de código autónomos que capturan y almacenan referencias a variables de su contexto circundante. Son el equivalente a las lambdas o funciones anónimas en otros lenguajes.

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
Semántica de captura

Los closures capturan variables por referencia de forma predeterminada. Para capturar por valor, se utiliza una lista de captura: { [count] in ... }.

Gestión de memoria y ARC

6. ¿Cómo funciona ARC (Automatic Reference Counting)?

ARC gestiona la memoria automáticamente contando las referencias fuertes a cada instancia de clase. Cuando el conteo llega a cero, la instancia se desaloca. A diferencia de la recolección de basura, ARC es determinista y predecible.

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. ¿Qué es un ciclo de retención y cómo se previene?

Un ciclo de retención ocurre cuando dos objetos mantienen referencias fuertes entre sí, impidiendo su desalocación. Las palabras clave weak y unowned rompen estos ciclos.

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. ¿Cuál es la diferencia entre weak y unowned?

Ambos rompen ciclos de retención, pero con garantías diferentes. weak es opcional y se convierte en nil si el objeto referenciado es desalocado. unowned asume que el objeto siempre existe y provoca un crash si se accede después de la desalocación.

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

Regla: se recomienda usar weak por defecto. Solo se debe usar unowned cuando se garantiza que el tiempo de vida del objeto referenciado es igual o mayor.

¿Listo para aprobar tus entrevistas de iOS?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Protocolos y genéricos

9. ¿Qué son los protocolos en Swift?

Los protocolos definen un contrato (propiedades y métodos requeridos) que los tipos conformantes deben implementar. Permiten el polimorfismo y son la base de la Programación Orientada a Protocolos (POP) en 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. ¿Qué es un tipo asociado (associated type)?

Los tipos asociados permiten que los protocolos definan tipos genéricos que serán especificados por los tipos conformantes. Este mecanismo es lo que hace que protocolos como Collection sean tan flexibles.

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. ¿Cómo funcionan los genéricos?

Los genéricos permiten escribir código flexible y reutilizable que funciona con cualquier tipo. Evitan la duplicación de código manteniendo la seguridad de tipos en tiempo de compilación.

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. ¿Qué es el protocolo Codable?

Codable (alias de Encodable & Decodable) permite la serialización automática de tipos Swift hacia y desde formatos como JSON. El compilador genera la implementación si todas las propiedades son 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)

Concurrencia y async/await

13. ¿Cómo funciona async/await en Swift?

async/await simplifica el código asíncrono permitiendo escribir operaciones no bloqueantes de forma secuencial. Una función async puede suspenderse sin bloquear el hilo, permitiendo que otras tareas se ejecuten.

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. ¿Qué es un Actor?

Los actores son tipos por referencia que protegen su estado interno del acceso concurrente. Garantizan que solo una tarea a la vez pueda acceder a sus propiedades mutables, eliminando las condiciones de carrera (data races).

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. ¿Qué son Task y TaskGroup?

Task crea una unidad de trabajo asíncrono. TaskGroup permite ejecutar múltiples tareas en paralelo y recolectar sus resultados.

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. ¿Cómo funciona @MainActor?

@MainActor garantiza que el código se ejecute en el hilo principal. Esto es esencial para las actualizaciones de UI, que siempre deben ejecutarse en el hilo principal.

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"
    }
}

Patrones y arquitectura

17. ¿Qué es el patrón Delegate?

El patrón Delegate permite que un objeto delegue ciertas responsabilidades a otro objeto. Es omnipresente en UIKit (UITableViewDelegate, UITextFieldDelegate, etc.).

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. ¿Qué es el patrón MVVM?

MVVM (Model-View-ViewModel) separa la lógica de presentación de la vista. El ViewModel expone datos observables que la Vista muestra, sin conocer los detalles de la Vista.

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() }
    }
}

¿Listo para aprobar tus entrevistas de iOS?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

19. ¿Qué es la inyección de dependencias?

La inyección de dependencias consiste en proporcionar las dependencias de un objeto desde el exterior en lugar de crearlas internamente. Esto mejora la testabilidad y el desacoplamiento.

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. ¿Cómo se implementa el patrón Singleton?

Singleton asegura que una clase tenga una única instancia accesible globalmente. En Swift, se utiliza una propiedad estática con un inicializador privado.

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)
Precaución con Singletons

Los Singletons crean estado global que complica las pruebas y el desacoplamiento. Se recomienda preferir la inyección de dependencias cuando sea posible.

Conceptos avanzados

21. ¿Qué significa @escaping en los closures?

Un closure es @escaping cuando puede ser llamado después de que la función que lo recibe retorne. Esto es común para callbacks asíncronos y almacenamiento de 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. ¿Qué es @propertyWrapper?

Los property wrappers encapsulan la lógica de almacenamiento y acceso de una propiedad. Permiten reutilizar patrones como validación, logging o persistencia.

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. ¿Qué son los result builders?

Los result builders permiten construir valores complejos con sintaxis declarativa. Este es el mecanismo detrás de la sintaxis DSL de 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. ¿Cómo funcionan some y los tipos opacos?

some declara un tipo opaco: el tipo exacto es conocido por el compilador pero está oculto para quien llama. Esto es esencial para protocolos con tipos asociados y permite optimizaciones.

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. ¿Qué son las macros en Swift?

Las macros (Swift 5.9+) generan código en tiempo de compilación. Reducen el código repetitivo manteniendo la seguridad de tipos y la capacidad de depuración.

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
}
Depuración de macros

En Xcode, se puede hacer clic derecho sobre una macro y seleccionar "Expand Macro" para ver el código generado. Resulta útil para comprender y depurar.

Conclusión

Estas 25 preguntas cubren los fundamentos que todo desarrollador Swift debe dominar para tener éxito en entrevistas iOS. Desde la gestión de memoria con ARC hasta los patrones modernos de concurrencia, cada concepto se integra en el ecosistema Swift.

Lista de verificación

  • Dominar optionals y sus diversos métodos de unwrapping
  • Comprender la diferencia entre tipos por valor y tipos por referencia
  • Saber identificar y resolver ciclos de retención con weak/unowned
  • Usar async/await y actores para la concurrencia
  • Implementar los patrones Delegate, MVVM e Inyección de Dependencias
  • Conocer protocolos, genéricos y Codable
  • Entender conceptos avanzados: property wrappers, result builders, macros

Recursos adicionales

Para un aprendizaje más profundo, la documentación oficial de Swift sigue siendo la referencia principal. La práctica regular con proyectos personales y ejercicios de codificación ayuda a consolidar estos conceptos.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Etiquetas

#swift
#ios
#interview
#career
#apple

Compartir

Artículos relacionados