Skip to content

Swift Folha de referência

Linguagem da Apple para iOS, macOS e além.

01

Básico

Variáveis & Constantes

Use let por padrão para valores que não mudam; mude para var apenas quando mutação for necessária. Swift infere tipos em tempo de compilação, mas anotações explícitas melhoram a legibilidade para tipos complexos ou ambíguos.

swift
var name = "Alice"        // mutable
let age = 30               // immutable constant
let pi: Double = 3.14159   // explicit type
var count: Int = 0
count = 10
print(type(of: age))       // Int

Optionals

Optionals representam a ausência de um valor. Use if-let para unwrapping seguro, ?? para valores padrão e ! apenas quando você tem certeza de que o valor existe (arriscado—pode causar crash).

swift
var nickname: String? = nil
nickname = "Al"
if let n = nickname {
    print("Hi, \(n)")
}
print(nickname ?? "Anonymous")  // nil-coalescing
let force: String = nickname!   // force unwrap (crashes if nil)

Tuples

Tuples agrupam múltiplos valores em um único valor composto. Úteis para retornar múltiplos valores de funções. Elementos nomeados melhoram a legibilidade.

swift
let person = (name: "Alice", age: 30)
print(person.name)   // Alice
print(person.0)      // Alice
let (n, a) = person
print("\(n), \(a)")  // Alice, 30

Conversão de Tipos

Swift não converte implicitamente entre tipos. Sempre use conversão explícita (ex.: Double(intVal)) para evitar ambiguidade e prevenir bugs sutis.

swift
let intVal = 42
let doubleVal = Double(intVal)
let strVal = String(intVal)
let fromStr = Int("100")!   // 100
let invalid = Int("abc")    // nil
print(doubleVal, strVal)

Asserções & Pré-condições

Use assert para verificações de depuração (removidas em builds de release) e precondition para invariantes críticos verificados em todos os builds. Ambos ajudam a capturar erros de lógica cedo.

swift
let age = -5
assert(age >= 0, "Age cannot be negative")
precondition(age >= 0, "Age must be non-negative")
// In debug builds, assert crashes if false
// precondition checks in release builds too
02

Strings & Caracteres

Interpolação de Strings

Interpolação de strings com \(expr) incorpora qualquer expressão em uma string. É type-safe e avaliada em tempo de compilação, tornando-a mais segura que format strings.

swift
let name = "Alice"
let age = 30
let msg = "Name: \(name), Age: \(age)"
let calc = "5 + 3 = \(5 + 3)"
print(msg)        // Name: Alice, Age: 30
print(calc)       // 5 + 3 = 8

Métodos Comuns de String

Strings em Swift são Unicode-correct, o que significa que count reflete caracteres reais (grapheme clusters), não bytes. Use esses métodos em vez de indexação manual para segurança.

swift
let s = "Hello, World"
print(s.count)              // 12
print(s.uppercased())       // HELLO, WORLD
print(s.lowercased())       // hello, world
print(s.hasPrefix("Hello")) // true
print(s.contains("World"))  // true
print(s.reversed())         // "dlroW ,olleH"

Substring & Indexação

Índices de string não são inteiros porque caracteres podem ter tamanhos de bytes diferentes. Use index(_:offsetBy:) para navegação. Substrings compartilham memória com a original—converta para String para armazenamento de longo prazo.

swift
let s = "Hello, World"
let idx = s.index(s.startIndex, offsetBy: 7)
print(s[idx])                     // W
let sub = s[idx...]               // "World"
let newStr = String(sub)          // convert to String
print(s.prefix(5))                // "Hello"

Strings Multiline

