En fördröjd Swift-beräkning håller sin ägare vid liv när dess uppgift fångar ägaren starkt. Använd en svag fångst så att ägaren kan försvinna under väntetiden, eller fånga en oberoende värdeögonblicksbild när beräkningen ska slutföras utan den.
För en Mac-utvecklare som bygger en liten förhandsvisning av en skrivbordsberäkning är den praktiska uppgiften att välja livslängden medvetet. Det här exemplet väntar en kort stund, lägger till de syntetiska värdena [4, 8, 12] och formaterar summan. Allt körs lokalt i minnet.
Vilken livslängd bör beräkningen ha?
Med Swift kan du ange vad en closure behåller med fångstlistor. En stark fångst håller den refererade instansen vid liv; en svag referens gör att den kan avallokeras och blir nil därefter. Det här är dokumenterat språkbeteende, oberoende av hur länge beräkningen väntar. Automatic Reference Counting
Välj ett kontrakt innan du väljer fångstsyntax. Följande beslutstabell tillämpar språkreglerna på en fördröjd förhandsvisning på datorn:
| Önskat beteende | Fångststrategi | Vad händer när anroparen släpper sin ägare? |
|---|---|---|
| Slutför med samma ägare | [self] |
Uppgiften håller ägaren tillgänglig för sitt arbete. |
| Fortsätt bara om ägaren fortfarande är tillgänglig | [weak self] |
Efter väntan kontrollerar uppgiften om ägaren fortfarande finns. |
| Slutför med värdena som valdes när schemaläggningen gjordes | [samples = self.samples] |
Uppgiften behåller de fångade värdena utan att behålla den här ägaren. |
Raden med ögonblicksbild har ett viktigt villkor: i det här exemplet fångas en array med heltal. Uttryck i fångstlistor utvärderas när closuren skapas. Om en klassinstans fångas i stället behålls en referens till den instansen; det skapas ingen oberoende kopia av dess ändringsbara innehåll. Expressions
Det finns också en separat värdefråga. De starka och svaga versionerna nedan läser ägarens värden efter fördröjningen. Ögonblicksbildsversionen använder värdena från schemaläggningstillfället. Om förhandsvisningens indata kan ändras under väntan bör du avgöra vilken tolkning resultatet ska representera.
Kör en fullständig lokal jämförelse på din Mac
Använd macOS 13 eller senare och en installerad Swift 6-verktygskedja eller senare. Den varaktighetsbaserade Task.sleep(for:)-överlagringen som används här är dokumenterad för macOS 13 och senare. Den suspenderar uppgiften utan att blockera den underliggande tråden och kastar CancellationError om avbrytning sker innan väntan är över. sleep(for:tolerance:clock:)
Spara följande fullständiga program som CaptureDemo.swift. Kalkylatorn, uppgiftsclosurerna och demonstrationen körs på @MainActor, vilket håller åtkomsten till deras tillstånd serialiserad. Swift dokumenterar både isolering på klassnivå med main actor och den explicita closure-annotering som används här. Concurrency
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)
}
}
Aritmetiken använder standardbibliotekets reduce(0, +) för att kombinera heltalen till en summa. Foundations NumberFormatter omvandlar summan till visningstext; metoden string(from:) returnerar en valfri sträng, så programmet rapporterar uttryckligen om formateringen misslyckas. Det fasta exemplet summeras till 24. reduce(::), NumberFormatter, string(from:)
Gå till mappen som innehåller filen i Terminal och kör dessa kommandon. Målet anger macOS 13 för din Macs processorarkitektur. Alternativet -parse-as-library tillåter filens uttryckliga @main-ingångspunkt. Swifts kompilator tar emot källfiler och kommandoradsalternativ; swiftc -help listar alternativen som ingår i din verktygskedja. Swift command-line manual
swift --version
swiftc -parse-as-library -swift-version 6 -target "$(uname -m)-apple-macos13.0" CaptureDemo.swift -o CaptureDemo
./CaptureDemo
Det förväntade programresultatet visas nedan. Det härleds från reglerna för fångst och avbrytning; i manuskriptets forskningsmiljö fanns ingen Swift-kompilator tillgänglig, så programmet granskades men kördes inte här.
strong retains owner: true
Total: 24
weak retains owner: false
Owner unavailable
snapshot retains owner: false
Total: 24
snapshot, cancelled retains owner: false
Cancelled
Den svaga probe observerar ägarskapet utan att lägga till ytterligare en stark referens. Varje demonstration rensar sin egen starka referens innan den inväntar uppgiften. Eftersom demonstrationen och uppgiftsclosurerna använder samma actor kan den nyskapade closuren inte avbryta den synkrona förberedelsen. Det gör jämförelsen oberoende av gissningar om hur snabbt uppgiften startar. Concurrency
Varför spelar placeringen av den svaga kontrollen roll?
I den svaga grenen står guard let self efter fördröjningen. Uppgiften kan därför vänta utan att hålla ägaren starkt. Om ägaren har försvunnit avslutar det uttryckliga resultatet Owner unavailable beräkningen.
Om uppackningen flyttas före fördröjningen ändras livslängden. Följande är ett illustrativt kodfragment som ersätter den svaga grenens closure-kropp; det är inte ett separat program:
// 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)
Här skapar en lyckad valfri bindning en stark lokal referens som behövs igen efter suspenderingen. Ägaren förblir därför tillgänglig under väntan när bindningen väl har lyckats. Det följer av Swifts regler för starka referenser: den svaga fångsten avgör hur closuren först når ägaren, medan den uppackade lokala variabeln avgör hur den efterföljande koden håller kvar den. Automatic Reference Counting
För kontraktet ”vänta utan att behålla ägaren, och beräkna sedan bara om den fortfarande finns” ska uppackningen ligga efter väntan och den lilla beräkningen utföras direkt därefter. Ytterligare suspenderingspunkter efter uppackningen kräver ett nytt beslut om livslängden.
En unowned-fångst kräver att den refererade instansen är giltig varje gång den används. Den ger inte den valfria frånvarokontroll som behövs i det här exemplet, där avsikten är att ägaren ska kunna släppas under fördröjningen. Automatic Reference Counting
Stoppar beräkningen om uppgiftshandtaget släpps?
En uppgift fortsätter att köras även om du slänger dess handtag. Om du behåller handtaget kan du invänta resultatet eller begära avbrytning. Apple dokumenterar också att en slutförd uppgift omedelbart släpper sin operationsclosure och de referenser som closuren höll. En ändlig stark fångst förlänger därför ägarens livslängd under arbetet utan att nödvändigtvis skapa en bestående referenscykel. Task
Avbrytning är ett separat kontrakt från ägarskap. Den sista demonstrationen begär avbrytning via handtaget. Den felkastande väntan och efterföljande Task.checkCancellation() ger platser där operationen kan stoppas, och anroparen rapporterar resultatet. Avbrytning i Swift är samarbetsbaserad: en begäran avbryter inte godtycklig beräkningskod med tvång. Task
Ersätt inte den felkastande väntan med try? och fortsätt sedan ovillkorligt om avbrytning ska förhindra ett resultat. Om felet tystas fortsätter körningen till beräkningen, såvida inte en annan avbrytningskontroll stoppar den. På samma sätt är det ingen avbrytningsbegäran att tilldela nil till ägarens variabel.
För en förhandsvisning på datorn ger dessa skillnader tre användbara styrningar: om ägaren förblir allokerad, vilka indatavärden beräkningen använder och om operationen ska stoppas. Välj varje styrning uttryckligen i stället för att förvänta dig att en svag fångst ska sköta alla tre.
Vad bör du ändra för en förhandsvisning på datorn?
Använd den svaga versionen när en väntande förhandsvisning bara har ett värde så länge dess ägare är tillgänglig. Använd ögonblicksbildsversionen när de valda syntetiska indata värdena ska ge ett resultat oberoende av ägaren. Använd den starka versionen när det är motiverat att operationen slutförs med ägaren.
Objektets livslängd skiljer sig också från gränssnittets synlighet. Att ett fönster stängs bevisar inte i sig att alla starka referenser till dess kalkylator har tagits bort. Om programmet ska stoppa väntande arbete när en förhandsvisning stängs kopplar du den programhändelsen till metoden cancel() på det sparade uppgiftshandtaget; beräkningen måste fortfarande samarbeta med avbrytningen. Task
Håll arbetsmängden i det här exemplet liten. Isolering med main actor gör åtkomsten till tillstånd enkel, och väntan suspenderar uppgiften, men den synkrona summan körs fortfarande på den aktorn. Swifts guide om samtidighet förklarar att main actor också hanterar gränssnittsarbete. En betydligt större beräkning kräver en separat körningslösning som skickar lämpliga värden till arbetaren. Concurrency
För den här lokala beräkningen är det avgörande testet enkelt: ta bort anroparens referens innan den fördröjda operationen körs och observera resultatet. Stark fångst bevarar ägaren, svag fångst tillåter ett uttryckligt resultat om att ägaren saknas, och en ögonblicksbild av en heltalsarray bevarar bara de indata som behövs för att slutföra arbetet.
Källor
- Automatic Reference Counting — Swift.org
- Expressions — Swift.org
- Concurrency — Swift.org
- Task — Apple Developer
- sleep(for:tolerance:clock:) — Apple Developer
- reduce(::) — Apple Developer
- NumberFormatter — Apple Developer
- string(from:) — Apple Developer
- Swift command-line manual — Swift project