Um cálculo Swift adiado mantém seu proprietário vivo quando a tarefa captura fortemente esse proprietário. Use uma captura fraca para permitir que o proprietário desapareça durante a espera, ou capture um instantâneo independente dos valores quando o cálculo deve terminar sem ele.

Para quem desenvolve para Mac e está criando uma pequena prévia de cálculo para desktop, a tarefa prática é escolher deliberadamente esse tempo de vida. Este exemplo espera brevemente, soma os valores sintéticos [4, 8, 12] e formata o total. Tudo é executado localmente, em memória.

Que tempo de vida seu cálculo deve ter?

As listas de captura do Swift permitem especificar o que uma closure retém. Uma captura forte mantém a instância referenciada; uma referência fraca permite que ela seja desalocada e se torna nil depois disso. Esses são comportamentos documentados da linguagem, independentemente de quanto tempo o cálculo espera. Contagem automática de referências

Escolha o contrato antes de escolher a sintaxe de captura. A tabela de decisão a seguir aplica essas regras da linguagem a uma prévia adiada para desktop:

Comportamento desejado Estratégia de captura O que acontece depois que quem chamou libera o proprietário?
Terminar usando o mesmo proprietário [self] A tarefa mantém o proprietário disponível para seu trabalho.
Continuar somente se o proprietário permanecer disponível [weak self] Depois de esperar, a tarefa verifica se o proprietário ainda existe.
Terminar usando os valores selecionados ao agendar [samples = self.samples] A tarefa retém os valores capturados sem reter este proprietário.

A linha do instantâneo tem uma condição essencial: este exemplo captura um array de inteiros. As expressões da lista de captura são avaliadas quando a closure é criada. Capturar uma instância de classe em vez disso reteria uma referência a essa instância; não criaria uma cópia independente de seu conteúdo mutável. Expressões

Há também uma questão distinta sobre os valores. As versões forte e fraca abaixo leem os valores do proprietário após a espera. A versão com instantâneo usa os valores do momento do agendamento. Se as entradas da prévia puderem mudar durante a espera, decida qual interpretação o resultado deve representar.

Execute uma comparação local completa no seu Mac

Use macOS 13 ou posterior e uma cadeia de ferramentas Swift 6 ou posterior instalada. A sobrecarga de Task.sleep(for:) baseada em duração, usada aqui, está documentada para macOS 13 em diante. Ela suspende a tarefa sem bloquear a thread subjacente e lança CancellationError se ocorrer cancelamento antes do fim da espera. sleep(for:tolerance:clock:)

Salve o programa completo a seguir como CaptureDemo.swift. A calculadora, as closures da tarefa e a demonstração são executadas em @MainActor, o que mantém o acesso ao estado serializado. O Swift documenta tanto o isolamento de classe pelo ator principal quanto a anotação explícita da closure usada aqui. Concorrência

import Foundation

enum CapturePolicy: String {
    case strong, weak, snapshot
}

@MainActor
func formatTotal(_ samples: [Int]) -> String {
    let total = samples.reduce(0, +)
    let formatter = NumberFormatter()
    formatter.numberStyle = .decimal
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.usesGroupingSeparator = false
    formatter.maximumFractionDigits = 0

    guard let text = formatter.string(from: NSNumber(value: total)) else {
        return "Formatting unavailable"
    }
    return "Total: \(text)"
}

@MainActor
final class Calculator {
    var samples = [4, 8, 12]

    func start(_ policy: CapturePolicy) -> Task<String, Error> {
        switch policy {
        case .strong:
            return Task { @MainActor [self] in
                try await Task.sleep(for: .milliseconds(300))
                try Task.checkCancellation()
                return formatTotal(self.samples)
            }

        case .weak:
            return Task { @MainActor [weak self] in
                try await Task.sleep(for: .milliseconds(300))
                try Task.checkCancellation()
                guard let self else {
                    return "Owner unavailable"
                }
                return formatTotal(self.samples)
            }

        case .snapshot:
            return Task { @MainActor [samples = self.samples] in
                try await Task.sleep(for: .milliseconds(300))
                try Task.checkCancellation()
                return formatTotal(samples)
            }
        }
    }
}

@main
struct CaptureDemo {
    @MainActor
    static func demonstrate(
        _ policy: CapturePolicy,
        cancel: Bool = false
    ) async {
        var owner: Calculator? = Calculator()
        weak var probe = owner
        let task = owner!.start(policy)
        owner = nil

        let label = cancel ? "\(policy.rawValue), cancelled" : policy.rawValue
        print("\(label) retains owner: \(probe != nil)")

        if cancel {
            task.cancel()
        }

        do {
            let result = try await task.value
            print(result)
        } catch is CancellationError {
            print("Cancelled")
        } catch {
            print("Calculation failed: \(error)")
        }
    }

    @MainActor
    static func main() async {
        await demonstrate(.strong)
        await demonstrate(.weak)
        await demonstrate(.snapshot)
        await demonstrate(.snapshot, cancel: true)
    }
}

A aritmética usa reduce(0, +) da biblioteca padrão para combinar os inteiros em uma única soma. O NumberFormatter do Foundation transforma essa soma em texto de exibição; seu método string(from:) retorna uma string opcional, então o programa informa explicitamente uma falha de formatação. A soma dos valores fixos de exemplo é 24. reduce(::), NumberFormatter, string(from:)

No Terminal, mude para a pasta que contém o arquivo e execute estes comandos. O destino especifica macOS 13 para a arquitetura do processador do seu Mac. A opção -parse-as-library permite o ponto de entrada @main explícito do arquivo. O compilador do Swift aceita arquivos-fonte e opções de linha de comando; swiftc -help lista as opções instaladas com sua cadeia de ferramentas. Manual de linha de comando do Swift

