Swift Macros: Ejemplos prácticos de metaprogramación
Guía completa sobre Swift Macros: creación de macros freestanding y attached incluyendo body macros de Swift 6, manipulación del AST con swift-syntax y ejemplos prácticos para eliminar código repetitivo.

Swift Macros transforman la forma en que se escribe y mantiene el código. Introducidas con Swift 5.9 y ampliadas significativamente en Swift 6, las macros permiten generar código en tiempo de compilación sin renunciar a la seguridad de tipos estática. A diferencia de las macros del preprocesador de C, las Swift Macros son seguras en tipos, están integradas en el compilador y cuentan con soporte completo en las herramientas de desarrollo, incluida la función "Expand Macro" de Xcode.
Esta guía cubre la creación de Swift Macros desde los conceptos fundamentales hasta implementaciones avanzadas, incluyendo las nuevas body macros introducidas en Swift 6 (SE-0415). Todos los ejemplos usan Swift 6 y swift-syntax 600.0.0+.
Comprender los tipos de Swift Macros
Swift ofrece dos categorías principales de macros, cada una con casos de uso distintos. Las macros freestanding actúan de forma autónoma como expresiones o declaraciones, mientras que las macros attached se asocian a declaraciones existentes para modificarlas o enriquecerlas.
Macros freestanding: expresión y declaración
Las macros freestanding empiezan por el símbolo # y pueden devolver un valor (expresión) o crear nuevas declaraciones. A continuación, un ejemplo concreto de macro de expresión:
// Freestanding expression macro - generates a value
let buildInfo = #buildDate
// Expansion → "2026-08-23 10:30:45"
// Freestanding macro with arguments
let message = #stringify(1 + 2)
// Expansion → "1 + 2 = 3"
// Freestanding declaration macro - creates declarations
#makeCase("success", "failure", "pending")
// Expansion →
// case success
// case failure
// case pendingLa diferencia esencial entre expresión y declaración reside en el resultado: una expresión produce un valor, mientras que una declaración produce código estructural (tipos, funciones, variables).
Macros attached: los seis roles
Las macros attached empiezan por @ y se colocan delante de una declaración. Swift 6 define seis roles distintos para estas macros, incluyendo el nuevo rol body para implementación de funciones:
// @attached(peer) - adds declarations at the same level
@AddAsync
func fetchUser(id: Int) -> User { ... }
// Expansion → adds func fetchUserAsync(id: Int) async -> User
// @attached(accessor) - adds getters/setters
@UserDefault("theme")
var currentTheme: String
// Expansion → adds get { UserDefaults.standard.string(...) }
// @attached(member) - adds members to a type
@AutoEquatable
struct Point {
var x: Int
var y: Int
}
// Expansion → adds static func == (lhs: Point, rhs: Point) -> Bool
// @attached(memberAttribute) - applies attributes to members
@CodableKeys
struct Config {
var apiUrl: String
var timeout: Int
}
// Expansion → adds @CodingKey("api_url") before apiUrl
// @attached(extension) - adds protocol conformances
@Hashable
struct User {
var id: Int
var name: String
}
// Expansion → adds extension User: Hashable { ... }
// @attached(body) - NEW in Swift 6: synthesizes function bodies
@Remote
func fetchData(endpoint: String) -> Data
// Expansion → generates the entire function implementationEstos roles pueden combinarse para crear macros potentes capaces de transformar el código en varias dimensiones a la vez.
Swift 6 Body Macros: SE-0415
Swift 6 introdujo las function body macros mediante SE-0415, permitiendo que las macros sinteticen o reemplacen implementaciones de funciones. Esto cubre casos de uso que ningún rol previo podía manejar.
Protocolo BodyMacro
El protocolo BodyMacro permite el reemplazo completo del cuerpo de una función:
import SwiftSyntax
import SwiftSyntaxMacros
public protocol BodyMacro: AttachedMacro {
static func expansion(
of node: AttributeSyntax,
providingBodyFor declaration: some DeclSyntaxProtocol &
WithOptionalCodeBlockSyntax,
in context: some MacroExpansionContext
) throws -> [CodeBlockItemSyntax]
}La macro recibe la declaración original y devuelve elementos de bloque de código que se convierten en el cuerpo de la función. Cualquier cuerpo existente se reemplaza.
Implementar una macro de llamada a procedimiento remoto
Las body macros son particularmente útiles para generar código de capa de red a partir de firmas de función:
import SwiftSyntax
import SwiftSyntaxMacros
public struct RemoteMacro: BodyMacro {
public static func expansion(
of node: AttributeSyntax,
providingBodyFor declaration: some DeclSyntaxProtocol &
WithOptionalCodeBlockSyntax,
in context: some MacroExpansionContext
) throws -> [CodeBlockItemSyntax] {
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
throw MacroError.invalidSyntax("@Remote requires a function")
}
let functionName = funcDecl.name.text
let parameters = funcDecl.signature.parameterClause.parameters
// Generate parameter encoding
let paramDict = parameters.map { param in
let name = param.firstName.text
return "\"\(name)\": \(name)"
}.joined(separator: ", ")
// Generate the RPC body
return [
"""
let params: [String: Any] = [\(raw: paramDict)]
return try await RPCClient.shared.call(
method: "\(raw: functionName)",
params: params
)
"""
]
}
}Declaración y uso
/// Generates a remote procedure call implementation
@attached(body)
public macro Remote() = #externalMacro(
module: "MyMacrosPlugin",
type: "RemoteMacro"
)class APIService {
@Remote
func createUser(name: String, email: String) async throws -> User
@Remote
func deleteUser(id: UUID) async throws -> Bool
// Each function body is generated at compile time:
// func createUser(name: String, email: String) async throws -> User {
// let params: [String: Any] = ["name": name, "email": email]
// return try await RPCClient.shared.call(
// method: "createUser",
// params: params
// )
// }
}Solo se puede aplicar una body macro a una función dada. Para inyección de preámbulo componible (logging, tracing), el protocolo PreambleMacro está bajo consideración en futuras versiones de Swift.
Configuración del proyecto para crear macros
La creación de Swift Macros requiere un Swift Package con una estructura concreta. El paquete depende de swift-syntax, la biblioteca oficial para manipular código Swift como un árbol de sintaxis abstracta (AST).
Estructura de Package.swift
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "MyMacros",
platforms: [.macOS(.v10_15), .iOS(.v13)],
products: [
// Library exposing macros to the main project
.library(
name: "MyMacros",
targets: ["MyMacros"]
),
// Executable for testing macros
.executable(
name: "MyMacrosClient",
targets: ["MyMacrosClient"]
)
],
dependencies: [
// Required dependency for macros - use 600.0.0+ for Swift 6
.package(
url: "https://github.com/swiftlang/swift-syntax.git",
from: "600.0.0"
)
],
targets: [
// Compiler plugin containing implementation
.macro(
name: "MyMacrosPlugin",
dependencies: [
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
// Target exposing macro declarations
.target(
name: "MyMacros",
dependencies: ["MyMacrosPlugin"]
),
// Test client
.executableTarget(
name: "MyMacrosClient",
dependencies: ["MyMacros"]
),
// Unit tests
.testTarget(
name: "MyMacrosTests",
dependencies: [
"MyMacrosPlugin",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax")
]
)
]
)Esta configuración separa con claridad las declaraciones de las macros (lo que ve el código cliente) de su implementación (que se ejecuta en tiempo de compilación).
Se necesitan al menos tres archivos: MyMacros.swift para las declaraciones, MyMacrosPlugin.swift para las implementaciones y MyMacrosTests.swift para los tests. Esta separación facilita el mantenimiento.
Crear una macro de expresión
Las macros de expresión generan un valor utilizable directamente en el código. A continuación se muestra cómo crear una macro #unwrap que desempaqueta un opcional con un mensaje de error personalizado que incluye el nombre de la variable. Para más información sobre patrones de manejo de errores en Swift, consultar la guía sobre concurrencia estructurada en Swift.
Declaración de la macro
import Foundation
/// Macro that unwraps an optional with an explicit error message
/// Usage: let value = #unwrap(optionalValue)
/// Expansion: guard let optionalValue else { fatalError("...") }; optionalValue
@freestanding(expression)
public macro unwrap<T>(_ value: T?) -> T = #externalMacro(
module: "MyMacrosPlugin",
type: "UnwrapMacro"
)La firma declara que la macro recibe un opcional y devuelve el valor no opcional. #externalMacro apunta a la implementación dentro del plugin.
Implementación con swift-syntax
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftCompilerPlugin
public struct UnwrapMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
// Get the first argument passed to the macro
guard let argument = node.argumentList.first?.expression else {
throw MacroError.missingArgument
}
// Extract the variable name for the error message
let variableName = argument.description.trimmingCharacters(
in: .whitespacesAndNewlines
)
// Generate the expansion code
// Uses an immediately-invoked closure to encapsulate the guard
return """
{
guard let value = \(argument) else {
fatalError("Failed to unwrap '\\(\(literal: variableName))' - value was nil")
}
return value
}()
"""
}
}
// Custom errors for macros
enum MacroError: Error, CustomStringConvertible {
case missingArgument
case invalidSyntax(String)
var description: String {
switch self {
case .missingArgument:
return "The macro requires an argument"
case .invalidSyntax(let message):
return "Invalid syntax: \(message)"
}
}
}El método expansion recibe el nodo del AST que representa la llamada a la macro junto con el contexto de compilación. Devuelve un ExprSyntax que contiene el código generado.
Registro del plugin
import SwiftCompilerPlugin
import SwiftSyntaxMacros
@main
struct MyMacrosPlugin: CompilerPlugin {
// List all macros provided by this plugin
let providingMacros: [Macro.Type] = [
UnwrapMacro.self,
RemoteMacro.self,
AutoInitMacro.self,
// Add other macros here
]
}Este punto de entrada informa al compilador sobre las macros disponibles en el plugin.
¿Listo para aprobar tus entrevistas de iOS?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Crear una macro attached de tipo member
Las macros member añaden miembros (propiedades, métodos, tipos anidados) a un tipo existente. Aquí se muestra una macro @AutoInit que genera automáticamente un inicializador con todas las propiedades almacenadas.
Declaración e implementación completas
/// Automatically generates an initializer with all stored properties
@attached(member, names: named(init))
public macro AutoInit() = #externalMacro(
module: "MyMacrosPlugin",
type: "AutoInitMacro"
)import SwiftSyntax
import SwiftSyntaxMacros
public struct AutoInitMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Verify the macro is applied to a struct or class
guard declaration.is(StructDeclSyntax.self) ||
declaration.is(ClassDeclSyntax.self) else {
throw MacroError.invalidSyntax(
"@AutoInit can only be applied to structs and classes"
)
}
// Collect stored properties
let properties = declaration.memberBlock.members
.compactMap { $0.decl.as(VariableDeclSyntax.self) }
.filter { isStoredProperty($0) }
// Generate initializer parameters
let parameters = properties.compactMap { property -> String? in
guard let binding = property.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self),
let type = binding.typeAnnotation?.type else {
return nil
}
let name = identifier.identifier.text
let typeName = type.description.trimmingCharacters(in: .whitespaces)
// Check if the property has a default value
if binding.initializer != nil {
return "\(name): \(typeName) = \(binding.initializer!.value)"
}
return "\(name): \(typeName)"
}
// Generate assignments in the init body
let assignments = properties.compactMap { property -> String? in
guard let binding = property.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self) else {
return nil
}
let name = identifier.identifier.text
return "self.\(name) = \(name)"
}
// Build the complete initializer
let initDecl: DeclSyntax = """
public init(\(raw: parameters.joined(separator: ", "))) {
\(raw: assignments.joined(separator: "\n "))
}
"""
return [initDecl]
}
// Check if a variable is a stored property (not computed)
private static func isStoredProperty(_ variable: VariableDeclSyntax) -> Bool {
guard let binding = variable.bindings.first else { return false }
// A computed property has an accessor block with get/set
if let accessor = binding.accessorBlock {
// If it's a block with explicit accessors, it's computed
if accessor.accessors.is(AccessorDeclListSyntax.self) {
return false
}
}
// let or var without accessor = stored property
return true
}
}Uso de la macro AutoInit
@AutoInit
struct User {
let id: UUID
var name: String
var email: String
var isActive: Bool = true
}
// Automatically generated code:
// public init(id: UUID, name: String, email: String, isActive: Bool = true) {
// self.id = id
// self.name = name
// self.email = email
// self.isActive = isActive
// }
// Usage
let user = User(id: UUID(), name: "Alice", email: "alice@example.com")
// isActive uses the default valueEsta macro elimina el boilerplate del inicializador, algo especialmente útil en modelos de datos con muchas propiedades.
Macro attached peer para generación async
Las macros peer añaden declaraciones al mismo nivel que la declaración anotada. A continuación se muestra una macro @AddAsync que genera la versión async de una función basada en completion handler. Este patrón es especialmente relevante al migrar código de red heredado, como se cubre en la guía de migración Combine vs async/await.
/// Automatically generates an async version of a function with completion handler
@attached(peer, names: suffixed(Async))
public macro AddAsync() = #externalMacro(
module: "MyMacrosPlugin",
type: "AddAsyncMacro"
)import SwiftSyntax
import SwiftSyntaxMacros
public struct AddAsyncMacro: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Verify it's a function
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
throw MacroError.invalidSyntax(
"@AddAsync requires a function"
)
}
let functionName = funcDecl.name.text
let asyncFunctionName = "\(functionName)Async"
// Analyze parameters to find the completion handler
let parameters = funcDecl.signature.parameterClause.parameters
// Filter parameters (exclude completion handler)
var regularParams: [String] = []
var completionType: String? = nil
for param in parameters {
let paramType = param.type.description
// Detect a completion handler (closure with Result or simple value)
if paramType.contains("->") && paramType.contains("Void") {
// Extract the return type from completion
completionType = extractCompletionReturnType(from: paramType)
} else {
let paramName = param.firstName.text
let paramSecondName = param.secondName?.text
let label = paramSecondName ?? paramName
regularParams.append("\(paramName): \(paramType)")
}
}
guard let returnType = completionType else {
throw MacroError.invalidSyntax(
"No completion handler found"
)
}
// Generate arguments for internal call
let callArgs = parameters.dropLast().map { param in
let name = param.firstName.text
return "\(name): \(name)"
}.joined(separator: ", ")
// Generate the async function
let asyncFunc: DeclSyntax = """
func \(raw: asyncFunctionName)(\(raw: regularParams.joined(separator: ", "))) async throws -> \(raw: returnType) {
try await withCheckedThrowingContinuation { continuation in
\(raw: functionName)(\(raw: callArgs.isEmpty ? "" : callArgs + ", ")completion: { result in
switch result {
case .success(let value):
continuation.resume(returning: value)
case .failure(let error):
continuation.resume(throwing: error)
}
})
}
}
"""
return [asyncFunc]
}
// Extract return type from a Result type
private static func extractCompletionReturnType(from type: String) -> String {
// Simplified pattern - in production, use the AST
if let match = type.range(of: #"Result<([^,]+)"#, options: .regularExpression) {
var result = String(type[match])
result = result.replacingOccurrences(of: "Result<", with: "")
return result.trimmingCharacters(in: .whitespaces)
}
return "Void"
}
}Demostración de la macro AddAsync
class NetworkService {
@AddAsync
func fetchUser(
id: Int,
completion: @escaping (Result<User, Error>) -> Void
) {
// Implementation with callback
URLSession.shared.dataTask(with: URL(string: "/users/\(id)")!) { data, _, error in
if let error = error {
completion(.failure(error))
} else if let data = data {
let user = try? JSONDecoder().decode(User.self, from: data)
completion(.success(user!))
}
}.resume()
}
// Automatically generates:
// func fetchUserAsync(id: Int) async throws -> User {
// try await withCheckedThrowingContinuation { continuation in
// fetchUser(id: id, completion: { result in
// switch result {
// case .success(let value):
// continuation.resume(returning: value)
// case .failure(let error):
// continuation.resume(throwing: error)
// }
// })
// }
// }
}
// Modern usage with async/await
let user = try await networkService.fetchUserAsync(id: 42)El nombre de la función generada debe declararse en names: del atributo @attached. Aquí, suffixed(Async) indica que la función generada llevará el sufijo "Async" añadido al nombre original.
Pruebas unitarias de las macros
Probar las macros es fundamental, ya que generan código que después se compilará. Swift proporciona SwiftSyntaxMacrosTestSupport para facilitar este tipo de pruebas. A partir de swift-syntax 603.0.0+, Swift Testing también está soportado junto con XCTest.
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest
@testable import MyMacrosPlugin
final class MyMacrosTests: XCTestCase {
// Dictionary of macros to test
let testMacros: [String: Macro.Type] = [
"unwrap": UnwrapMacro.self,
"AutoInit": AutoInitMacro.self,
"AddAsync": AddAsyncMacro.self,
"Remote": RemoteMacro.self
]
func testUnwrapMacroExpansion() throws {
assertMacroExpansion(
"""
let value = #unwrap(optionalString)
""",
expandedSource: """
let value = {
guard let value = optionalString else {
fatalError("Failed to unwrap 'optionalString' - value was nil")
}
return value
}()
""",
macros: testMacros
)
}
func testAutoInitMacroWithStruct() throws {
assertMacroExpansion(
"""
@AutoInit
struct Point {
let x: Int
var y: Int
}
""",
expandedSource: """
struct Point {
let x: Int
var y: Int
public init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
""",
macros: testMacros
)
}
func testAutoInitWithDefaultValues() throws {
assertMacroExpansion(
"""
@AutoInit
struct Config {
var timeout: Int = 30
var retryCount: Int
}
""",
expandedSource: """
struct Config {
var timeout: Int = 30
var retryCount: Int
public init(timeout: Int = 30, retryCount: Int) {
self.timeout = timeout
self.retryCount = retryCount
}
}
""",
macros: testMacros
)
}
func testAutoInitFailsOnEnum() throws {
assertMacroExpansion(
"""
@AutoInit
enum Status {
case active
}
""",
expandedSource: """
enum Status {
case active
}
""",
diagnostics: [
DiagnosticSpec(
message: "@AutoInit can only be applied to structs and classes",
line: 1,
column: 1
)
],
macros: testMacros
)
}
func testRemoteMacroExpansion() throws {
assertMacroExpansion(
"""
@Remote
func fetchUser(id: Int) async throws -> User
""",
expandedSource: """
func fetchUser(id: Int) async throws -> User {
let params: [String: Any] = ["id": id]
return try await RPCClient.shared.call(
method: "fetchUser",
params: params
)
}
""",
macros: testMacros
)
}
}Las pruebas verifican la correcta expansión del código y los mensajes de error apropiados ante un uso indebido.
Depurar e inspeccionar las macros
Xcode ofrece varias herramientas para depurar las macros y entender el código generado.
Expansión en Xcode
// Right-click on macro call → "Expand Macro"
// Displays generated code inline
@AutoInit
struct Product {
let id: UUID
var name: String
var price: Decimal
}
// To see the expansion:
// 1. Right-click on @AutoInit
// 2. Select "Expand Macro"
// 3. Generated code displays inline for inspection and debuggingLogging durante el desarrollo
public struct DebugMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
// Print the node's AST to understand the structure
print("=== DEBUG MACRO ===")
print("Node: \(node)")
print("Arguments: \(node.argumentList)")
// Complete dump of the syntax tree
dump(node)
// Continue with normal expansion
return "42"
}
}Explorar el AST con swift-ast-explorer
La herramienta en línea swift-ast-explorer.com permite visualizar el árbol de sintaxis de cualquier código Swift. Comprender la estructura de los nodos del AST es esencial al implementar macros, como se demuestra en la guía del framework Swift Testing que cubre las macros de testing integradas en Swift.
Buenas prácticas para Swift Macros
Crear macros mantenibles requiere seguir ciertas convenciones y evitar errores habituales.
Validación y mensajes de error
public struct ValidatedMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Validate usage context
guard declaration.is(StructDeclSyntax.self) else {
// Clear error messages with possible localization
context.diagnose(
Diagnostic(
node: node,
message: MacroDiagnosticMessage(
id: "invalid-target",
message: "This macro can only be applied to structs",
severity: .error
)
)
)
return []
}
// Check required arguments
guard let arguments = node.arguments else {
context.diagnose(
Diagnostic(
node: node,
message: MacroDiagnosticMessage(
id: "missing-args",
message: "Required arguments missing",
severity: .error
)
)
)
return []
}
// Implementation...
return []
}
}
// Structure for diagnostic messages
struct MacroDiagnosticMessage: DiagnosticMessage {
let id: String
let message: String
let severity: DiagnosticSeverity
var diagnosticID: MessageID {
MessageID(domain: "MyMacros", id: id)
}
}Generar código legible
// Avoid: hard-to-read generated code
let badCode: DeclSyntax = "public init(a:Int,b:String,c:Bool){self.a=a;self.b=b;self.c=c}"
// Preferred: properly formatted generated code
let goodCode: DeclSyntax = """
public init(
a: Int,
b: String,
c: Bool
) {
self.a = a
self.b = b
self.c = c
}
"""El código generado debe resultar tan legible como el escrito a mano, ya que las personas que desarrollen lo inspeccionarán mediante "Expand Macro".
Sources
- SE-0415: Function Body Macros, implementado en Swift 6.0
- swift-syntax releases, versión 600.0.0+ para Swift 6
- Swift.org Blog, notas de lanzamiento de Swift 6.3
Lo que las Swift Macros permiten en la práctica
Las Swift Macros representan una herramienta poderosa para eliminar boilerplate sin renunciar a la seguridad de tipos estática. Esta tecnología permite:
- Dos categorías de macros: freestanding (
#) y attached (@) - Seis roles attached en Swift 6: peer, accessor, member, memberAttribute, extension, y el nuevo rol body
- Implementación mediante swift-syntax 600.0.0+ y manipulación del AST
- Pruebas obligatorias con
SwiftSyntaxMacrosTestSupport, ahora con soporte para Swift Testing - Paquete separado obligatorio para las implementaciones
- Depuración mediante "Expand Macro" en Xcode
- Mensajes de error explícitos esenciales para la experiencia de quien desarrolla
Las Swift Macros resultan especialmente útiles para generar conformidades (Equatable, Codable), crear property wrappers avanzados, sintetizar implementaciones RPC a partir de firmas, y modernizar APIs basadas en callbacks hacia async/await.
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
¿Sabrías detectar el bug en iOS?
Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Escrito por
Anthony Fillion-MailletFundador de SharpSkill
Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.
Actualizado el 23 de agosto de 2026
Etiquetas
Compartir
Artículos relacionados

Combine vs async/await en Swift: Patrones de Migración Progresiva
Guía completa para migrar de Combine a async/await en Swift: estrategias progresivas, patrones de puente y coexistencia de paradigmas en bases de código iOS.

Preguntas de entrevista sobre accesibilidad iOS en 2026: VoiceOver y Dynamic Type
Prepárate para entrevistas iOS con preguntas clave de accesibilidad: VoiceOver, Dynamic Type, traits semánticos y auditorías.

Entrevista StoreKit 2: Gestión de Suscripciones y Validación de Recibos
Domina las preguntas de entrevista iOS sobre StoreKit 2, gestión de suscripciones, validación de recibos e implementación de compras integradas con ejemplos prácticos en Swift.