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
- Contagem automática de referências — Swift.org
- Expressões — Swift.org
- Concorrência — Swift.org
- Task — Apple Developer
- sleep(for:tolerance:clock:) — Apple Developer
- reduce(::) — Apple Developer
- NumberFormatter — Apple Developer
- string(from:) — Apple Developer
- Manual de linha de comando do Swift — Projeto Swift