Een vertraagde Swift-berekening houdt de eigenaar in leven wanneer de taak die eigenaar sterk vastlegt. Gebruik een zwakke vastlegging zodat de eigenaar tijdens het wachten kan verdwijnen, of leg een onafhankelijke momentopname van de waarden vast als de berekening zonder de eigenaar moet worden voltooid.

Voor een Mac-ontwikkelaar die een klein voorbeeld van een desktopberekening bouwt, is het praktisch gezien zaak om bewust voor die levensduur te kiezen. Dit voorbeeld wacht kort, telt de synthetische waarden [4, 8, 12] op en maakt de uitkomst op. Alles wordt lokaal in het geheugen uitgevoerd.

Welke levensduur moet je berekening hebben?

Met Swift-capturelijsten kun je aangeven wat een closure vasthoudt. Een sterke vastlegging houdt de verwezen instantie vast; met een zwakke verwijzing kan deze worden gedealloceerd, waarna de verwijzing nil wordt. Dit zijn gedocumenteerde eigenschappen van de taal, ongeacht hoelang de berekening wacht. Automatic Reference Counting

Kies eerst het gewenste gedrag en daarna de syntaxis voor de vastlegging. De volgende beslissingstabel past deze taalregels toe op een vertraagd desktopvoorbeeld:

Gewenst gedrag Vastleggingsstrategie Wat gebeurt er nadat de aanroeper de eigenaar loslaat?
Voltooien met dezelfde eigenaar [self] De taak houdt de eigenaar beschikbaar voor de uitvoering.
Alleen doorgaan als de eigenaar beschikbaar blijft [weak self] Na het wachten controleert de taak of de eigenaar nog bestaat.
Voltooien met de waarden die bij het plannen zijn gekozen [samples = self.samples] De taak houdt de vastgelegde waarden vast zonder deze eigenaar vast te houden.

Bij de rij met de momentopname geldt een belangrijke voorwaarde: in dit voorbeeld wordt een array met gehele getallen vastgelegd. Expressies in capturelijsten worden geëvalueerd wanneer de closure wordt aangemaakt. Als je in plaats daarvan een klasse-instantie vastlegt, houd je een verwijzing naar die instantie vast; er wordt geen onafhankelijke kopie van de veranderlijke inhoud gemaakt. Expressions

Er speelt ook nog een aparte vraag over de waarden. De sterke en zwakke varianten hieronder lezen de waarden van de eigenaar na de vertraging. De momentopnamevariant gebruikt de waarden van het moment waarop de berekening werd gepland. Als de invoer voor het voorbeeld tijdens het wachten kan veranderen, bepaal dan welke interpretatie de uitkomst moet weergeven.

Voer een volledige lokale vergelijking uit op je Mac

Gebruik macOS 13 of nieuwer en een geïnstalleerde Swift 6-toolchain of nieuwer. De hier gebruikte duurgebaseerde overload van Task.sleep(for:) is gedocumenteerd voor macOS 13 en nieuwer. Deze onderbreekt de taak zonder de onderliggende thread te blokkeren en gooit CancellationError als de taak wordt geannuleerd voordat het wachten voorbij is. sleep(for:tolerance:clock:)

Sla het volgende volledige programma op als CaptureDemo.swift. De calculator, taakclosures en demonstratie worden uitgevoerd op @MainActor, zodat de toegang tot hun status geserialiseerd blijft. Swift documenteert zowel isolatie op het niveau van de klasse door de main actor als de expliciete closureannotatie die hier wordt gebruikt. 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)
    }
}

Voor de rekenkundige bewerking wordt de standaardbibliotheekfunctie reduce(0, +) gebruikt om de gehele getallen tot één som op te tellen. Foundation's NumberFormatter zet die som om in tekst voor weergave; de methode string(from:) retourneert een optionele string, dus het programma meldt een formatteringsfout expliciet. De vaste voorbeeldwaarden tellen op tot 24. reduce(::), NumberFormatter, string(from:)

Ga in Terminal naar de map met het bestand en voer deze opdrachten uit. Het doelplatform specificeert macOS 13 voor de processorarchitectuur van je Mac. Met de optie -parse-as-library kan het bestand een expliciet toegangspunt @main bevatten. De Swift-compiler accepteert bronbestanden en opdrachtregelopties; swiftc -help toont de opties die bij je toolchain zijn geïnstalleerd. Swift command-line manual

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

Hieronder staat de verwachte uitvoer van de toepassing. Deze is afgeleid van de regels voor vastlegging en annulering; in de onderzoeksomgeving van dit manuscript was geen Swift-compiler beschikbaar, dus het programma is hier bekeken maar niet uitgevoerd.

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

De zwakke probe observeert het eigenaarschap zonder een extra sterke verwijzing toe te voegen. Elke demonstratie wist de eigen sterke verwijzing voordat de taak wordt afgewacht. Omdat de demonstratie en de taakclosures dezelfde actor gebruiken, kan de nieuw aangemaakte closure niet tussen die synchrone voorbereiding door worden uitgevoerd. Daardoor hangt de vergelijking niet af van een gok over hoe snel de taak start. Concurrency

Waarom is de plek van de zwakke controle van belang?