swift --version
swiftc -parse-as-library -swift-version 6 -target "$(uname -m)-apple-macos13.0" CaptureDemo.swift -o CaptureDemo
./CaptureDemo

A saída esperada do aplicativo é mostrada abaixo. Ela é derivada das regras de captura e cancelamento; o ambiente de pesquisa deste manuscrito não tinha um compilador Swift disponível, portanto o programa foi inspecionado, mas não foi executado aqui.

strong retains owner: true
Total: 24
weak retains owner: false
Owner unavailable
snapshot retains owner: false
Total: 24
snapshot, cancelled retains owner: false
Cancelled

A captura fraca probe observa a propriedade sem adicionar outra referência forte. Cada demonstração limpa sua própria referência forte antes de aguardar a tarefa. Como a demonstração e as closures da tarefa usam o mesmo ator, a closure recém-criada não pode intercalar-se com essa configuração síncrona. Isso torna a comparação independente de suposições sobre a rapidez com que a tarefa começa. Concorrência

Por que a posição da verificação de referência fraca importa?

No ramo fraco, guard let self aparece depois da espera. Assim, a tarefa pode esperar sem manter o proprietário fortemente. Se o proprietário tiver desaparecido, o resultado explícito Owner unavailable encerra esse cálculo.

Mover a desembrulhagem para antes da espera muda o tempo de vida. O trecho a seguir ilustra a substituição do corpo da closure do ramo fraco; não é um programa separado:

// Inside the existing [weak self] task closure:
guard let self else {
    return "Owner unavailable"
}
try await Task.sleep(for: .milliseconds(300))
try Task.checkCancellation()
return formatTotal(self.samples)

Aqui, a vinculação opcional bem-sucedida cria uma referência local forte que será necessária novamente após a suspensão. Consequentemente, o proprietário permanece disponível durante a espera depois que essa vinculação é bem-sucedida. Isso decorre das regras de referências fortes do Swift: a captura fraca determina como a closure chega inicialmente ao proprietário, enquanto a variável local desembrulhada determina como o código subsequente o mantém. Contagem automática de referências

Para o contrato “esperar sem reter o proprietário e, em seguida, calcular somente se ele ainda existir”, mantenha a desembrulhagem depois da espera e faça o pequeno cálculo logo em seguida. Pontos de suspensão adicionais após a desembrulhagem exigem outra decisão sobre o tempo de vida.

Uma captura unowned exige que a instância referenciada permaneça válida sempre que for acessada. Ela não oferece a verificação opcional de ausência necessária neste exemplo, em que liberar o proprietário durante a espera é intencional. Contagem automática de referências

Liberar o identificador da tarefa interrompe o cálculo?

Uma tarefa continua em execução mesmo que você descarte seu identificador. Manter o identificador permite aguardar o resultado ou solicitar o cancelamento. A Apple também documenta que uma tarefa concluída libera prontamente sua closure de operação e as referências que ela mantinha. Portanto, uma captura forte com duração finita estende o tempo de vida do proprietário durante o trabalho sem necessariamente criar um ciclo de referências duradouro. Task

O cancelamento é um contrato distinto da propriedade. A demonstração final solicita o cancelamento por meio do identificador. O sleep que lança erro e o Task.checkCancellation() subsequente oferecem pontos em que a operação pode parar, e quem chamou informa esse resultado. O cancelamento no Swift é cooperativo: solicitá-lo não interrompe à força código de cálculo arbitrário. Task

Não substitua o sleep que lança erro por try? e depois prossiga incondicionalmente se o cancelamento deve impedir um resultado. Suprimir esse erro permitiria que a execução chegasse ao cálculo, a menos que outra verificação de cancelamento a interrompesse. Da mesma forma, atribuir nil à variável do proprietário não é uma solicitação de cancelamento.

Para uma prévia de desktop, essas distinções oferecem três controles úteis: se o proprietário permanece alocado, quais valores de entrada o cálculo usa e se a operação deve parar. Escolha cada um explicitamente, em vez de esperar que uma captura fraca implemente os três.

O que você deve mudar para uma prévia de desktop?

Use a versão fraca quando uma prévia pendente só tiver valor enquanto seu proprietário permanecer disponível. Use a versão com instantâneo quando as entradas sintéticas selecionadas devem produzir um resultado de forma independente. Use a versão forte quando concluir a operação exigir legitimamente o proprietário.

O tempo de vida do objeto também difere da visibilidade da interface. Fechar uma janela não prova, por si só, que todas as referências fortes à sua calculadora foram removidas. Se o aplicativo deve interromper o trabalho pendente quando uma prévia é fechada, conecte esse evento do aplicativo ao método cancel() do identificador de tarefa retido; o cálculo ainda precisa cooperar com o cancelamento. Task

Mantenha pequena a carga de trabalho deste exemplo. O isolamento pelo ator principal torna simples o acesso ao estado, e a espera suspende a tarefa, mas a soma síncrona ainda é executada nesse ator. O guia de concorrência do Swift explica que o ator principal também lida com o trabalho da interface. Um cálculo muito maior precisa de um projeto de execução separado, que passe valores adequados ao executor. Concorrência

Para este cálculo local, o teste decisivo é simples: remova a referência de quem chamou antes de a operação adiada ser executada e observe o resultado. A captura forte preserva o proprietário, a captura fraca permite um resultado explícito de indisponibilidade e um instantâneo do array de inteiros preserva apenas as entradas necessárias para terminar.

Fontes