Strings com aspas triplas preservam quebras de linha e indentação. As """ de fechamento determinam a indentação base. Ideais para HTML, JSON ou blocos de texto longos.

swift
let poem = """
Roses are red,
Violets are blue,
Swift is great,
And so are you.
"""
print(poem)
// Use \\(expr) for interpolation in multiline

Construção & Divisão de Strings

Use joined(separator:) para concatenar com um delimitador e split(separator:) para tokenizar. Esses são mais eficientes que loops manuais com concatenação +.

swift
let parts = ["apple", "banana", "cherry"]
let joined = parts.joined(separator: ", ")
print(joined)  // apple, banana, cherry
let csv = "a,b,c"
let fields = csv.split(separator: ",")
print(fields)  // ["a", "b", "c"]
let reversed = String(s.reversed())
03

Estruturas de Dados

Array

Arrays são coleções ordenadas, indexadas a partir de zero. Use append/insert para adições, filter/map/reduce para transformações. Prefira tipos de valor (Array é uma struct) para thread safety.

swift
var nums = [1, 2, 3]
nums.append(4)
nums.insert(0, at: 0)
nums[1] = 20
print(nums.count)        // 5
print(nums.contains(20)) // true
let evens = nums.filter { $0 % 2 == 0 }
print(nums.first, nums.last)

Dictionary

Dictionaries armazenam pares chave-valor com lookup O(1) médio. Use default: para evitar nil ao acessar chaves ausentes. As chaves devem ser Hashable.

swift
var ages: [String: Int] = ["Alice": 30]
ages["Bob"] = 25
ages["Alice"] = 31
print(ages["Alice", default: 0])  // 31
for (name, age) in ages {
    print("\(name): \(age)")
}
print(ages.keys.sorted())

Set

Sets armazenam valores únicos com teste de pertencimento O(1). Ideais para deduplicação e operações de conjunto (union, intersection, difference). Os elementos devem ser Hashable.

swift
var a: Set<Int> = [1, 2, 3]
var b: Set<Int> = [3, 4, 5]
print(a.union(b))           // {1,2,3,4,5}
print(a.intersection(b))    // {3}
print(a.subtracting(b))     // {1,2}
print(a.isSubset(of: b))    // false
a.insert(6)

Ranges

..< é um intervalo semi-aberto (exclui o limite superior), ... é um intervalo fechado (inclui ambos). Ranges são úteis em loops, slicing e pattern matching com o operador ~=.

swift
for i in 0..<5 { print(i) }   // 0,1,2,3,4
for i in 0...5 { print(i) }   // 0,1,2,3,4,5
let nums = Array(1...5)       // [1,2,3,4,5]
let prefix = nums.prefix(3)   // [1,2,3]
if 5...10 ~= 7 { print("in range") }

Funções de Ordem Superior

map transforma cada elemento, filter seleciona elementos correspondentes, reduce combina todos em um. Esses são a base da programação funcional em Swift e permitem pipelines de dados concisos e legíveis.

swift
let nums = [1, 2, 3, 4, 5]
let doubled = nums.map { $0 * 2 }       // [2,4,6,8,10]
let evens = nums.filter { $0 % 2 == 0 } // [2,4]
let sum = nums.reduce(0, +)             // 15
let strs = nums.map { String($0) }      // ["1","2",...]
print(doubled, evens, sum)
04

Fluxo de Controle

If / Else If / Else

Branching condicional padrão. As condições devem ser Boolean (tipo Bool). Swift exige chaves mesmo para instruções únicas para evitar bugs como a vulnerabilidade goto fail da Apple.

swift
let score = 85
if score >= 90 {
    print("A")
} else if score >= 80 {
    print("B")
} else if score >= 70 {
    print("C")
} else {
    print("F")
}

Switch (Pattern Matching)

Switch do Swift é poderoso: suporta value binding, tuples, where guards e ranges. Deve ser exaustivo (default cobre casos restantes) e não faz fall-through por padrão.

swift
let point = (2, 0)
switch point {
case (0, 0):
    print("origin")
case (_, 0):
    print("on x-axis")
case (0, _):
    print("on y-axis")
case let (x, y) where x == y:
    print("on diagonal")
default:
    print("elsewhere: \(point.0),\(point.1)")
}

Loops For-In

For-in itera sobre ranges, arrays, dictionaries e qualquer Sequence. Use enumerated() quando precisar do índice. Use _ para ignorar variáveis de loop que você não usa.

swift
for i in 0..<5 { print(i) }
let fruits = ["apple", "banana"]
for (i, fruit) in fruits.enumerated() {
    print("\(i): \(fruit)")
}
let dict = ["a": 1, "b": 2]
for (k, v) in dict { print("\(k)=\(v)") }
for _ in 0..<3 { print("tick") }

While & Repeat-While

while verifica a condição antes de cada iteração; repeat-while verifica depois (como do-while em C). Use repeat-while quando o corpo deve executar pelo menos uma vez.

swift
var n = 5
while n > 0 {
    print(n)
    n -= 1
}
var x = 0
repeat {
    x += 1
} while x < 3
print(x)  // 3

Guard (Saída Antecipada)

guard fornece saída antecipada quando pré-requisitos não são atendidos. Optionals unwrapped permanecem disponíveis no resto do escopo, reduzindo aninhamento. Prefira guard a aninhamento profundo de if-let.

swift
func greet(_ name: String?) {
    guard let n = name, !n.isEmpty else {
        print("No name provided")
        return
    }
    print("Hello, \(n)")
    // n is unwrapped and available here
}
greet("Alice")
greet(nil)
05

Funções & Closures

Definição de Função

Use _ para omitir argument labels para legibilidade. Valores padrão de parâmetros tornam parâmetros opcionais. Funções com uma única expressão têm retorno implícito (Swift 5.9+).

swift
func greet(_ name: String, greeting: String = "Hello") -> String {
    return "\(greeting), \(name)!"
}
print(greet("Alice"))               // Hello, Alice!
print(greet("Bob", greeting: "Hi")) // Hi, Bob!
func add(_ a: Int, _ b: Int) -> Int { a + b }
print(add(3, 4))  // 7

Múltiplos Valores de Retorno

Tuples permitem retornar múltiplos valores nomeados. Retornar uma tuple optional sinaliza possível falha. Acesse via r.min ou r.0—elementos nomeados são mais claros.

swift
func minMax(_ nums: [Int]) -> (min: Int, max: Int)? {
    guard let first = nums.first else { return nil }
    var mn = first, mx = first
    for n in nums { mn = min(mn, n); mx = max(mx, n) }
    return (mn, mx)
}
if let r = minMax([3, 1, 4, 1, 5]) {
    print("min=\(r.min), max=\(r.max)")
}

Closures

Closures são blocos autônomos de funcionalidade. Use $0, $1 para shorthand argument names. Trailing closure syntax (omitindo o último argument label) é idiomática para map/filter/reduce.

swift
let square: (Int) -> Int = { x in x * x }
print(square(5))  // 25
let add: (Int, Int) -> Int = { $0 + $1 }
print(add(3, 4))  // 7
let nums = [1, 2, 3]
let doubled = nums.map { $0 * 2 }
print(doubled)  // [2, 4, 6]

Escaping & Auto-Closures

@escaping marca closures armazenadas ou chamadas após a função retornar (necessário para callbacks async). @autoclosure envolve uma expressão em uma closure, atrasando sua avaliação (usado em assert).

swift
var handlers: [() -> Void] = []
func saveHandler(_ fn: @escaping () -> Void) {
    handlers.append(fn)
}
saveHandler { print("done") }
handlers.first?()
// @autoclosure delays evaluation
func logIfTrue(_ cond: @autoclosure () -> Bool) {
    if cond() { print("true") }
}
logIfTrue(2 > 1)

Parâmetros Inout

Parâmetros inout permitem que funções modifiquem a variável do chamador (passagem por referência). Use com moderação—prefira retornar novos valores para clareza. O prefixo & marca o local de mutação.

swift
func swap(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}
var x = 10, y = 20
swap(&x, &y)
print(x, y)  // 20 10
06

Classes & Structs

Struct (Tipo de Valor)

Structs são tipos de valor—copiadas na atribuição. Use para contêineres de dados simples. Propriedades computadas (distance) calculam no acesso. Métodos mutating modificam self.

swift
struct Point {
    var x: Double
    var y: Double
    var distance: Double {
        (x * x + y * y).squareRoot()
    }
    mutating func moveBy(dx: Double, dy: Double) {
        x += dx; y += dy
    }
}
var p = Point(x: 3, y: 4)
print(p.distance)  // 5.0

Class (Tipo de Referência)

Classes são tipos de referência—compartilhadas via referências, suportam herança e deinit. Use quando precisar de identidade, estado mutável compartilhado ou interoperabilidade com Objective-C. Caso contrário, prefira structs.

swift
class Person {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    deinit { print("\(name) deallocated") }
}
let p = Person(name: "Alice", age: 30)
print(p.name)  // Alice

Herança & Override

Use override para redefinir um método de superclasse. Swift usa dynamic dispatch para que a versão da subclasse seja chamada. Marque métodos como final para evitar overriding adicional por desempenho.

swift
class Animal {
    func speak() { print("...") }
}
class Dog: Animal {
    override func speak() { print("Woof!") }
}
class Cat: Animal {
    override func speak() { print("Meow!") }
}
let pets: [Animal] = [Dog(), Cat()]
for pet in pets { pet.speak() }

Propriedades (Computadas & Lazy)

Propriedades computadas têm blocos get/set; set usa newValue por padrão. lazy adia a inicialização até o primeiro acesso—útil para propriedades caras ou raramente necessárias. Deve ser var.

swift
class Circle {
    var radius: Double
    var area: Double {
        get { .pi * radius * radius }
        set { radius = (newValue / .pi).squareRoot() }
    }
    lazy var heavy = loadExpensiveData()
    init(radius: Double) { self.radius = radius }
}
func loadExpensiveData() -> String { "data" }

Property Observers

willSet/didSet observam mudanças de propriedade. Use didSet para validar ou reagir a mudanças (ex.: clamp valores, disparar atualizações de UI). Observers não disparam durante init.

swift
class Counter {
    var count: Int = 0 {
        willSet { print("about to set \(newValue)") }
        didSet {
            print("was \(oldValue), now \(count)")
            if count > 10 { count = 10 }
        }
    }
}
let c = Counter()
c.count = 5
c.count = 20  // clamped to 10
07

Protocols & Extensions

Definição de Protocol

Protocols definem um blueprint de métodos e propriedades. Tipos conformam implementando-os. Use para abstração, polimorfismo e desacoplamento—semelhante a interfaces em Java/C#.

swift
protocol Greetable {
    var name: String { get }
    func greet() -> String
}
struct User: Greetable {
    let name: String
    func greet() -> String { "Hi, \(name)" }
}
let u = User(name: "Alice")
print(u.greet())  // Hi, Alice

Protocol Extensions (Impl Padrão)

Protocol extensions fornecem implementações padrão. Isso permite modelagem retroativa e reuso de código sem uma classe base. Um recurso poderoso para compartilhar comportamento entre tipos não relacionados.

swift
protocol Describable {
    func describe() -> String
}
extension Describable {
    func describe() -> String { "A \(type(of: self))" }
}
struct Box: Describable { }
let b = Box()
print(b.describe())  // A Box

Extensions

Extensions adicionam funcionalidade a tipos existentes (mesmo os que você não possui, como Int). Use para organizar código, adicionar propriedades computadas ou conformar a protocols. Não pode adicionar stored properties.

swift
extension Int {
    var squared: Int { self * self }
    func times(_ block: () -> Void) {
        for _ in 0..<self { block() }
    }
}
print(5.squared)  // 25
3.times { print("hi") }  // prints hi 3 times

Generics

Generics escrevem código flexível e reutilizável que funciona com qualquer tipo, preservando a segurança de tipo. Use <T> para parâmetros de tipo. Constraints (where T: Equatable) restringem tipos permitidos.

swift
func stackOf<T>(_ items: T...) -> [T] {
    var arr: [T] = []
    for item in items { arr.append(item) }
    return arr
}
struct Stack<Element> {
    private var items: [Element] = []
    mutating func push(_ e: Element) { items.append(e) }
}
let s = stackOf(1, 2, 3)  // [Int]

Protocol com Associated Type

Associated types permitem que protocols usem tipos placeholder, como generics para protocols. O tipo conforming especifica o tipo real. Use typealias para torná-lo explícito, ou deixe Swift inferir.

swift
protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}
struct IntStack: Container {
    typealias Item = Int
    private var items: [Int] = []
    var count: Int { items.count }
    mutating func append(_ item: Int) { items.append(item) }
    subscript(i: Int) -> Int { items[i] }
}
08

Tratamento de Erros

Definindo & Lançando Erros

Erros conformam ao protocol Error (geralmente enums). throw sinaliza um erro. Associated values (coinsNeeded) carregam contexto. Use throws para marcar funções que podem falhar.

swift
enum VendingError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}
func vend(item: String, coins: Int) throws -> String {
    guard item == "Candy" else { throw VendingError.invalidSelection }
    guard coins >= 2 else { throw VendingError.insufficientFunds(coinsNeeded: 2) }
    return "Dispensing \(item)"
}

Do-Catch & Try

do-catch trata erros lançados. try marca chamadas que lançam. Pattern-match em cases específicos para tratamento direcionado. Um catch-all trata erros inesperados. Erros se propagam pela call stack.

swift
do {
    let result = try vend(item: "Candy", coins: 1)
    print(result)
} catch VendingError.insufficientFunds(let needed) {
    print("Need \(needed) more coins")
} catch VendingError.outOfStock {
    print("Sold out")
} catch {
    print("Other error: \(error)")
}

Try? / Try!

try? converte erros em nil (retorna Optional). try! assume sucesso e causa crash em erro—use apenas quando a falha é verdadeiramente impossível. Prefira try? com optional binding para degradação graciosa.

swift
let result1 = try? vend(item: "Candy", coins: 1)
print(result1)  // nil (error converted to optional)
let result2 = try! vend(item: "Candy", coins: 5)
print(result2)  // Dispensing Candy (crashes if error)
if let r = try? vend(item: "Candy", coins: 5) {
    print(r)
}

Tipo Result

Result codifica sucesso ou falha como um valor, permitindo tratamento de erro async sem throw. Use para callbacks, APIs async ou quando quiser armazenar/encadear resultados. .get() lança em caso de falha.

swift
enum FetchError: Error { case network, parse }
func fetch(_ url: String) -> Result<String, FetchError> {
    url.isEmpty ? .failure(.network) : .success("data from \(url)")
}
switch fetch("https://api.com") {
case .success(let data): print(data)
case .failure(let err): print("Error: \(err)")
}
let value = try? fetch("").get()

Defer (Limpeza)

defer agenda código de limpeza para executar quando o escopo sair, independentemente de como (retorno normal, throw, erro). Use para liberação de recursos (arquivos, locks). Múltiplos defers executam em ordem LIFO.

swift
func processFile(_ path: String) throws {
    let handle = openFile(path)
    defer { closeFile(handle) }
    // ... work that might throw ...
    let data = try read(handle)
    // closeFile always runs, even if throw
    print("processed \(data)")
}
09

File I/O & Date/Time

Ler & Escrever Arquivos

Foundation fornece APIs simples de arquivo. write(toFile:atomically:) escreve com segurança (arquivo temporário + rename). Use String(contentsOfFile:) para texto. Para arquivos grandes, use FileHandle para streaming.

swift
import Foundation
let path = "test.txt"
let content = "Hello, File I/O!"
try content.write(toFile: path, atomically: true,
                  encoding: .utf8)
let read = try String(contentsOfFile: path, encoding: .utf8)
print(read)  // Hello, File I/O!
let lines = read.split(separator: "\n")
print(lines.count)

URL & FileManager

FileManager lida com operações de sistema de arquivos (criar, excluir, verificar existência). Use URLs (não caminhos) para APIs modernas. .documentDirectory é o armazenamento persistente do app no iOS/macOS.

swift
import Foundation
let fm = FileManager.default
let url = fm.urls(for: .documentDirectory, in: .userDomainMask)[0]
    .appendingPathComponent("data.json")
try "data".write(to: url, atomically: true, encoding: .utf8)
let exists = fm.fileExists(atPath: url.path)
print("Exists: \(exists)")
try fm.removeItem(at: url)  // delete

Date & DateFormatter

Date representa um ponto no tempo (UTC internamente). DateFormatter converte entre Date e String—sempre defina locale para en_US_POSIX para parsing de formato fixo para evitar bugs de locale.

swift
import Foundation
let now = Date()
let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
fmt.locale = Locale(identifier: "en_US_POSIX")
let str = fmt.string(from: now)
print(str)  // e.g. 2024-01-15 14:30:00
let parsed = fmt.date(from: "2024-01-01 00:00:00")
print(parsed ?? "invalid")

Encoding/Decoding JSON

Codable automatiza serialização JSON. JSONEncoder/JSONDecoder lidam com a conversão. Conforme seus tipos a Codable—o compilador sintetiza a lógica. Use CodingKeys para personalizar nomes de chave.

swift
struct User: Codable {
    let name: String
    let age: Int
}
let user = User(name: "Alice", age: 30)
let json = try JSONEncoder().encode(user)
let str = String(data: json, encoding: .utf8)!
print(str)  // {"name":"Alice","age":30}
let decoded = try JSONDecoder().decode(User.self, from: json)
print(decoded.name)  // Alice

Cálculos de Date

Calendar lida com aritmética de datas respeitando time zones e DST. Use dateComponents para extrair campos ou computar diferenças. Nunca use segundos brutos para matemática de data—use APIs de Calendar.

swift
import Foundation
let cal = Calendar.current
let now = Date()
let tomorrow = cal.date(byAdding: .day, value: 1, to: now)!
let components = cal.dateComponents([.year, .month, .day], from: now)
print(components.year!, components.month!)
let diff = cal.dateComponents([.hour], from: now, to: tomorrow)
print(diff.hour!)  // 24
10

Concorrência & Async

Async / Await

async/await (Swift 5.5+) faz código async ler como código sync. Task cria um novo contexto async. async let executa concorrentemente e aguarda todos. Elimina callback hell—prefira a completion handlers.

swift
func fetchUser(_ id: Int) async -> String {
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    return "User \(id)"
}
Task {
    let user = await fetchUser(42)
    print(user)  // User 42
    async let u1 = fetchUser(1)
    async let u2 = fetchUser(2)
    let users = await [u1, u2]  // concurrent
    print(users)
}

Task & Cancellation

Task representa uma unidade de trabalho async. Cancelamento cooperativo via Task.isCancelled—tarefas de longa duração devem verificar periodicamente. task.value aguarda o resultado. Cancelamento é cooperativo, não forçado.

swift
let task = Task {
    for i in 1...100 {
        if Task.isCancelled { return }
        try? await Task.sleep(nanoseconds: 100_000_000)
        print(i)
    }
}
Task {
    try? await Task.sleep(nanoseconds: 500_000_000)
    task.cancel()  // request cancellation
}
await task.value  // wait for completion

Actors (Thread Safety)

Actors (Swift 5.5+) protegem estado mutável de data races serializando o acesso. Todo acesso passa por await. Use em vez de locks/queues para estado compartilhado. O compilador verifica a segurança.

swift
actor Counter {
    private var count = 0
    func increment() { count += 1 }
    func value() -> Int { count }
}
let counter = Counter()
Task {
    await counter.increment()
    print(await counter.value())
}
// Actors serialize access—no data races

GCD (Grand Central Dispatch)

GCD é a API de concorrência tradicional. DispatchQueue.global() para trabalho em segundo plano, .main para atualizações de UI. QoS (.userInitiated, .background) prioriza tarefas. Ainda útil para código não async.

swift
import Dispatch
DispatchQueue.global(qos: .userInitiated).async {
    let result = heavyComputation()
    DispatchQueue.main.async {
        print("UI update: \(result)")
    }
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    print("delayed by 2s")
}

Async Sequence

AsyncSequence permite iterar sobre valores async (como um stream). Use for await para consumir. Ideal para APIs paginadas, server-sent events ou qualquer fonte que produza valores ao longo do tempo.

swift
let urls = ["url1", "url2", "url3"]
func fetchAll() async {
    for url in urls {
        let data = await fetchData(url)
        print(data)
    }
}
func fetchData(_ url: String) async -> String {
    "data from \(url)"
}
// AsyncSequence: for await item in stream { ... }
11

Protocols Aprofundado

Protocol com Associated Types

Associated types (associatedtype) permitem que protocols declarem um tipo placeholder que tipos conforming especificam. Isso é o equivalente em Swift a parâmetros de tipo genérico para protocols. O tipo é inferido a partir dos métodos do tipo conforming. Use cláusulas 'where' para restringir associated types. PATs (Protocols with Associated Types) não podem ser usados como tipos existenciais diretamente sem type erasure ou (Swift 5.7+) 'any Container'.

swift
protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}

struct IntStack: Container {
    // associatedtype Item inferred as Int
    private var items: [Int] = []
    var count: Int { items.count }
    mutating func append(_ item: Int) { items.append(item) }
    subscript(i: Int) -> Int { items[i] }
}

// Generic constraint with associated type
func sum<C: Container>(_ c: C) -> Int where C.Item == Int {
    var total = 0
    for i in 0..<c.count { total += c[i] }
    return total
}

Protocol Extensions (Implementações Padrão)

Protocol extensions fornecem implementações padrão—tipos conforming recebem o método de graça, mas podem sobrescrevê-lo. Essa é a maneira do Swift de adicionar funcionalidade a tipos retroativamente. Constrained extensions (where Element: Numeric) adicionam métodos apenas a tipos que atendem à constraint. É assim que a biblioteca padrão adiciona map/filter/reduce a todas as Collections.

swift
protocol Describable {
    var description: String { get }
}

// Default implementation via extension
extension Describable {
    var description: String { "A \(type(of: self))" }
}

struct Point: Describable {
    let x, y: Int
    // Uses default description: "A Point"
}

struct Person: Describable {
    let name: String
    var description: String { "Person named (name)" }  // override
}

// Extension with constraints
extension Collection where Element: Numeric {
    var sum: Element { reduce(0, +) }
}
[1, 2, 3].sum  // 6

Protocol Composition e Existentials

Protocol composition (A & B) exige que um valor conforme a múltiplos protocols. Existential types (any Protocol) podem armazenar qualquer tipo conforming, mas têm sobrecarga de dispatch em runtime—uma witness table lookup por chamada. Use 'some Protocol' (opaque return type) quando você retorna um tipo específico, mas quer escondê-lo. Prefira generics a existentials para desempenho; use existentials quando precisar de coleções heterogêneas.

swift
protocol Named { var name: String { get } }
protocol Aged { var age: Int { get } }

struct Person: Named, Aged {
    let name: String
    let age: Int
}

// Protocol composition: requires both protocols
func wishHappyBirthday(to celebrant: Named & Aged) {
    print("Happy birthday, (celebrant.name), you're (celebrant.age)!")
}

let p = Person(name: "Alice", age: 30)
wishHappyBirthday(to: p)

// Existential types (any) — type erasure overhead
let items: [any Named] = [p, Pet(name: "Rex")]
for item in items { print(item.name) }

// Swift 5.7: 'any' keyword required for existentials
// 'some' for opaque types (returns one concrete type)

Programação Orientada a Protocol

Programação Orientada a Protocol é a mudança de paradigma do Swift em relação a OOP. Em vez de hierarquias de classe, projete em torno de protocols com implementações padrão. Benefícios: funciona com tipos de valor (structs/enums), suporta conformidade retroativa (estenda tipos que você não possui), permite múltiplas 'heranças' (um tipo pode conformar a muitos protocols). Classes ainda são úteis para semântica de referência e interop com Objective-C, mas structs+protocols são preferidos para a maioria dos modelos.

swift
// POP: design around protocols, not inheritance
protocol Drawable {
    func draw(on canvas: Canvas)
}

extension Drawable {
    // Default implementation + polymorphism
    func drawTwice(on canvas: Canvas) {
        draw(on: canvas)
        draw(on: canvas)
    }
}

struct Circle: Drawable { let radius: Double
    func draw(on canvas: Canvas) { /* ... */ }
}
struct Square: Drawable { let side: Double
    func draw(on canvas: Canvas) { /* ... */ }
}

// Value types + protocols = no inheritance needed
let shapes: [any Drawable] = [Circle(radius: 5), Square(side: 3)]
shapes.forEach { $0.draw(on: canvas) }

// Retroactive modeling (extend types you don't own)
extension Int: Drawable {
    func draw(on canvas: Canvas) { /* draw the number */ }
}

Custom Protocol Witnesses

Protocols como dependências (Repository) permitem trocar implementações (real vs mock) para teste e flexibilidade. Associated types tornam protocols genéricos. Essa é a injeção de dependência do Swift—injete um tipo conforming ao protocol. O 'protocol witness' é o tipo conforming que fornece comportamento concreto. Esse padrão (repository, data source) é comum na arquitetura Swift (VIPER, Clean Architecture).

swift
// Protocol with requirements
protocol Repository {
    associatedtype Entity
    func get(_ id: Int) -> Entity?
    func save(_ entity: Entity)
}

// Concrete implementation
struct UserRepo: Repository {
    typealias Entity = User
    func get(_ id: Int) -> User? { /* fetch */ nil }
    func save(_ user: User) { /* persist */ }
}

// Generic function using protocol
func display<R: Repository>(_ repo: R, id: Int) where R.Entity: CustomStringConvertible {
    if let entity = repo.get(id) {
        print(entity.description)
    }
}

// Test double for testing
struct MockUserRepo: Repository {
    typealias Entity = User
    var stored: [Int: User] = [:]
    func get(_ id: Int) -> User? { stored[id] }
    func save(_ user: User) { /* no-op for tests */ }
}
12

Generics Aprofundado

Funções e Tipos Genéricos

Generics escrevem código flexível e reutilizável que funciona com qualquer tipo, preservando a segurança de tipo. T é um parâmetro de tipo (placeholder). O compilador gera versões especializadas para desempenho (sem boxing). Tipos genéricos (Stack<T>) mantêm seu parâmetro de tipo. Element é o tipo genérico de Array. Generics são resolvidos em tempo de compilação—sem sobrecarga de runtime, diferentemente de existentials.

swift
// Generic function
func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a; a = b; b = temp
}
var x = 1, y = 2
swap(&x, &y)  // x=2, y=1

// Generic type
struct Stack<T> {
    private var items: [T] = []
    mutating func push(_ item: T) { items.append(item) }
    mutating func pop() -> T? { items.popLast() }
}

var intStack = Stack<Int>()
intStack.push(42)
var strStack = Stack<String>()
strStack.push("hello")

// Generic method on non-generic type
extension Array {
    func chunked(into size: Int) -> [[Element]] {
        stride(from: 0, to: count, by: size).map {
            Array(self[$0..<Swift.min($0 + size, count)])
        }
    }
}

Type Constraints

Type constraints limitam quais tipos podem ser usados: T: Equatable exige que T conforme a um protocol, T: SomeClass exige uma hierarquia de classe. A cláusula where adiciona constraints mais complexas (ex.: correspondência de associated types). Constraints permitem usar os métodos do protocol (== para Equatable, < para Comparable). Sem constraints, você só pode atribuir e passar T adiante—sem operações.

swift
// Constraint: T must conform to Equatable
func indexOf<T: Equatable>(_ item: T, in array: [T]) -> Int? {
    for (i, elem) in array.enumerated() {
        if elem == item { return i }  // needs Equatable
    }
    return nil
}

// Multiple constraints
func max<T: Comparable>(_ a: T, _ b: T) -> T {
    return a > b ? a : b
}

// Constraint on associated type
protocol Repository {
    associatedtype Entity: Identifiable
    func find(_ id: Entity.ID) -> Entity?
}

// where clause for complex constraints
func merge<C1: Collection, C2: Collection>(
    _ c1: C1, _ c2: C2
) -> [C1.Element] where C1.Element == C2.Element {
    Array(c1) + Array(c2)
}

Opaque Types (some)

Opaque types (some Protocol, Swift 5.1) retornam um tipo concreto específico que está oculto do chamador. Diferente de 'any' (existential), o tipo é fixo e conhecido pelo compilador—sem boxing, dispatch em tempo de compilação. Essa é a base do 'some View' do SwiftUI. Use 'some' quando quiser esconder o tipo concreto, mas manter o desempenho. O chamador pode usar métodos do protocol, mas não pode depender do tipo específico.

swift
// Opaque return type: returns ONE concrete type
// (hidden from caller, but fixed per call site)
func makeStack() -> some Collection<Int> {
    return [1, 2, 3]  // concrete type is [Int]
}

let s = makeStack()  // type is 'some Collection<Int>'
print(s.count)  // 3

// Useful for hiding implementation details
protocol Shape { func draw() }
struct Circle: Shape { func draw() { print("circle") } }

func makeShape() -> some Shape {
    return Circle()  // caller doesn't know it's Circle
}

// Difference from existential (any):
// - some: one concrete type, no boxing, compile-time dispatch
// - any: can be different types, runtime dispatch, boxing

// Opaque types in properties
struct View {
    var body: some View { Text("Hello") }  // SwiftUI
}

Conditional Conformance

Conditional conformance faz um tipo conformar a um protocol apenas quando seus parâmetros de tipo atendem a certas condições. Array<Int> é Equatable porque Int é; Array<MyStruct> não é, a menos que MyStruct seja. Isso propaga: [[Int]] é Equatable porque [Int] é. A biblioteca padrão usa isso extensivamente—Array, Optional, Dictionary todos conformam condicionalmente a Equatable, Hashable, Codable.

swift
// Array conforms to Equatable only if Element is Equatable
extension Array: Equatable where Element: Equatable {
    static func == (lhs: [Element], rhs: [Element]) -> Bool {
        return lhs.count == rhs.count && lhs.elementsEqual(rhs)
    }
}

[1, 2, 3] == [1, 2, 3]  // true (Int: Equatable)
// [SomeStruct] == [SomeStruct]  // error if SomeStruct isn't Equatable

// Conditional conformance for Hashable
extension Stack: Equatable where T: Equatable {
    static func == (lhs: Stack, rhs: Stack) -> Bool {
        return lhs.items == rhs.items
    }
}

// Enables: [Stack<Int>] == [Stack<Int>] (transitively)

Result e Tratamento de Erro Genérico

Result<Success, Failure> é um enum genérico para operações que podem falhar, carregando um valor ou um erro. É útil quando você quer adiar o tratamento de erro ou armazenar resultados. map transforma o valor de sucesso; flatMap encadeia operações que também podem falhar. Result { try ... } converte funções que lançam. .get() converte de volta para throwing. Com async/await, Result é menos necessário, mas ainda útil para erros armazenados/passados.

swift
// Result: built-in generic for success/failure
enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

func fetchUser(_ id: Int) -> Result<User, FetchError> {
    if id < 0 { return .failure(.invalidId) }
    return .success(User(id: id))
}

switch fetchUser(42) {
case .success(let user): print(user)
case .failure(let err): print(err)
}

// map/flatMap for chaining
let result = fetchUser(1)
    .map { $0.name }
    .flatMap { name in fetchAvatar(name) }

// Throwing function → Result
let result = Result { try throwingFunction() }
// Result → throwing
let value = try result.get()
13

ARC & Gerenciamento de Memória

Automatic Reference Counting (ARC)

ARC (Automatic Reference Counting) gerencia memória para instâncias de classe (tipos de referência). Cada referência forte incrementa a contagem; remover uma decrementa. Quando a contagem chega a 0, o objeto é liberado imediatamente (determinístico, diferentemente de garbage collection). Structs/enums (tipos de valor) não usam ARC—são copiados. ARC não consegue lidar com ciclos de referência—você deve usar weak/unowned para quebrá-los.

swift
class Person {
    let name: String
    init(name: String) { self.name = name; print("\(name) initialized") }
    deinit { print("\(name) deinitialized") }
}

var p1: Person? = Person(name: "Alice")  // "Alice initialized"
var p2 = p1  // strong ref count = 2
p1 = nil     // ref count = 1, not deinitialized
p2 = nil     // ref count = 0, "Alice deinitialized"

// ARC tracks strong references:
// - +1 on assignment/copy
// - -1 when variable goes out of scope
// - Object freed when count hits 0
// Unlike GC, deterministic (freed immediately when count=0)

Referências Strong, Weak e Unowned

Referências strong (padrão) mantêm objetos vivos. Referências weak não mantêm o objeto vivo e se tornam nil quando o objeto é liberado (devem ser optional var). Referências unowned não mantêm o objeto vivo, mas são non-optional—use apenas quando tem certeza de que o objeto referenciado sobrevive à referência (crash se acessado após dealloc). Use weak para padrões delegate; unowned para pai-filho onde o pai sempre sobrevive ao filho.

swift
class Person {
    var name: String
    var apartment: Apartment?  // strong
    init(name: String) { self.name = name }
    deinit { print("\(name) deinit") }
}

class Apartment {
    var unit: String
    weak var tenant: Person?  // weak breaks cycle
    init(unit: String) { self.unit = unit }
    deinit { print("Apartment \(unit) deinit") }
}

var alice: Person? = Person(name: "Alice")
var apt: Apartment? = Apartment(unit: "4A")
alice?.apartment = apt
apt?.tenant = alice  // weak, doesn't keep alice alive

alice = nil  // "Alice deinit" (no strong cycle)
apt = nil    // "Apartment 4A deinit"

// unowned: non-optional, assumed to always have a value
// Use when the referenced object outlives the reference
class Customer {
    var card: CreditCard?  // strong
}
class CreditCard {
    unowned let owner: Customer  // non-nil, non-optional
    init(owner: Customer) { self.owner = owner }
}

Retain Cycles e Closures

Closures capturam referências fortes por padrão. Se uma classe armazena uma closure que captura self, você tem um retain cycle (ambos mantêm um ao outro vivos → vazamento de memória). Corrija com capture lists: [weak self] (self se torna optional, use guard let) ou [unowned self] (non-optional, crasha se nil). Sempre use weak self em closures armazenadas como propriedades ou passadas para objetos de longa duração (observers, async tasks).

swift
class ViewModel {
    var name = "VM"
    var onUpdate: (() -> Void)?

    func setup() {
        // BAD: closure captures self strongly → cycle
        onUpdate = {
            print(self.name)  // strong capture
        }
        // ViewModel holds closure, closure holds ViewModel → leak!

        // GOOD: capture list with weak self
        onUpdate = { [weak self] in
            guard let self = self else { return }
            print(self.name)
        }

        // GOOD: unowned if self outlives closure
        onUpdate = { [unowned self] in
            print(self.name)
        }
    }
    deinit { print("VM deinit") }
}

Closures Escaping e Non-escaping

Closures non-escaping (padrão) executam dentro da função e são descartadas—sem risco de retain cycle, sem necessidade de self. no closure. Closures @escaping sobrevivem à função (armazenadas em uma propriedade, dispatchadas async, etc.)—elas podem causar retain cycles, então você deve usar self. explicitamente e considerar [weak self]. O compilador impõe isso. A maioria dos completion handlers é @escaping; map/filter/reduce são non-escaping.

swift
class DataLoader {
    var storedHandler: (() -> Void)?

    // @escaping: closure outlives the function (stored or async)
    func load(with handler: @escaping (Data) -> Void) {
        storedHandler = { handler(cachedData) }  // stored
        DispatchQueue.global().async {
            handler(self.fetch())  // escapes via async
        }
    }

    // Non-escaping (default): closure runs during the function
    func process(_ transform: (Int) -> Int) -> [Int] {
        return [1, 2, 3].map(transform)  // runs now, no storage
    }
    // No need for [weak self]—self is alive during the call
}

// @escaping requires explicit self. in closures
class View {
    func load() {
        DataLoader().load { [self] data in  // self. required
            print(data)
        }
    }
}

Segurança de Memória e Exclusividade

Swift impõe exclusividade—uma variável não pode ser acessada (read+write ou write+write) simultaneamente de escopos sobrepostos. Isso previne data races e comportamento indefinido. Parâmetros inout obtêm acesso exclusivo pela duração da chamada. Métodos mutating de struct mantêm acesso exclusivo a self. Actors (Swift 5.5+) impõem exclusividade no nível da linguagem para estado mutável compartilhado, eliminando data races por design.

swift
// Exclusivity: Swift prevents overlapping access to a variable
// This is a compile-time or runtime error:
var step = 1
func increment(_ n: inout Int) {
    step += 1  // accessing global while n is inout to step
}
// increment(&step)  // ERROR: overlapping access

// Fix with explicit copy
func safeIncrement(_ n: inout Int) {
    var copy = step
    copy += 1
    step = copy
}

// Struct mutating methods have exclusive access
struct Counter {
    var count = 0
    mutating func increment(_ other: inout Counter) {
        count += other.count  // ok, different instances
    }
}

// 'inout' parameters: exclusive access for duration
// 'isolation' (Swift 5.5+): actors enforce exclusivity
14

SwiftUI Básico

Views e Modifiers

SwiftUI é o framework de UI declarativo da Apple. Views são structs conforming a View com uma propriedade 'body'. Modifiers (.font, .padding) retornam novas views envolvidas—não mutam. Views são tipos de valor leves; SwiftUI faz diff delas para atualizar a UI real. O estilo declarativo descreve como a UI deve parecer para um determinado estado, e SwiftUI lida com as transições.

swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(spacing: 20) {
            Text("Hello, World!")
                .font(.title)
                .fontWeight(.bold)
                .foregroundColor(.blue)

            Button(action: { print("tapped") }) {
                HStack {
                    Image(systemName: "star.fill")
                    Text("Tap me")
                }
            }
            .padding()
            .background(Color.orange)
            .cornerRadius(10)
        }
        .padding()
    }
}

// Views are value types (structs), declarative
// Modifiers return new views (immutability)

State e Binding

@State é para estado de view local e mutável—quando muda, SwiftUI re-renderiza a view. @Binding permite que uma view filha leia/escreva o @State do pai via prefixo $ (cria um binding). State deve ser a única fonte de verdade; passe bindings para baixo, não valores para modificar. Para estado compartilhado/complexo entre views, use @StateObject/@ObservedObject/@EnvironmentObject com ObservableObject.

swift
struct CounterView: View {
    @State private var count = 0  // local, mutable state

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") { count += 1 }
        }
    }
}

// @Binding: child receives a reference to parent's state
struct StepperView: View {
    @Binding var value: Int
    var body: some View {
        HStack {
            Button("-") { value -= 1 }
            Text("\(value)")
            Button("+") { value += 1 }
        }
    }
}

// Parent passes binding with $
struct ParentView: View {
    @State var score = 0
    var body: some View {
        StepperView(value: $score)  // $ creates a Binding
    }
}

ObservableObject e EnvironmentObject

ObservableObject (classe) é para estado compartilhado entre múltiplas views. Propriedades @Published disparam atualizações de view quando mudam. @StateObject cria e possui o objeto (use no topo da hierarquia). @EnvironmentObject injeta o objeto na árvore de views—qualquer descendente pode acessá-lo sem passar por bindings. @ObservedObject recebe um objeto existente (não o possui). Use @StateObject para propriedade, @ObservedObject/@EnvironmentObject para recebimento.

swift
// Model: ObservableObject for shared state
class AppModel: ObservableObject {
    @Published var username: String = ""  // @Published triggers UI update
    @Published var isLoggedIn: Bool = false

    func login() {
        // ... auth logic ...
        isLoggedIn = true
    }
}

// @StateObject: owns the model (created once)
struct RootView: View {
    @StateObject var model = AppModel()
    var body: some View {
        // Inject into environment
        ContentView().environmentObject(model)
    }
}

// @EnvironmentObject: receives from environment
struct ProfileView: View {
    @EnvironmentObject var model: AppModel
    var body: some View {
        Text("Hello, \(model.username)")
    }
}

// @ObservedObject: receives from parent (doesn't own)
struct LoginView: View {
    @ObservedObject var model: AppModel
}

Lists e Navigation

List renderiza linhas roláveis (como UITableView). Itens devem conformar a Identifiable (ou você fornece id:). NavigationStack (iOS 16+) gerencia navegação; NavigationLink empurra destinos. LazyVStack/LazyHStack carregam conteúdo preguiçosamente para desempenho. ForEach é para repetir views dentro de outros containers. Use List para dados tabulares, ScrollView+LazyVStack para layouts personalizados.

swift
struct Item: Identifiable {
    let id = UUID()
    let name: String
}

struct ListView: View {
    let items = [Item(name: "Apple"), Item(name: "Banana")]

    var body: some View {
        NavigationStack {
            List(items) { item in
                NavigationLink(item.name) {
                    DetailView(item: item)
                }
            }
            .navigationTitle("Fruits")
        }
    }
}

struct DetailView: View {
    let item: Item
    var body: some View {
        Text("Detail: \(item.name)")
            .navigationTitle(item.name)
    }
}

// ForEach for custom layouts
ScrollView {
    LazyVStack {
        ForEach(items) { item in
            Text(item.name)
        }
    }
}

Forms e Sheets

Form estiliza automaticamente controles para telas de configurações (agrupado, apropriado à plataforma). Controles comuns: TextField, Toggle, Slider, Picker, Stepper. .sheet apresenta um modal; .fullScreenCover para tela cheia. Bindings ($) conectam controles ao estado. Form se adapta à plataforma (lista agrupada no iOS, layout de formulário no macOS). Use Form para entrada de dados e configurações; use VStack para layouts personalizados.

swift
struct SettingsView: View {
    @State private var name = ""
    @State private var notifications = true
    @State private var volume: Double = 0.5
    @State private var theme = "Light"
    @State private var showSheet = false

    var body: some View {
        Form {
            Section("Profile") {
                TextField("Name", text: $name)
                Toggle("Notifications", isOn: $notifications)
            }
            Section("Audio") {
                Slider(value: $volume, in: 0...1)
            }
            Section("Appearance") {
                Picker("Theme", selection: $theme) {
                    Text("Light").tag("Light")
                    Text("Dark").tag("Dark")
                }
            }
            Button("Show Modal") { showSheet = true }
        }
        .sheet(isPresented: $showSheet) {
            Text("Modal content")
        }
    }
}
15

Closures Aprofundado

Variações de Sintaxe de Closure

Closures têm muitas formas de sintaxe. Forma completa: { (params) -> Type in body }. Type inference permite omitir tipos. Shorthand arguments ($0, $1) substituem params nomeados. Trailing closure syntax move a closure para fora de () quando é o último arg. Múltiplas trailing closures (Swift 5.3+) nomeiam closures adicionais. Use a forma mais concisa que ainda seja legível—shorthand args são ótimos para closures curtas como map.

swift
// Full closure
let add: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
    return a + b
}

// Type inference
let add2: (Int, Int) -> Int = { a, b in a + b }

// Shorthand argument names ($0, $1...)
let add3: (Int, Int) -> Int = { $0 + $1 }

// Trailing closure syntax
let result = [1, 2, 3].map { $0 * 2 }  // [2, 4, 6]

// Multiple trailing closures (Swift 5.3+)
Button {
    print("action")  // action
} label: {
    Text("Click")    // label
}

// No return for Void closures
let log: (String) -> Void = { msg in print(msg) }

Capturando Valores

Closures capturam variáveis de seu escopo delimitador. Por padrão, capturam por referência (mudanças na variável capturada são visíveis). Capture lists [foo] capturam por valor (um snapshot na criação da closure). Para tipos de referência (classes), [weak self] ou [unowned self] quebram retain cycles. Tipos de valor (structs) capturados por referência ainda veem atualizações porque a closure mantém uma box. Capture lists vão antes da lista de parâmetros.

swift
// Closures capture surrounding state
func makeIncrementer(amount: Int) -> () -> Int {
    var total = 0
    return {
        total += amount  // captures total and amount
        return total
    }
}

let inc = makeIncrementer(amount: 5)
inc()  // 5
inc()  // 10
// total persists between calls (captured by reference)

// Capture list controls how
class Foo { var x = 0 }
let foo = Foo()
let closure = { [foo] in  // captures foo by value (snapshot)
    print(foo.x)
}
foo.x = 100
closure()  // prints 0 (captured the old value)

// Without capture list, foo is captured by reference
let refClosure = { print(foo.x) }
foo.x = 200
refClosure()  // prints 200

Escaping e Autoclosure

@autoclosure envolve uma expressão em uma closure sem argumentos automaticamente, então chamadores escrevem a expressão diretamente (sem chaves). Isso permite avaliação preguiçosa—assert() usa para pular a condição em builds de release. Combinado com @escaping, você pode adiar a avaliação. Os operadores && e || usam @autoclosure para short-circuit evaluation. Use @autoclosure com moderação—esconde que código é adiado, o que pode confundir leitores.

swift
// @autoclosure: wraps an expression in a closure automatically
func log(_ condition: @autoclosure () -> Bool, _ message: String) {
    if condition() {  // evaluated here, not at call
        print(message)
    }
}
log(2 > 1, "two is greater")  // no { } needed

// assert uses @autoclosure to avoid evaluating in release
assert(isValid, "Invalid state")  // isValid not checked in release

// @autoclosure + @escaping
func deferred(_ block: @autoclosure @escaping () -> Int) {
    DispatchQueue.main.async { print(block()) }
}
deferred(expensiveComputation())  // runs later, on main

// Short-circuit evaluation with @autoclosure
func &&(lhs: @autoclosure () -> Bool, rhs: @autoclosure () -> Bool) -> Bool {
    return lhs() ? rhs() : false  // rhs not evaluated if lhs is false
}

Funções de Ordem Superior

Funções de ordem superior do Swift (map, filter, reduce, flatMap, compactMap) permitem programação funcional em coleções. map transforma, filter seleciona, reduce agrega, flatMap achatá, compactMap remove nils. Esses retornam novas coleções (imutabilidade). Encadeamento compõe operações. Para grandes conjuntos de dados, use .lazy para evitar arrays intermediários. Esses são a base do Swift funcional—prefira-os a loops imperativos for ao transformar dados.

swift
let nums = [1, 2, 3, 4, 5, 6]

// map: transform each element
let doubled = nums.map { $0 * 2 }  // [2, 4, 6, 8, 10, 12]

// filter: keep elements matching predicate
let evens = nums.filter { $0 % 2 == 0 }  // [2, 4, 6]

// reduce: combine all into one
let sum = nums.reduce(0, +)  // 21
let product = nums.reduce(1, *)  // 720

// flatMap: transform + flatten
let nested = [[1, 2], [3, 4]]
let flat = nested.flatMap { $0 }  // [1, 2, 3, 4]

// compactMap: transform + filter nils
let strings = ["1", "abc", "3"]
let ints = strings.compactMap { Int($0) }  // [1, 3]

// Chaining (lazy evaluation)
let result = nums
    .filter { $0 % 2 == 0 }
    .map { $0 * $0 }
    .reduce(0, +)  // 4 + 16 + 36 = 56

Closures como Completion Handlers

Completion handlers eram o padrão async padrão antes de async/await (Swift 5.5). São @escaping (executam depois) e frequentemente usam Result para tratamento de erro. A desvantagem é callbacks aninhados ('pyramid of doom'). async/await torna isso linear e legível. Novo código deve usar async/await; completion handlers permanecem para APIs delegate e interop com Objective-C. Você pode envolver completion handlers com withCheckedContinuation para usá-los com async/await.

swift
// Completion handler pattern (pre-async/await)
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, _, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        guard let data = data,
              let user = try? JSONDecoder().decode(User.self, from: data) else {
            completion(.failure(DecodeError()))
            return
        }
        completion(.success(user))
    }.resume()
}

// Usage (pyramid of doom)
fetchUser(id: 42) { result in
    switch result {
    case .success(let user):
        fetchAvatar(user) { avatarResult in
            // nested...
        }
    case .failure(let error):
        print(error)
    }
}

// Modern: async/await replaces this
func fetchUserAsync(id: Int) async throws -> User {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}
16

Property Wrappers

Definindo um Property Wrapper

Property wrappers (@propertyWrapper) encapsulam comportamento de propriedade reutilizável. A propriedade wrappedValue é com o que os usuários interagem. O init recebe o valor inicial e quaisquer argumentos personalizados. SwiftUI usa property wrappers extensivamente: @State, @Binding, @Published, @AppStorage. Defina o seu próprio para validação, cache, logging ou valores padrão. O wrapper é uma struct/class que gerencia o armazenamento.

swift
@propertyWrapper
struct Clamped<Value: Comparable> {
    var value: Value
    let range: ClosedRange<Value>

    var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
        self.range = range
    }
}

// Usage
struct Slider {
    @Clamped(0...100) var progress: Int = 50
}
var s = Slider()
s.progress = 150  // clamped to 100
s.progress = -10  // clamped to 0
print(s.progress)  // 0

Projected Value

O projectedValue (acessado via $) é uma interface secundária para a propriedade. SwiftUI usa isso intensivamente: o $ do @State fornece um Binding para views filhas, o $ do @Published fornece um Publisher. Defina projectedValue quando o wrapper deve expor algo além do valor envolvido (um binding, um publisher, o próprio wrapper). A sintaxe de prefixo $ torna isso ergonômico. Nem todos os wrappers precisam de um projectedValue—é opcional.

swift
@propertyWrapper
struct Observable<T> {
    var value: T
    var wrappedValue: T {
        get { value }
        set { value = newValue; onChange() }
    }
    var onChange: () -> Void = {}

    // projectedValue: accessed via $ (like @State's binding)
    var projectedValue: Observable<T> { self }
}

struct View {
    @Observable var count = 0
    // count: the wrapped value (Int)
    // $count: the projected value (Observable<Int>)
}

// SwiftUI's @State projects a Binding
struct Counter: View {
    @State var value = 0
    var body: some View {
        Stepper("Value: \(value)", value: $value)  // Binding<Int>
    }
}

Property Wrappers Integrados

SwiftUI fornece muitos property wrappers: @State (estado local), @Binding (estado do pai), @ObservedObject/@StateObject (modelo externo), @EnvironmentObject (modelo injetado), @AppStorage (UserDefaults), @SceneStorage (restauração de cena), @FocusState (foco do teclado), @ScaledMetric (Dynamic Type), @Namespace (animações). Cada um gerencia armazenamento e ciclo de vida. O @Published do Combine dispara atualizações de UI. Saber qual usar quando é chave para SwiftUI.

swift
import SwiftUI
import Combine

struct ContentView: View {
    @State var count = 0  // local view state

    @Binding var externalValue: Int  // parent-owned

    @EnvironmentObject var model: AppModel  // injected

    @AppStorage("username") var username = ""  // UserDefaults

    @SceneStorage("draft") var draft = ""  // per-scene storage

    @FocusState var isFocused: Bool  // focus management

    @ScaledMetric var size = 16  // Dynamic Type scaling

    @Namespace var animationNamespace  // matched geometry

    // Combine
    @Published var data = [Item]()  // in ObservableObject
}

// @AppStorage auto-syncs with UserDefaults
// @SceneStorage restores state per scene (iPad multitasking)

Wrappers Personalizados para Validação

Property wrappers brilham para cross-cutting concerns. NonEmpty valida em set. Cached computa preguiçosamente e memoiza. Outras ideias: Logged (registra mudanças), Trimmed (remove whitespace), Formatted (formata em get), Clamped (limites de intervalo), UserDefaults-backed. Wrappers reduzem boilerplate—você escreve a lógica uma vez e a aplica com @. Eles compõem: @Logged @Clamped(0...100) var value. Mantenha wrappers focados em uma preocupação.

swift
@propertyWrapper
struct NonEmpty {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set {
            guard !newValue.isEmpty else {
                print("Warning: cannot set to empty")
                return
            }
            value = newValue
        }
    }
    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue
    }
}

@propertyWrapper
struct Cached<T> {
    private var cached: T?
    private var generator: () -> T
    var wrappedValue: T {
        mutating get {
            if cached == nil { cached = generator() }
            return cached!
        }
    }
    init(wrappedValue: @autoclosure @escaping () -> T) {
        self.generator = wrappedValue
    }
}

struct Config {
    @NonEmpty var name: String = "default"
    @Cached var expensiveValue: Int = computeExpensive()
}

Property Wrappers em Protocols e Generics

Property wrappers podem ser genéricos e usados em protocols. No entanto, eles têm limitações: uma struct com propriedades wrapped não obtém memberwise initializers (você deve escrever init manualmente), e cópia pode ser complicada (o wrapper é copiado, incluindo seu estado). Para classes, isso é menos um problema. Apesar das limitações, wrappers são poderosos para comportamento de propriedade declarativo e reutilizável. O design do SwiftUI depende fortemente deles.

swift
// Apply to protocol requirements
protocol Configurable {
    @NonEmpty var name: String { get set }
}

// Generic property wrapper
@propertyWrapper
struct Validated<T> {
    private var value: T?
    let validator: (T) -> Bool
    var wrappedValue: T? {
        get { value }
        set {
            if let v = newValue, validator(v) { value = v }
        }
    }
    init(wrappedValue: T?, validator: @escaping (T) -> Bool) {
        self.validator = validator
        self.wrappedValue = wrappedValue
    }
}

struct Form {
    @Validated(validator: { $0.count >= 8 }) var password: String? = nil
}

// Limitation: property wrappers in structs can't be
// initialized from another instance easily (copy issues).
// They work best as stored properties, not computed.
17

Combine Framework

Publishers e Subscribers

Combine é o framework reativo da Apple (como RxSwift). Publishers emitem valores; Subscribers os recebem. sink cria um subscriber com closures. assign vincula a saída a uma propriedade. Operators (map, filter, reduce) transformam publishers declarativamente. Combine é declarativo—descreva o pipeline, e os valores fluem através. É usado para operções async, bindings de UI e tratamento de eventos. Swift moderno prefere async/await, mas Combine permanece para pipelines complexos.

swift
import Combine

// Publisher: emits values over time
let publisher = [1, 2, 3].publisher

// Subscriber: receives values
publisher.sink { value in
    print(value)  // 1, 2, 3
}

// With completion
publisher.sink(receiveCompletion: { completion in
    print("done: \(completion)")
}, receiveValue: { value in
    print(value)
})

// Assign to a property
class ViewModel {
    var data: String = ""
}
let vm = ViewModel()
Just("hello").assign(to: &vm.data)

// Operators transform publishers
[1, 2, 3, 4].publisher
    .map { $0 * 2 }
    .filter { $0 > 4 }
    .sink { print($0) }  // 6, 8

@Published e ObservableObject

@Published envolve uma propriedade e emite seu novo valor para subscribers sempre que muda. O prefixo $ acessa o publisher. Isso faz a ponte entre Combine e SwiftUI—@Published em um ObservableObject dispara atualizações de view. Subscriptions são canceladas quando o AnyCancellable é desalocado, então armazene-os (ex.: em um Set). Use Combine para debouncing, throttling, combinar múltiplas fontes async—coisas que async/await lida menos elegantemente.

swift
class UserStore: ObservableObject {
    @Published var name: String = ""  // publishes on change
    @Published var age: Int = 0

    // $name is a Publisher<String, Never>
    func demo() {
        $name.sink { print("name changed to \($0)") }
        name = "Alice"  // prints "name changed to Alice"
    }
}

// Combine with SwiftUI
struct ProfileView: View {
    @ObservedObject var store: UserStore
    var body: some View {
        Text(store.name)  // updates when name changes
    }
}

// Cancellable: store subscriptions to keep them alive
class Service {
    var cancellables = Set<AnyCancellable>()
    let store = UserStore()

    init {
        store.$name
            .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
            .sink { name in saveToServer(name) }
            .store(in: &cancellables)  // keep alive
    }
}

Operators

Operators do Combine transformam, filtram, combinam e controlam o tempo de publishers. map/scan transformam; filter/selectors escolhem; merge/zip/combineLatest combinam múltiplos streams; debounce/throttle controlam o tempo. Operators retornam novos publishers (imutabilidade, encadeamento). debounce espera por período quieto (search-as-you-type); throttle limita a taxa (taps de botão). collect reúne todos os valores em um array. Esses permitem pipelines reativos declarativos.

swift
let publisher = [1, 2, 3, 4, 5].publisher

// Transforming
publisher.map { $0 * 2 }  // [2, 4, 6, 8, 10]
publisher.scan(0, +)  // [1, 3, 6, 10, 15] (running sum)

// Filtering
publisher.filter { $0 % 2 == 0 }  // [2, 4]
publisher.removeDuplicates()  // consecutive dupes
publisher.first()  // just 1

// Combining
let p1 = [1, 2].publisher
let p2 = [3, 4].publisher
p1.merge(with: p2)  // interleaved: 1, 2, 3, 4 (or any order)
p1.zip(p2)  // pairs: (1,3), (2,4)
p1.combineLatest(p2)  // latest of each

// Timing
publisher.delay(for: 1, scheduler: RunLoop.main)
publisher.throttle(for: 1, scheduler: .main, latest: true)
publisher.debounce(for: 0.5, scheduler: .main)
publisher.collect()  // gather all into [1,2,3,4,5]

Subjects (Publicação Manual)

Subjects são publishers mutáveis aos quais você pode enviar valores manualmente—fazendo a ponte entre código imperativo e reativo. PassthroughSubject transmite sem armazenar (event stream). CurrentValueSubject armazena o valor mais recente (estado). Novos subscribers de CurrentValueSubject imediatamente obtêm o valor atual. Use Subjects para envolver delegates, notifications ou eventos de UI em pipelines Combine. @Published é essencialmente um CurrentValueSubject integrado com ObservableObject.

swift
import Combine

// PassthroughSubject: broadcasts to subscribers, no current value
let subject = PassthroughSubject<Int, Never>()
subject.sink { print($0) }  // subscriber 1
subject.send(1)  // prints 1
subject.send(2)  // prints 2

// CurrentValueSubject: holds current value, new subs get it
let current = CurrentValueSubject<Int, Never>(0)
current.sink { print($0) }  // prints 0 (current)
current.send(1)  // prints 1
current.sink { print($0) }  // prints 1 (current)
current.value  // 1

// Use cases:
// - PassthroughSubject: events (button taps, notifications)
// - CurrentValueSubject: state (like @Published but more control)
// - Bridge imperative code to Combine

// Finish a subject
subject.send(completion: .finished)

Tratamento de Erros

Publishers do Combine têm um tipo Failure (Never para não pode falhar). catch substitui um erro por um publisher fallback. retry reinscreve em falha (bom para redes instáveis). mapError transforma o tipo de erro. assertNoFailure crasha se um erro ocorrer (use quando tem certeza). Publishers com Failure == Never podem ser usados em qualquer lugar; aqueles com erros precisam de tratamento. Isso torna o tratamento de erro explícito e composável em pipelines reativos.

swift
enum APIError: Error { case network, parse }

// Publishers can fail with an error
let publisher = Fail<Int, APIError>(error: .network)

// catch: replace error with fallback publisher
publisher
    .catch { _ in Just(0) }  // fallback to 0
    .sink { print($0) }  // 0

// retry: retry on failure
URLSession.shared.dataTaskPublisher(for: url)
    .retry(3)  // retry up to 3 times
    .sink(receiveCompletion: { _ in }, receiveValue: { _ in })

// mapError: transform error type
publisher
    .mapError { _ in NSError(domain: "x", code: 1) }

// assertNoFailure: crash if error (when you're sure it won't)
Just(1).assertNoFailure().sink { print($0) }

// Result publisher
let resultPublisher = [1, 2, 3].publisher.setFailureType(to: Error.self)
18

Controle de Acesso & Organização de Código

Níveis de Acesso

Swift tem cinco níveis de acesso. private (menor escopo, dentro da declaração), fileprivate (dentro do arquivo fonte), internal (dentro do módulo, padrão), public (qualquer um que importe), open (public + subclassable/overridable, apenas classes). Swift 5.9 adiciona package para módulos SPM. Comece restritivo e amplie conforme necessário. Use public para APIs de biblioteca, internal para código de app, private para detalhes de implementação. open é para classes de framework projetadas para subclassing.

swift
// public: accessible from any module that imports this
public class PublicAPI {
    public func connect() {}
}

// internal (default): accessible within the module
class InternalClass {
    func helper() {}  // internal by default
}

// fileprivate: accessible within the source file
fileprivate func fileHelper() {}

// private: accessible within the enclosing declaration
class Counter {
    private var count = 0  // only Counter can access
    fileprivate func reset() { count = 0 }  // same file
}

// Swift 5.9: package (for Swift Package Manager)
package struct PackageType {
    package var value: Int
}

// Guidelines:
// - Start restrictive (private), open up as needed
// - public for framework API, internal for implementation
// - private for encapsulation within a type

Extensions e Organização

Extensions permitem dividir a funcionalidade de um tipo em arquivos (String+Validation.swift, String+Parsing.swift) e adicionar conformidade retroativamente. Isso mantém os arquivos focados e gerenciáveis. A conformidade com protocol pode estar em uma extension/arquivo separado da definição principal do tipo. Conformidade condicional (where Element: ...) adiciona conformidade condicionalmente. Use extensions para organizar: mantenha o tipo principal mínimo, adicione funcionalidade em arquivos de extensão focados.

swift
// Split a type across files with extensions
// String+Validation.swift
extension String {
    var isValidEmail: Bool {
        contains("@") && contains(".")
    }
}

// String+Parsing.swift
extension String {
    func toURL() -> URL? { URL(string: self) }
}

// Conform to protocols in extensions
extension String: Comparable {  // already conforms, just showing
    // protocol methods
}

// Conditional conformance
extension Array: MyProtocol where Element: MyProtocol {}

// Protocol conformance in a separate file
// (keeps the main type definition clean)
struct User { let id: Int; let name: String }

extension User: Codable {}  // synthesized
extension User: Equatable {}  // synthesized
extension User: CustomStringConvertible {
    var description: String { "User(\(id), \(name))" }
}

Modules e Packages

Swift Package Manager (SPM) define módulos via Package.swift. Cada target é um módulo—acesso internal é dentro de um target, public cruza módulos. Dependencies são outros packages (git URLs). Products são o que seu package expõe. SPM é a maneira moderna de gerenciar código Swift (substituindo CocoaPods/Carthage). Xcode integra SPM nativamente. Estrutura: Sources/MyLibrary/ para código, Tests/MyLibraryTests/ para testes. Use módulos para separar preocupações e controlar o acesso.

swift
// Package.swift (Swift Package Manager)
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyLibrary",
    products: [
        .library(name: "MyLibrary", targets: ["MyLibrary"]),
    ],
    dependencies: [
        .package(url: "https://github.com/.../Logging", from: "1.0.0"),
    ],
    targets: [
        .target(name: "MyLibrary", dependencies: ["Logging"]),
        .testTarget(name: "MyLibraryTests", dependencies: ["MyLibrary"]),
    ]
)

// Importing modules
import Foundation
import MyLibrary  // your module
import Logging    // dependency

// Each target is a module
// internal access is within a target/module
// public access crosses module boundaries

Initializers e Designated/Convenience

Classes têm designated initializers (init completo, deve chamar designated do super) e convenience initializers (delegam para outro init na mesma classe). Esse sistema de dois níveis garante inicialização completa. Structs obtêm um memberwise initializer de graça. Failable initializers (init?) retornam nil em falha. Required initializers (required init) devem ser implementados por subclasses. Para a maioria do código, prefira structs (inicialização mais simples) a classes.

swift
class Person {
    let name: String
    let age: Int

    // Designated initializer: fully initializes
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

class Employee: Person {
    let employeeId: String

    // Designated init must call super's designated init
    init(name: String, age: Int, employeeId: String) {
        self.employeeId = employeeId
        super.init(name: name, age: age)  // must call super
    }

    // Convenience init must call another init (same class)
    convenience init(name: String) {
        self.init(name: name, age: 0, employeeId: "TEMP")
    }
}

// Structs: memberwise initializer (free)
struct Point { let x, y: Int }
let p = Point(x: 1, y: 2)  // synthesized

// Failable initializers
struct Config {
    let port: Int
    init?(port: Int) {
        guard port > 0 else { return nil }
        self.port = port
    }
}

Optionals Aprofundado

Optionals são o mecanismo de null-safety do Swift—um enum que é .some(value) ou .none (nil). Você deve fazer unwrap para acessar o valor. Optional chaining (?.) navega com segurança; nil-coalescing (??) fornece um padrão; if let/guard let fazem unwrap com segurança. Evite ! (force unwrap) a menos que tenha certeza—crasha em nil. Optionals forçam você a lidar com a ausência explicitamente, eliminando null-reference exceptions comuns em outras linguagens.

swift
// Optional: enum with .some(Value) and .none
let maybeInt: Int? = 42
let nilInt: Int? = nil

// Optional chaining (short-circuits on nil)
let name: String? = user?.profile?.name  // nil if any link is nil

// Nil-coalescing (default value)
let value = maybeInt ?? 0  // 42
let value2 = nilInt ?? 0   // 0

// Forced unwrapping (crashes if nil)
let forced = maybeInt!  // 42
// let crash = nilInt!   // RUNTIME CRASH

// Optional binding
if let actual = maybeInt {
    print(actual)  // 42, actual is Int (not optional)
}

// Guard for early exit
func process(_ data: Data?) {
    guard let data = data else { return }
    // data is non-optional here
}

// Optional try
let result = try? parse(json)  // nil on error
let result2 = try! parse(json) // crashes on error

// Multiple binding
if let a = optA, let b = optB, a < b { /* ... */ }
19

Gerenciamento de Memória (ARC/weak)

Básico de ARC

Automatic Reference Counting (ARC) rastreia referências fortes. Quando a contagem chega a zero, o objeto é desalocado. ARC é determinístico (diferentemente de garbage collection): deinit executa imediatamente quando a última referência é liberada. Na maioria do tempo, ARC simplesmente funciona. Problemas surgem com ciclos de referência: dois objetos referenciam fortemente um ao outro, impedindo a desalocação. Use weak ou unowned para quebrar ciclos.

swift
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

var reference1: Person?
var reference2: Person?

reference1 = Person(name: "Alice")
// Prints: Alice is being initialized

reference2 = reference1  // Strong reference count: 2

reference1 = nil  // Count: 1
reference2 = nil  // Count: 0, deinit runs
// Prints: Alice is being deinitialized

Referências Weak

Referências weak não mantêm o objeto referenciado vivo. Quando o objeto é desalocado, referências weak automaticamente se tornam nil. weak deve ser optional (var tenant: Person?). Use weak quando o objeto referenciado tem um tempo de vida mais curto (o inquilino pode sair do apartamento). O caso de uso clássico é o padrão delegate: o delegate é weak para evitar ciclos. weak tem leve sobrecarga (registrado com o runtime para nil-setting).

swift
class Person {
    var name: String
    var apartment: Apartment?  // Strong (person owns apartment)
    init(name: String) { self.name = name }
    deinit { print("\(name) deinit") }
}

class Apartment {
    var unit: String
    weak var tenant: Person?  // Weak (breaks cycle)
    init(unit: String) { self.unit = unit }
    deinit { print("Apartment \(unit) deinit") }
}

var alice: Person? = Person(name: "Alice")
var apt: Apartment? = Apartment(unit: "4A")

alice!.apartment = apt
apt!.tenant = alice  // Weak reference, no cycle

alice = nil  // Alice deinit (apartment's tenant becomes nil)
apt = nil    // Apartment deinit

// weak must be optional (becomes nil when deallocated)

Referências Unowned

Referências unowned, como weak, não mantêm o objeto vivo. Diferente de weak, unowned é non-optional e não se torna nil. Acessar uma referência unowned após desalocação crasha. Use unowned quando pode garantir que o objeto referenciado sobrevive à referência (ex.: um cartão de crédito não pode existir sem seu cliente). unowned tem menos sobrecarga que weak. Escolha: weak para tempos de vida incertos, unowned para tempos de vida garantidos.

swift
class Customer {
    let name: String
    var card: CreditCard?  // Strong
    init(name: String) { self.name = name }
    deinit { print("\(name) deinit") }
}

class CreditCard {
    let number: UInt
    unowned let customer: Customer  // Non-optional, no cycle
    init(number: UInt, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card \(number) deinit") }
}

var john: Customer? = Customer(name: "John")
john!.card = CreditCard(number: 1234, customer: john!)

john = nil
// Customer deinit, then CreditCard deinit

// unowned: non-optional, assumed to always have a value
// CRASH if accessed after deallocation
// Use when the referenced object has same or longer lifetime

Closures & Capture Lists

Closures capturam referências fortes por padrão, causando ciclos quando a closure é armazenada em self. Capture lists ([weak self] ou [unowned self]) quebram o ciclo. weak self exige tratamento optional (guard let self = self). Capture valores específicos (let name = self.name) para evitar capturar self inteiramente. Closures passadas para funções (não armazenadas) não causam ciclos. Sempre use capture lists para closures armazenadas que referenciam self.

swift
class ViewController {
    var name = "MyVC"
    var onComplete: (() -> Void)?

    // BAD: Strong reference cycle
    func setupBad() {
        onComplete = {
            print("\(self.name) completed")  // Captures self strongly
        }
    }

    // GOOD: Capture list breaks cycle
    func setupGood() {
        onComplete = { [weak self] in
            guard let self = self else { return }
            print("\(self.name) completed")
        }
    }

    // unowned for guaranteed lifetime
    func setupUnowned() {
        onComplete = { [unowned self] in
            print("\(self.name) completed")
        }
    }

    // Capture specific values
    func setupCapture() {
        let name = self.name  // Capture value, not self
        onComplete = {
            print("\(name) completed")
        }
    }

    deinit { print("VC deinit") }
}

Detectando & Corrigindo Vazamentos

Fontes comuns de vazamento: delegates armazenados strong (use weak), Timer retendo target (invalidate em deinit), observers de NotificationCenter (remova em deinit). O Xcode Memory Graph Debugger visualiza grafos de objetos e destaca vazamentos. Execute com MallocStackLogging para obter traces de alocação. Teste deinit definindo referências para nil e verificando se deinit executa. Instruments (ferramenta Leaks) detecta vazamentos em tempo de execução. Sempre pareie setup com teardown.

swift
// Xcode memory graph debugger: visualize reference cycles
// Product > Debug > View Memory Graph Hierarchy

// Common leak patterns:
// 1. Delegate stored as strong
class View {
    weak var delegate: ViewDelegate?  // MUST be weak
}

// 2. Timer retains target
class TimerHolder {
    var timer: Timer?
    func start() {
        // BAD: timer retains self via #selector
        timer = Timer.scheduledTimer(timeInterval: 1,
            target: self, selector: #selector(fire),
            userInfo: nil, repeats: true)
    }
    @objc func fire() {}
    deinit { timer?.invalidate() }  // MUST invalidate
}

// 3. NotificationCenter observer
class Observer {
    init() {
        NotificationCenter.default.addObserver(
            self, selector: #selector(handle),
            name: .someNotification, object: nil)
    }
    @objc func handle() {}
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

// Use Xcode's Debug Memory Graph to find leaks
20

Actors & async/await

Básico de async/await

async/await (Swift 5.5+) faz código assíncrono parecer síncrono. async marca funções que podem suspender; await marca pontos de suspensão. Tasks fazem a ponte de sync para contextos async. async let inicia uma tarefa concorrente; await coleta o resultado. Use async let para execução paralela, await regular para sequencial. Erros se propagam com throws/try. O compilador impõe await em pontos de suspensão. async/await substitui completion handlers e Combine para muitos casos de uso.

swift
// Async function
func fetchUser(id: Int) async throws -> User {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Calling async functions
func loadProfile() async {
    do {
        let user = try await fetchUser(id: 1)
        print("Loaded: \(user.name)")
    } catch {
        print("Error: \(error)")
    }
}

// Task: bridge async to sync context
Task {
    await loadProfile()
}

// Concurrent execution
func loadMultiple() async {
    async let user1 = fetchUser(id: 1)
    async let user2 = fetchUser(id: 2)
    async let user3 = fetchUser(id: 3)

    let users = try await [user1, user2, user3]
    print("Loaded \(users.count) users")
}

// Sequenced vs concurrent
let sequential = try await fetchUser(id: 1)  // Waits
let concurrent = try await fetchUser(id: 2)  // Then waits again

Actors

Actors (Swift 5.5+) são tipos de referência com acesso serializado, prevenindo data races. Apenas um método executa em um actor por vez—sem locking manual necessário. Métodos de actor devem ser chamados com await (eles podem suspender). Propriedades são isoladas e não podem ser acessadas diretamente de fora. Actors são o padrão seguro para estado mutável compartilhado em código concorrente. Use actors em vez de classes com locks para a maioria do estado concorrente.

swift
// Actor: reference type with serialized access
actor BankAccount {
    private var balance: Decimal

    init(balance: Decimal) {
        self.balance = balance
    }

    func deposit(_ amount: Decimal) {
        balance += amount
    }

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

    func getBalance() -> Decimal { balance }
}

// Usage (must be async)
let account = BankAccount(balance: 100)
Task {
    await account.deposit(50)
    let balance = await account.getBalance()
    print("Balance: \(balance)")  // 150
}

// Actors prevent data races automatically
// Only one method runs at a time per actor instance
// No need for locks or queues

Actor Isolation

Actor isolation protege o estado. Membros nonisolated podem ser acessados sem await (para valores puros ou métodos que não tocam estado isolado). @MainActor é um actor global para isolamento de main-thread (atualizações de UI). @globalActor cria actors globais personalizados. Cruzar boundaries de actor exige await. O compilador verifica o isolamento em tempo de compilação, prevenindo data races. Use @MainActor para ViewModels e código de UI; actors personalizados para isolamento específico de domínio.

swift
actor Counter {
    private var count = 0

    // Isolated method (default)
    func increment() { count += 1 }

    // Nonisolated: can be called without await
    nonisolated var description: String { "Counter" }

    // nonisolated function
    nonisolated func id() -> String { "counter-\(ObjectIdentifier(self))" }
}

// Global actor: @MainActor runs on main thread
@MainActor
class ViewModel: ObservableObject {
    @Published var data: [Item] = []

    func loadData() async {
        // Always runs on main thread
        data = try await fetchItems()
    }
}

// Mark specific functions
class Service {
    @MainActor
    func updateUI(_ result: Result) {
        // UI updates on main thread
    }
}

// globalActor lets you apply to types, functions, properties
@globalActor
actor MyActor {
    static let shared = MyActor()
}

Concorrência Estruturada

Task groups permitem computação paralela dinâmica. addTask gera tarefas filhas; for await group coleta resultados conforme completam. Task groups impõem concorrência estruturada: todos os filhos completam antes de o pai continuar. Cancelamento se propaga de pai para filhos automaticamente. Verifique Task.isCancelled em tarefas de longa duração. withThrowingTaskGroup propaga erros, cancelando siblings em throw. Use task groups para padrões fan-out/fan-in.

swift
// TaskGroup: dynamic parallel computation
func fetchAllUsers(ids: [Int]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask { try await fetchUser(id: id) }
        }

        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

// Task tree: parent-child relationship
func process() async {
    await withTaskGroup(of: Void.self) { group in
        for i in 1...5 {
            group.addTask {
                await processItem(i)
            }
        }
        // All tasks complete before group returns
    }
    print("All done")
}

// Cancellation
func longRunning() async {
    await withTaskGroup(of: Void.self) { group in
        group.addTask {
            for i in 1...100 {
                if Task.isCancelled { return }
                await doWork(i)
            }
        }
    }
}

// Cancel from outside
let task = Task { await longRunning() }
task.cancel()

AsyncSequence & Streams

AsyncSequence é a versão async de Sequence, permitindo loops for await. AsyncStream faz a ponte de APIs baseadas em callback ou delegate para async/await. yield emite valores, finish termina. onTermination limpa recursos. AsyncThrowingStream suporta erros. Use AsyncSequence para APIs paginadas, dados em tempo real ou qualquer stream de valores ao longo do tempo. A biblioteca padrão fornece .lines em URL para leitura de arquivo linha por linha. AsyncSequence se integra com cancelamento de task.

swift
// AsyncSequence: iterate asynchronously
struct Counter: AsyncSequence {
    struct AsyncIterator: AsyncIteratorProtocol {
        var current: Int
        let max: Int
        mutating func next() async -> Int? {
            guard current <= max else { return nil }
            defer { current += 1 }
            try? await Task.sleep(nanoseconds: 100_000_000)
            return current
        }
    }

    let start: Int
    let max: Int

    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator(current: start, max: max)
    }
}

// Usage
for await num in Counter(start: 1, max: 5) {
    print(num)  // 1 2 3 4 5 (with delays)
}

// AsyncStream: bridge callbacks to async
func urlLines(_ url: URL) -> AsyncThrowingStream<String, Error> {
    AsyncThrowingStream { continuation in
        let task = Task {
            do {
                for try await line in url.lines {
                    continuation.yield(line)
                }
                continuation.finish()
            } catch {
                continuation.finish(throwing: error)
            }
        }
        continuation.onTermination = { _ in task.cancel() }
    }
}
21

Testing

Noções Básicas do XCTest

XCTest é a estrutura de testes padrão. XCTestCase agrupa testes relacionados. setUp/tearDown são executados antes/depois de cada teste. Asserções: XCTAssertEqual, XCTAssertNil, XCTAssertThrowsError, XCTAssertTrue. @testable import acessa símbolos internos. Os testes são executados na thread principal por padrão. Use XCTAssertNotNil para opcionais. Execute testes com Cmd+U. O Xcode mostra os resultados dos testes inline. Escreva testes junto com o código para feedback rápido.

swift
import XCTest
@testable import MyApp

class UserTests: XCTestCase {
    var user: User!

    override func setUp() {
        super.setUp()
        user = User(name: "Alice", age: 30)
    }

    override func tearDown() {
        user = nil
        super.tearDown()
    }

    func testName() {
        XCTAssertEqual(user.name, "Alice")
    }

    func testAgeAfterBirthday() {
        user.haveBirthday()
        XCTAssertEqual(user.age, 31)
    }

    func testValidation() {
        XCTAssertThrowsError(try User(name: "", age: -1)) { error in
            guard let error = error as? UserError else {
                XCTFail("Wrong error type")
                return
            }
            XCTAssertEqual(error, .invalidName)
        }
    }

    func testOptional() {
        XCTAssertNotNil(user.email)
        XCTAssertNil(user.address)
    }
}

// Run: Cmd+U in Xcode

Testes Assíncronos

Swift 5.5+ suporta métodos de teste assíncronos diretamente (async throws func test...). Use try await dentro dos testes. Para código mais antigo, use XCTestExpectation: crie uma expectation, fulfill() no callback, wait(for:timeout:). Defina timeouts razoáveis para detectar travamentos. Para testar sequências assíncronas, use loops for await. Simule chamadas de rede com URLProtocol ou injeção de dependência. Teste tanto caminhos de sucesso quanto de falha. Testes assíncronos são executados concorrentemente por padrão—use o trait .serialized se a ordem importar.

swift
class APITests: XCTestCase {
    func testFetchUser() async throws {
        let user = try await fetchUser(id: 1)
        XCTAssertEqual(user.name, "Alice")
    }

    func testFetchFailure() async {
        do {
            _ = try await fetchUser(id: -1)
            XCTFail("Should have thrown")
        } catch {
            // Expected
        }
    }

    // Old style with expectations (still works)
    func testAsyncWithExpectation() {
        let expectation = XCTestExpectation(description: "Fetch completes")

        fetchUserAsync { result in
            switch result {
            case .success(let user):
                XCTAssertEqual(user.name, "Alice")
            case .failure:
                XCTFail("Should succeed")
            }
            expectation.fulfill()
        }

        wait(for: [expectation], timeout: 5)
    }
}

Mocks e Stubs

A injeção de dependência permite testes: defina protocolos para dependências, injete mocks nos testes. MockNetworkService retorna resultados predeterminados. Teste casos de sucesso e falha definindo mock.fetchUserResult. Esse padrão desacopla os testes da rede, tornando-os rápidos e determinísticos. Para mocks complexos, use bibliotecas como Cuckoo ou Mockingbird. Teste a API pública dos seus tipos, não detalhes de implementação. Mocks devem ser simples e focados.

swift
// Protocol for dependency injection
protocol NetworkService {
    func fetchUser(id: Int) async throws -> User
}

// Production implementation
class APIService: NetworkService {
    func fetchUser(id: Int) async throws -> User {
        // Real network call
    }
}

// Mock for testing
class MockNetworkService: NetworkService {
    var fetchUserResult: Result<User, Error>?

    func fetchUser(id: Int) async throws -> User {
        switch fetchUserResult {
        case .success(let user): return user
        case .failure(let error): throw error
        case .none: throw URLError(.badURL)
        }
    }
}

class ViewModelTests: XCTestCase {
    func testLoadUser() async throws {
        let mock = MockNetworkService()
        mock.fetchUserResult = .success(User(name: "Alice", age: 30))

        let vm = ViewModel(service: mock)
        await vm.load()

        XCTAssertEqual(vm.userName, "Alice")
    }
}

Testes de UI

Testes de UI automatizam interações do usuário via identificadores de acessibilidade. XCUIApplication inicia o app. Encontre elementos por identificador de acessibilidade (definido no código com .accessibilityIdentifier). Ações: tap, typeText, swipeLeft/Right. Asserções: waitForExistence, exists, hittable. Testes de UI são mais lentos que testes de unidade, mas capturam bugs de integração. Defina identificadores de acessibilidade no SwiftUI com .accessibilityIdentifier("Email"). Execute testes de UI em múltiplos dispositivos/simuladores para cobertura.

swift
import XCTest

class AppUITests: XCTestCase {
    override func setUpWithError() throws {
        continueAfterFailure = false
        let app = XCUIApplication()
        app.launch()
    }

    func testLoginFlow() {
        let app = XCUIApplication()

        let emailField = app.textFields["Email"]
        let passwordField = app.secureTextFields["Password"]
        let loginButton = app.buttons["Log In"]

        emailField.tap()
        emailField.typeText("[email protected]")

        passwordField.tap()
        passwordField.typeText("password123")

        loginButton.tap()

        // Verify navigation
        XCTAssertTrue(app.staticTexts["Welcome, Alice!"].waitForExistence(timeout: 5))
    }

    func testSwipeAndTap() {
        let cell = app.cells["Item 5"]
        cell.swipeLeft()
        app.buttons["Delete"].tap()
        XCTAssertFalse(cell.exists)
    }
}

Desempenho e Cobertura de Código

measure avalia o desempenho do código, comparando com baselines. Defina baselines no Xcode; os testes falham se a regressão exceder o limite. measureMetrics acompanha métricas específicas (pico de memória, CPU). Ative a cobertura de código no esquema para ver caminhos de código não testados. Busque 80%+ de cobertura em lógica de negócios. Testes de snapshot (swift-snapshot-testing) capturam a UI para regressão visual. Faça profile com Instruments (Time Profiler, Allocations) para análise mais profunda. Testes de desempenho capturam regressões cedo.

swift
class PerformanceTests: XCTestCase {
    func testSortingPerformance() {
        let data = (1...10000).shuffled()

        measure {
            _ = data.sorted()
        }
    }

    func testMemoryUsage() {
        measureMetrics([XCTPerformanceMetric.peakMemory]) {
            _ = (0..<10000).map { _ in [Int](repeating: 0, count: 100) }
        }
    }

    // Baseline comparison
    func testWithBaselines() {
        let options = XCTMeasureOptions()
        options.defaultOptions.measurementUnits = .seconds

        measure(options) {
            // Code to benchmark
        }
    }
}

// Code coverage: enable in Edit Scheme > Test > Options
// View coverage: Cmd+Shift+Y (Report Navigator) > Coverage

// Snapshot testing (third-party)
// func testAppearance() {
//     assertSnapshot(of: view, as: .image)
// }
22

Concorrência do Swift

async/await

async/await (Swift 5.5+) simplifica código assíncrono. async marca funções que podem suspender. await marca pontos de suspensão. Task cria um contexto assíncrono. Muito mais limpo que completion handlers. Erros propagam com try.

swift
func fetchData() async throws -> String {
    let (data, _) = try await URLSession.shared.data(from: url)
    return String(data: data, encoding: .utf8) ?? ""
}
// Call from async context
Task {
    let result = try await fetchData()
    print(result)
}

Actors

actor (Swift 5.5+) é um tipo por referência com exclusão mútua automática. Apenas uma tarefa acessa seu estado por vez. Métodos são implicitamente async quando chamados de fora. Substitui locks e dispatch queues para estado mutável compartilhado. Thread-safe por design.

swift
actor Counter {
    private var count = 0
    func increment() {
        count += 1
    }
    func getValue() -> Int {
        count
    }
}
// Usage
Task {
    let counter = Counter()
    await counter.increment()
    let value = await counter.getValue()
}

Async Sequence

AsyncSequence é o equivalente assíncrono de Sequence. for await itera de forma assíncrona. Útil para streaming de dados (rede, linhas de arquivo). Implemente AsyncIteratorProtocol para criar sequências assíncronas customizadas. O compilador gerencia suspensão e cancelamento.

swift
for try await item in asyncSequence {
    print(item)
}
// Custom AsyncSequence
struct Counter: AsyncSequence {
    struct AsyncIterator: AsyncIteratorProtocol {
        var current = 0
        mutating func next() async -> Int? {
            current += 1
            return current <= 5 ? current : nil
        }
    }
    func makeAsyncIterator() -> AsyncIterator { AsyncIterator() }
}

Task Groups

Task groups executam múltiplas tarefas concorrentemente e coletam resultados. addTask adiciona uma tarefa filha. for await itera resultados conforme são concluídos. Todas as tarefas devem ser concluídas antes que o grupo retorne. Concorrência estruturada: o cancelamento propaga para os filhos. Os resultados são coletados com segurança.

swift
let results = await withTaskGroup(of: Int.self) { group in
    for i in 1...5 {
        group.addTask { i * i }
    }
    var sum = 0
    for await result in group {
        sum += result
    }
    return sum
}

Continuation

withCheckedContinuation faz a ponte entre APIs de completion-handler e async/await. resume(returning:) retoma a função assíncrona. Deve ser chamado exatamente uma vez. withCheckedThrowingContinuation suporta erros. Útil para adotar async/await com APIs existentes.

swift
func fetchWithCompletion(_ completion: @escaping (String) -> Void) {
    // Legacy completion handler API
    completion("data")
}
// Wrap in async
func fetchAsync() async -> String {
    await withCheckedContinuation { continuation in
        fetchWithCompletion { result in
            continuation.resume(returning: result)
        }
    }
}
23

SwiftUI Profundo

View Modifiers

Modifiers envolvem views para mudar aparência ou comportamento. A ordem importa: modifiers posteriores envolvem os anteriores. .padding antes de .background coloca o padding dentro do background. Modifiers retornam novas instâncias de View. Encadeie-os para estilização complexa.

swift
Text("Hello")
    .font(.title)
    .foregroundColor(.blue)
    .padding()
    .background(Color.gray)
    .cornerRadius(8)
    .shadow(radius: 4)

Listas

List exibe linhas roláveis. ForEach gera linhas a partir de dados. onDelete habilita swipe-to-delete. Outros modifiers: onMove, onInsert. Dados Identifiable (propriedade id) são necessários para ForEach. Estilo de List: .plain, .insetGrouped, .sidebar.

swift
List {
    ForEach(items) { item in
        HStack {
            Text(item.name)
            Spacer()
            Text("\(item.price)")
        }
    }
    .onDelete { indexSet in
        items.remove(atOffsets: indexSet)
    }
}
.listStyle(.insetGrouped)

Navegação

NavigationStack gerencia uma pilha de views. NavigationLink empurra um destino. navigationTitle define o título. toolbar adiciona botões à barra de navegação. Para iOS 16+. Use NavigationView para versões mais antigas. Sheets e alerts usam modifiers .sheet e .alert.

swift
NavigationStack {
    List(items) { item in
        NavigationLink(item.name) {
            DetailView(item: item)
        }
    }
    .navigationTitle("Items")
    .toolbar {
        ToolbarItem(placement: .navigationBarTrailing) {
            Button("Add") { addItem() }
        }
    }
}

Gerenciamento de Estado

@State para estado local da view (tipos de valor). @Binding para estado passado. @StateObject para observable objects próprios (criados uma vez). @ObservedObject para observable objects externos. @EnvironmentObject para estado global do app. Mudanças em @State disparam atualizações da view.

swift
struct CounterView: View {
    @State private var count = 0
    @Binding var selected: Int
    @StateObject var viewModel = ViewModel()
    var body: some View {
        Button("\(count)") { count += 1 }
    }
}

Animações

withAnimation anima mudanças de estado. .spring, .easeInOut são curvas de animação. .animation(value:) anima quando o valor muda. .scaleEffect, .opacity, .offset são animáveis. Animações implícitas usam o modifier .animation. Explícitas usam withAnimation.

swift
struct AnimatedView: View {
    @State private var scale: CGFloat = 1.0
    var body: some View {
        Button("Tap") {
            withAnimation(.spring(response: 0.3)) {
                scale = scale == 1 ? 1.5 : 1
            }
        }
        .scaleEffect(scale)
        .animation(.easeInOut(duration: 0.3), value: scale)
    }
}
24

Combine Framework

Publishers e Subscribers

Combine é a estrutura reativa da Apple. Publishers emitem valores ao longo do tempo. Subscribers os recebem. sink cria um subscriber. Just emite um único valor. PassthroughSubject é um publisher manual. Cancellables devem ser retidos ou a assinatura é cancelada. Similar ao RxSwift.

swift
import Combine

let publisher = Just(42)
let cancellable = publisher.sink { value in
    print("Received: \(value)")
}
// Just emits one value then finishes
// PassthroughSubject: manual values
let subject = PassthroughSubject<Int, Never>()
subject.send(1)
subject.send(2)

Operators

Operators transformam a saída do publisher. map transforma valores. filter seleciona valores. debounce atrasa até silenciar. throttle limita a taxa. combineLatest mescla streams. flatMap encadeia publishers. Operators são lazy: nada acontece até que sink assine.

swift
let publisher = (1...10).publisher
    .map { $0 * 2 }
    .filter { $0 > 10 }
    .sink { print($0) }  // 12, 14, 16, 18, 20

// Debounce
let debounced = subject
    .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
    .sink { print($0) }

@Published

@Published expõe uma propriedade como um publisher do Combine. O prefixo $ acessa o publisher. Mudanças emitem novos valores. Funciona com ObservableObject para SwiftUI. O publisher emite o valor atual na assinatura. Útil para atualizações reativas de UI sem notificações manuais.

swift
class ViewModel: ObservableObject {
    @Published var count = 0
}
let vm = ViewModel()
let cancellable = vm.$count.sink { newCount in
    print("Count changed: \(newCount)")
}
vm.count = 1  // Prints "Count changed: 1"
vm.count = 2  // Prints "Count changed: 2"

Future

Future envolve uma API de completion-handler como um publisher. promise(.success) emite um valor. promise(.failure) emite um erro. Future emite exatamente uma vez. Útil para fazer ponte de APIs de callback para o Combine. A closure é executada eagermente a menos que envolvida com .delay ou deferred.

swift
func fetchUser() -> Future<User, Error> {
    Future { promise in
        URLSession.shared.dataTask(with: url) { data, _, error in
            if let error = error { promise(.failure(error)) }
            else { promise(.success(parse(data!))) }
        }.resume()
    }
}
// Usage
fetchUser().sink(receiveCompletion: { _ in }, receiveValue: { user in print(user) })

Tratamento de Erros

Fail emite um erro imediatamente. catch substitui um publisher falho por outro. retry re-assina em caso de falha. assertNoFailure trava em erro (para depuração). O evento completion sinaliza que não há mais valores. Erros propagam downstream a menos que sejam capturados.

swift
let publisher = Fail<Int, MyError>(error: .notFound)
    .catch { error in
        Just(0)  // Fallback value
    }
    .sink { print($0) }  // 0

// retry on failure
let retried = requestPublisher
    .retry(3)
    .sink(receiveCompletion: { _ in }, receiveValue: { _ in })
25

Gerenciamento de Memória

Noções Básicas de ARC

ARC (Automatic Reference Counting) rastreia referências. Quando a contagem chega a zero, deinit é executado e a memória é liberada. Referências strong incrementam a contagem. Ciclos de referência impedem a desalocação. ARC é determinístico (diferente do GC). deinit é executado de forma síncrona quando a última referência é descartada.

swift
class Person {
    let name: String
    init(name: String) { self.name = name }
    deinit { print("\(name) is being deinitialized") }
}
var p1: Person? = Person(name: "Alice")
p1 = nil  // Prints "Alice is being deinitialized"

Referências Weak

Referências weak não incrementam a contagem de retain. Elas são automaticamente definidas como nil quando o objeto é desalocado. Devem ser optional var. Use para delegates e observers para quebrar ciclos. Referências weak são zeroing: seguras de acessar após o objeto ter sido desalocado. A correção mais comum para ciclos de retain.

swift
class View {
    weak var delegate: Delegate?
}
class Delegate {
    var view: View?  // strong
}
// weak: does not keep object alive, auto-set to nil
// Use for delegate patterns to avoid cycles

Referências Unowned

Referências unowned não incrementam a contagem de retain. Diferente de weak, elas são non-optional e não são zeroing. Acessar após desalocação trava. Use quando o objeto referenciado sobrevive ou morre com o referenciador. Mais rápido que weak (sem verificação de nil). Comum em closures que capturam self quando self sobrevive à closure.

swift
class Customer {
    var card: CreditCard?
}
class CreditCard {
    unowned let customer: Customer
    init(customer: Customer) { self.customer = customer }
}
// unowned: does not keep alive, but not optional
// CRASHES if accessed after dealloc
// Use when the other object has same or shorter lifetime

Ciclos de Retain

Ciclos de retain ocorrem quando objetos se referenciam fortemmente. Nenhum é desalocado, vazando memória. Corrija com weak ou unowned. Comum em delegates, observers e closures. Closures capturam self fortemente por padrão. [weak self] ou [unowned self] quebra o ciclo. Use o Memory Graph Debugger para encontrar ciclos.

swift
// BAD: retain cycle
class Node {
    var next: Node?
    var prev: Node?  // strong -> cycle
}
// GOOD: break with weak
class Node {
    var next: Node?
    weak var prev: Node?
}
// Closures: use [weak self]
{ [weak self] in self?.update() }

Closures e Captura

Closures capturam variáveis por referência. Closures armazenadas (escaping) criam ciclos de retain se capturarem self. [weak self] torna a captura optional e weak. @escaping marca closures que sobrevivem à chamada da função. Closures non-escaping (padrão) não podem causar ciclos. O compilador avisa sobre ciclos potenciais.

swift
class Timer {
    var handler: (() -> Void)?
    func start() {
        handler = { [weak self] in
            self?.tick()  // weak to avoid cycle
        }
    }
    func tick() { /* ... */ }
}
// Escaping closure: stored for later
// @escaping marks escaping closures
26

Tratamento de Erros Profundo

Erros Customizados

Erros customizados conformam a Error. LocalizedError fornece errorDescription. Valores associados carregam contexto. Enums são a forma idiomática de definir erros. Cada case representa uma falha distinta. A exaustividade do switch garante que todos os erros sejam tratados. Conforme a CustomStringConvertible para saída de depuração.

swift
enum APIError: Error, LocalizedError {
    case invalidURL
    case unauthorized
    case serverError(Int)
    case decodingFailed(Error)

    var errorDescription: String? {
        switch self {
        case .invalidURL: return "Invalid URL"
        case .unauthorized: return "Unauthorized"
        case .serverError(let code): return "Server error: \(code)"
        case .decodingFailed(let err): return "Decode failed: \(err)"
        }
    }
}

Tipo Result

Result é um enum tipado de sucesso/falha. Útil para operações síncronas que podem falhar. map transforma o valor de sucesso. flatMap encadeia operações. get() throws para converter para try/catch. Result é preferido em vez de funções que lançam para resultados armazenados ou passados. Combina tratamento de erros com semântica de valor.

swift
func fetchUser(id: Int) -> Result<User, APIError> {
    guard id > 0 else { return .failure(.invalidURL) }
    return .success(User(name: "Alice"))
}
// Usage
switch fetchUser(id: 1) {
case .success(let user): print(user)
case .failure(let error): print(error)
}
// map and flatMap
let result = fetchUser(id: 1).map { $0.name }

Rethrows

rethrows propaga erros de um parâmetro closure. Se a closure não lança, a função também não lança. Isso evita forçar try em chamadores que passam closures que não lançam. Usado por map, filter e outras funções de ordem superior. A função em si não pode lançar independentemente da closure.

swift
func withLock<T>(_ lock: NSLock, _ body: () throws -> T) rethrows -> T {
    lock.lock()
    defer { lock.unlock() }
    return try body()
}
// Only throws if body throws
let x = withLock(lock) { 5 }  // no try needed
let y = try withLock(lock) { try throwingFunc() }

Propagação de Erros

try propaga erros para o chamador. try? converte erros em nil (retorna Optional). try! trava em erro (use quando certo). Erros sobem pela pilha de chamadas até serem capturados. Funções que lançam devem ser marcadas com throws. O compilador impõe marcadores try, prevenindo erros não tratados. defer é executado independentemente de erros lançados.

swift
func process() throws {
    let data = try fetchData()  // may throw
    let user = try parse(data)  // may throw
    save(user)                  // does not throw
}
// try?: converts to optional, nil on error
let user = try? fetchUser()
// try!: asserts no error, crashes if thrown
let user = try! fetchUser()

Limpeza com defer

defer agenda limpeza para executar quando o escopo sai. É executado independentemente de como o escopo sai (return, throw ou fall-through). Múltiplos defers são executados em ordem LIFO. Útil para fechar arquivos, liberar locks, liberar recursos. defer não pode lançar ou break/continue. A closure captura variáveis por referência.

swift
func processFile() throws {
    let file = open("file.txt")
    defer { close(file) }  // runs on scope exit

    let data = try read(file)
    // ... process ...
    // close() runs here even if read throws
}
// Multiple defers run in reverse order (LIFO)
defer { print("1") }
defer { print("2") }  // prints 2 then 1

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.