In de zwakke tak staat guard let self na de vertraging. De taak kan dus wachten zonder de eigenaar sterk vast te houden. Als de eigenaar is verdwenen, beëindigt het expliciete resultaat Owner unavailable die berekening.

Als je de uitpakbewerking vóór de vertraging plaatst, verandert de levensduur. Het volgende is een illustratief fragment dat de body van de closure in de zwakke tak vervangt; het is geen afzonderlijk programma:

// 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)

Als de optionele binding slaagt, wordt hier een sterke lokale verwijzing aangemaakt die na de onderbreking opnieuw nodig is. Daardoor blijft de eigenaar beschikbaar gedurende het wachten zodra die binding slaagt. Dit volgt uit de regels voor sterke verwijzingen in Swift: de zwakke vastlegging bepaalt hoe de closure in eerste instantie bij de eigenaar komt, terwijl de uitgepakte lokale variabele bepaalt hoe de volgende code die vasthoudt. Automatic Reference Counting

Voor het contract ‘wacht zonder de eigenaar vast te houden en bereken daarna alleen als die nog bestaat’ laat je de uitpakbewerking na het wachten plaatsvinden en voer je de kleine berekening direct daarna uit. Bij extra onderbrekingspunten na het uitpakken moet je opnieuw over de levensduur beslissen.

Voor een unowned-vastlegging moet de verwezen instantie geldig blijven wanneer je die gebruikt. Deze biedt niet de controle op afwezigheid met een optionele waarde die dit voorbeeld nodig heeft, waarin het bewust de bedoeling is dat de eigenaar tijdens de vertraging kan worden losgelaten. Automatic Reference Counting

Stopt de berekening als je de taakhandle loslaat?

Een taak blijft actief, ook als je de handle weggooit. Als je de handle bewaart, kun je het resultaat afwachten of annulering aanvragen. Apple documenteert ook dat een voltooide taak de closure van de bewerking en de verwijzingen die deze closure vasthield meteen vrijgeeft. Een eindige sterke vastlegging verlengt dus de levensduur van de eigenaar gedurende de uitvoering, zonder noodzakelijkerwijs een blijvende referentiecyclus te veroorzaken. Task

Annulering is een afzonderlijk contract, los van eigenaarschap. De laatste demonstratie vraagt via de handle om annulering. De gooiende slaapfunctie en de daaropvolgende Task.checkCancellation() bieden momenten waarop de bewerking kan stoppen, waarna de aanroeper dat resultaat meldt. Annulering in Swift werkt op basis van medewerking: het aanvragen ervan onderbreekt niet op harde wijze willekeurige berekeningscode. Task

Vervang de gooiende slaapfunctie niet door try? en ga daarna onvoorwaardelijk verder als annulering een resultaat moet voorkomen. Door die fout te onderdrukken, kan de uitvoering de berekening bereiken, tenzij een andere annuleringscontrole haar stopt. Ook is nil toewijzen aan de variabele van de eigenaar geen annuleringsverzoek.

Voor een desktopvoorbeeld bieden deze verschillen drie handige regelmogelijkheden: of de eigenaar in het geheugen blijft, welke invoerwaarden de berekening gebruikt en of de bewerking moet stoppen. Kies elk daarvan expliciet in plaats van te verwachten dat een zwakke vastlegging ze alle drie afhandelt.

Wat moet je voor een desktopvoorbeeld aanpassen?

Gebruik de zwakke variant wanneer een voorbeeld in de wachtrij alleen waarde heeft zolang de eigenaar beschikbaar blijft. Gebruik de momentopnamevariant wanneer de geselecteerde synthetische invoer zelfstandig een resultaat moet opleveren. Gebruik de sterke variant wanneer het voltooien van de bewerking daadwerkelijk vereist dat de eigenaar beschikbaar blijft.

De levensduur van een object verschilt ook van de zichtbaarheid van de interface. Het sluiten van een venster bewijst op zichzelf niet dat alle sterke verwijzingen naar de calculator zijn verwijderd. Als de toepassing lopende taken moet stoppen wanneer een voorbeeld wordt gesloten, koppel die gebeurtenis dan aan de methode cancel() van de bewaarde taakhandle; de berekening moet nog steeds meewerken aan annulering. Task

Houd de werklast in dit voorbeeld klein. Isolatie door de main actor maakt de toegang tot de status eenvoudig en het wachten onderbreekt de taak, maar de synchrone som wordt nog steeds op die actor uitgevoerd. In Swift's gids voor gelijktijdigheid staat dat de main actor ook interfacewerk afhandelt. Voor een aanzienlijk grotere berekening is een aparte uitvoeringsopzet nodig die geschikte waarden aan de worker doorgeeft. Concurrency

Voor deze lokale berekening is de beslissende test eenvoudig: verwijder de verwijzing van de aanroeper voordat de vertraagde bewerking wordt uitgevoerd en bekijk het resultaat. Een sterke vastlegging bewaart de eigenaar, een zwakke vastlegging maakt een expliciet resultaat mogelijk dat aangeeft dat de eigenaar niet beschikbaar is, en een momentopname van een array met gehele getallen bewaart alleen de invoer die nodig is om de berekening te voltooien.

Bronnen