Swift Macros: exemplos práticos de metaprogramação
Guia completo sobre Swift Macros: criação de macros freestanding e attached incluindo body macros do Swift 6, manipulação da AST com swift-syntax e exemplos práticos para eliminar código repetitivo.

As Swift Macros transformam a forma como se escreve e mantém o código. Introduzidas com o Swift 5.9 e ampliadas significativamente no Swift 6, as macros permitem gerar código em tempo de compilação sem abrir mão da segurança de tipos estática. Diferentemente das macros do pré-processador C, as Swift Macros são seguras em tipos, integradas ao compilador e totalmente suportadas pelas ferramentas de desenvolvimento, incluindo o recurso "Expand Macro" do Xcode.
Este guia cobre a criação de Swift Macros desde os conceitos fundamentais até implementações avançadas, incluindo as novas body macros introduzidas no Swift 6 (SE-0415). Todos os exemplos usam Swift 6 e swift-syntax 600.0.0+.
Entendendo os tipos de Swift Macros
O Swift oferece duas categorias principais de macros, cada uma com casos de uso distintos. As macros freestanding atuam de forma autônoma como expressões ou declarações, enquanto as macros attached se associam a declarações existentes para modificá-las ou enriquecê-las.
Macros freestanding: expressão e declaração
Macros freestanding começam com o símbolo # e podem retornar um valor (expressão) ou criar novas declarações. A seguir, um exemplo concreto de macro de expressão:
// 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 pendingA diferença essencial entre expressão e declaração está no resultado: uma expressão produz um valor, ao passo que uma declaração produz código estrutural (tipos, funções, variáveis).
Macros attached: os seis papéis
Macros attached começam com @ e ficam à frente de uma declaração. O Swift 6 define seis papéis distintos para essas macros, incluindo o novo papel body para implementação de funções:
// @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 implementationEsses papéis podem ser combinados para criar macros poderosas, capazes de transformar o código em várias dimensões ao mesmo tempo.
Swift 6 Body Macros: SE-0415
O Swift 6 introduziu as function body macros por meio da SE-0415, permitindo que macros sintetizem ou substituam implementações de funções. Isso cobre casos de uso que nenhum papel anterior conseguia tratar.
Protocolo BodyMacro
O protocolo BodyMacro permite a substituição completa do corpo de uma função:
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]
}A macro recebe a declaração original e retorna itens de bloco de código que se tornam o corpo da função. Qualquer corpo existente é substituído.
Implementando uma macro de chamada de procedimento remoto
Body macros são particularmente úteis para gerar código de camada de rede a partir de assinaturas de função:
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
)
"""
]
}
}Declaração e 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
// )
// }
}Apenas uma body macro pode ser aplicada a uma determinada função. Para injeção de preâmbulo composível (logging, tracing), o protocolo PreambleMacro está em consideração para futuras versões do Swift.
Configuração do projeto para criar macros
A criação de Swift Macros exige um Swift Package com uma estrutura específica. O pacote depende do swift-syntax, biblioteca oficial para manipular código Swift como uma árvore de sintaxe abstrata (AST).
Estrutura do 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")
]
)
]
)Essa configuração separa de forma clara as declarações das macros (o que o código cliente enxerga) de sua implementação (executada em tempo de compilação).
São necessários no mínimo três arquivos: MyMacros.swift para as declarações, MyMacrosPlugin.swift para as implementações e MyMacrosTests.swift para os testes. Essa separação facilita a manutenção.
Criando uma macro de expressão
Macros de expressão geram um valor utilizável diretamente no código. Veja como criar uma macro #unwrap que faz o unwrap de um opcional com uma mensagem de erro personalizada que inclui o nome da variável. Para mais informações sobre padrões de tratamento de erros em Swift, consulte o guia sobre concorrência estruturada em Swift.
Declaração da 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"
)A assinatura indica que a macro recebe um opcional e devolve o valor não opcional. O #externalMacro aponta para a implementação dentro do plugin.
Implementação com 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)"
}
}
}O método expansion recebe o nó da AST que representa a chamada da macro e o contexto de compilação. Ele retorna um ExprSyntax que contém o código gerado.
Registro do 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
]
}Esse ponto de entrada informa ao compilador as macros disponíveis no plugin.
Pronto para mandar bem nas entrevistas de iOS?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Criando uma macro attached do tipo member
Macros member adicionam membros (propriedades, métodos, tipos aninhados) a um tipo existente. A seguir, uma macro @AutoInit que gera automaticamente um inicializador com todas as propriedades armazenadas.
Declaração e implementação 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 da 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 valueEssa macro elimina o boilerplate do inicializador, algo especialmente útil em modelos de dados com muitas propriedades.
Macro attached peer para geração async
Macros peer adicionam declarações no mesmo nível da declaração anotada. Veja a macro @AddAsync que gera a versão async de uma função baseada em completion handler. Esse padrão é especialmente relevante ao migrar código de rede legado, conforme abordado no guia de migração 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"
}
}Demonstração da 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)O nome da função gerada precisa ser declarado em names: do atributo @attached. Aqui, suffixed(Async) indica que a função gerada vai ter o sufixo "Async" anexado ao nome original.
Testes unitários para macros
Testar macros é essencial, já que elas geram código que será compilado. O Swift fornece SwiftSyntaxMacrosTestSupport para facilitar esses testes. A partir do swift-syntax 603.0.0+, o Swift Testing também é suportado junto com o 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
)
}
}Os testes verificam a expansão correta do código e as mensagens de erro adequadas para usos inválidos.
Depurar e inspecionar macros
O Xcode oferece várias ferramentas para depurar macros e entender o código gerado.
Expansão no 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 o desenvolvimento
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"
}
}Explorando a AST com o swift-ast-explorer
A ferramenta on-line swift-ast-explorer.com permite visualizar a árvore de sintaxe de qualquer código Swift. Compreender a estrutura dos nós da AST é essencial ao implementar macros, conforme demonstrado no guia do framework Swift Testing, que aborda as macros de teste integradas ao Swift.
Boas práticas para Swift Macros
Criar macros sustentáveis exige seguir certas convenções e evitar armadilhas comuns.
Validação e mensagens de erro
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)
}
}Gerar código legível
// 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
}
"""O código gerado precisa ser tão legível quanto o escrito à mão, já que quem desenvolve vai inspecioná-lo via "Expand Macro".
Sources
- SE-0415: Function Body Macros, implementado no Swift 6.0
- swift-syntax releases, versão 600.0.0+ para Swift 6
- Swift.org Blog, notas de lançamento do Swift 6.3
O que as Swift Macros permitem na prática
As Swift Macros representam uma ferramenta poderosa para eliminar boilerplate sem abrir mão da segurança de tipos estática. Essa tecnologia permite:
- Duas categorias de macros: freestanding (
#) e attached (@) - Seis papéis attached no Swift 6: peer, accessor, member, memberAttribute, extension, e o novo papel body
- Implementação via swift-syntax 600.0.0+ e manipulação da AST
- Testes obrigatórios com
SwiftSyntaxMacrosTestSupport, agora com suporte ao Swift Testing - Pacote separado obrigatório para as implementações
- Depuração via "Expand Macro" no Xcode
- Mensagens de erro explícitas, essenciais para a experiência de quem desenvolve
As Swift Macros são particularmente úteis para gerar conformances (Equatable, Codable), criar property wrappers avançados, sintetizar implementações RPC a partir de assinaturas, e modernizar APIs baseadas em callbacks rumo ao async/await.
Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Você saberia encontrar o bug em iOS?
Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Escrito por
Anthony Fillion-MailletFundador da SharpSkill
Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.
Atualizado em 23 de agosto de 2026
Tags
Compartilhar
Artigos relacionados

Combine vs async/await em Swift: Padrões de Migração Progressiva
Guia completo para migrar de Combine para async/await em Swift: estratégias progressivas, padrões de ponte e coexistência de paradigmas em bases de código iOS.

Perguntas de entrevista sobre acessibilidade iOS em 2026: VoiceOver e Dynamic Type
Prepare-se para entrevistas iOS com perguntas-chave de acessibilidade: VoiceOver, Dynamic Type, traits semânticos e auditorias.

Entrevista StoreKit 2: Gerenciamento de Assinaturas e Validação de Recibos
Domine perguntas de entrevista iOS sobre StoreKit 2, gerenciamento de assinaturas, validação de recibos e implementação de compras no aplicativo com exemplos práticos em Swift.