DisainDisainiinseneeria

Swifti kirjutamine

Kuidas kirjutada kaasaegset Swifti hästi: väärtustüübid, Swift 6 andmevõistluste turvalisus ja konkurentsus.

EEmil Kowalski·Disain·MIT

Kogu oskus — vaikeversiooni hoitakse GitHubis; sinu muudatused jäävad sinu enda klooni.

Kasuta seda oskustLaadi alla .zip
Kuidas see töötab?
  • ChatGPT avab uue vestluse, kuhu oskus on laaditud. Kui see on lingi jaoks liiga pikk, kopeeritakse see lõikelauale — lihtsalt kleebi.
  • Claude töötab samamoodi. Püsivaks paigaldamiseks laadi alla .zip ja lae see üles: Claude → Settings → Capabilities → Skills (Pro/Team/Enterprise).
  • Kopeeri oskus kopeerib oskuse, et saaksid selle kleepida mis tahes assistenti, sh Grok.

Kirjuta Swifti

Esmane vastus

Kui see oskus esimest korda ilma konkreetse küsimuseta käivitatakse, vasta ainult:

Olen valmis aitama sul kirjutada kaasaegset Swifti nii, nagu see keel tahab olla kirjutatud.

Ära anna mingit muud teavet enne, kui kasutaja küsimuse esitab.

Kuidas kirjutada Swifti nii, nagu keel tahab olla kirjutatud, ajakohane kuni Swift 6.4-ni.

Tööriistaahela lähtetase: Swift 6.3 (praegune väljalase 2026. aasta augusti seisuga). Kõik siin kompileerub versioonis 6.3, välja arvatud ⚠-ga märgitu, mis tähistab avaldamata Swift 6.4 funktsioone. Samaaegsuse juhised eeldavad Swift 6.2 mudelit — kui projekt on versioonil 6.1 või vanemal, ei kehti §3 reeglid async ja @concurrent kohta.

Läbiv mõte: Swift on järkjärgulise avalikustamise keel. Alusta kõige lihtsamast, kõige staatilisemast ja kõige ühelõimelisemast lahendusest, mis töötab, ning osta dünaamilisust — samaaegsust, viitesemantikat, eksistentsiaale, ebaturvalisi viitasid — ainult seal, kus saad põhjusele osutada. Iga allpool toodud reegel on selle rakendus.

Modelleeri see vaikevalikute hierarhia. Liigu tasemel allapoole ainult siis, kui oskad põhjuse sõnastada:

Vajadus Kasuta Liigu allapoole ainult siis, kui
Andmed struct / enum vajad identiteeti, jagamist või pärilust
Abstraktsioon konkreetne tüüp sul on tüüpide vahel korduvat koodi
Polümorfism some P (geneeriline) vajad heterogeenset salvestust → any P
Täitmine peaaktor, sünkroonne profileerimine näitab hangumist → async → @concurrent → actor
Mälu Array, String profileerimine näitab kulu → InlineArray, Span
Ohutus turvaline API C-koostöö või mõõdetud kuum rada → Unsafe*

1. Modelleeri andmeid väärtustüüpidega

Väärtustüübid on Swiftis vaikimisi valik, mitte erijuhtum.

struct Material {                       // value semantics preserved
  var roughness: Double
  private var _texture: Texture         // a class

  var color: Color {
    get { _texture.color }
    set {
      if !isKnownUniquelyReferenced(&_texture) { _texture = Texture(copying: _texture) }
      _texture.color = newValue
    }
  }
}

Mittekopeeritavad tüübid (~Copyable) väljendavad ainuomandit: failideskriptor, pangaülekanne, avatud ressurss. Kopeerimise keelamine muudab „seda ei tohi kaks korda käivitada“ väitest kompileerimisveaks ja annab structi deinit-ile tähenduse. Märgi lõpetav meetod consuming-uks, et kompilaator tõestaks, et see on viimane kasutus. Parameetrite omand muutub selgesõnaliseks: borrowing (ainult lugemine, vaikimisi), consuming (võtab ära), inout/mutating (ajutine kirjutusõigus).


2. Vead ja valikulised väärtused — tee ebaõnnestumise rajad nähtavaks

Swifti veakäsitlus toetub kolmele punktile: veaallikad on märgistatud, et need sind ei üllataks; vead kannavad piisavalt konteksti, et nende põhjal tegutseda; ja taastatavad vead erinevad programmeerija vigadest.


3. Samaaegsus: püsi ühelõimelisena, kuni profileerimine ütleb muud

Selle jaotise teevad agendid kõige sagedamini valesti, sest mudel muutus Swift 6.2-s.

Alusta iga rakendust täielikult põhilõimel. Ühelõimeline kood viib kaugele ja enamik rakendusi ei pea sellest kunagi lahkuma.

Edenemine järjekorras. Ära jäta samme vahele.

  1. Ühelõimeline peaaktoril. Samaaegsust üldse pole. Sobib enamikule rakendustele.
  2. async/await latentsuse varjamiseks (võrk, ketas). Ikka veel pole sul omaenda samaaegsust — SDK API-d nagu URLSession.data(from:) tõstavad töö sinu eest mujale.
  3. @concurrent, et tõsta sinu kulukas töö põhilõimelt ära — ainult pärast seda, kui Instruments näitab hangumist.
  4. actor, et tõsta olek peaaktorilt ära — ainult siis, kui liiga palju peaaktori olekut sunnib ülesandeid pidevalt tagasi hüppama.

Lülita esmalt sisse õiged ehitusseaded. Lülita igas projektis sisse Approachable Concurrency. Rakendusmoodulite ja kasutajaliidesega seotud moodulite puhul määra ka Default Actor Isolation väärtuseks MainActor — see on Xcode 26-s uute rakendusprojektide vaikeväärtus ja kustutab enamiku sinu @MainActor annotatsioonidest. Paketis: swiftSettings: [.defaultIsolation(MainActor.self)]. Ära määra main-actor-by-default'i üldotstarbelisele teegile — teegid peaksid tarnima nonisolated API-sid ja laskma klientidel otsustada, kus töö jookseb.

Reegel, mis muutus

Swift 6.2-s ei vii funktsiooni märkimine async-iks seda praeguselt aktorilt ära. See jookseb seal, kust see välja kutsuti. Just see teeb „kõige loomulikumalt kirjutatava koodi“ vaikimisi andmevõidujooksudest vabaks.

nonisolated struct PhotoProcessor {          // decoupled from the main actor
  @concurrent                                // guaranteed to run in the background
  func process(_ data: Data) async -> ProcessedPhoto {
    async let sticker = extractSticker(data)  // two independent jobs, in parallel
    async let colors  = extractColors(data)
    return await ProcessedPhoto(sticker: sticker, colors: colors)
  }
}

Aktori taassisenemine

Aktorid tagavad vastastikuse välistamise, mitte tehingud. Kahe await-i vahel samal aktoril jookseb muu töö.


4. Sendable ja andmete jagamine

Sendable märgib tüübi, mida on ohutu isolatsioonidomeenide vahel jagada. Kompilaator kontrollib seda iga ülesande ja aktori piiril.

Kui saad andmevõidujooksu vea, liigu läbi see loend:

  1. Ära jaga seda. Tõsta jagatud objekt lokaalsesse muutujasse, et iga samaaegne töö saaks oma eksemplari. (See on lahendus valdavale enamikule tegelikest vigadest.)
  2. Tee sellest Sendable väärtustüüp, nii et „jagamine“ on tegelikult kopeerimine.
  3. Isoleeri see aktorile — peaaktorile või omaenda aktorile.
  4. Alles siis võta kasutusele Mutex/Atomic moodulist Synchronization (salvesta need let-omadustesse) või @unchecked Sendable.

Globaalsed ja staatilised muutujad on kõige levinum veaallikas. Eelistuse järjekorras: tee sellest let; pane see @MainActor-ile; mähi see Mutex-isse; nonisolated(unsafe). Pane tähele, et Swiftis initsialiseeritakse globaalsed muutujad laisalt ja atomaarselt — erinevalt C-st.

Vanade tagasikutse-API-de sildamine: annoteeri delegaatprotokollid @MainActor-iga, kui need on sinu omad. Kui ei ole, märgi meetod nonisolated-iks ja kasuta MainActor.assumeIsolated { } — see kinnitab, mitte ei hüppa, nii et see kukub valjult kokku, selle asemel et vaikselt võidu joosta. @preconcurrency vastavusel on sama asja lühivorm. Kasuta @preconcurrency import-i, et ajutiselt vaigistada sendability hoiatusi moodulist, mis pole veel migreeritud; hoiatused tulevad — õigustatult — tagasi, kui see on tehtud.


5. Struktureeritud samaaegsus

Eelista alati struktureeritud ülesandeid.

Struktureeritud ülesannetel (async let, ülesandegrupid) on lokaalsete muutujate sarnane skoop: need ei saa plokist kauem elada, neid oodatakse automaatselt ja need pärivad ülesandepuu kaudu tühistamise, prioriteedi ja ülesandelokaalsed väärtused. Struktureerimata ülesanded (Task { }, Task.detached) ei anna sulle midagi sellest automaatselt.

Tühistamine on koostööpõhine. Tühistamine seab lipu; see ei peata midagi. Kontrolli Task.isCancelled või try Task.checkCancellation() enne kuluka töö alustamist, ka sünkroonsetes abifunktsioonides. Töö puhul, mis on pigem peatatud kui jookseb (AsyncSequence'i next()), kasuta withTaskCancellationHandler-it — ja pea meeles, et käsitleja jookseb kohe ja kehaga samaaegselt, nii et olek, mida see puudutab, vajab tõelist sünkroniseerimist (atomaarset muutujat või lukku, mitte aktorit — aktoril ei saa järjestust tagada).

Piira oma samaaegsust. Ära hargne piiramatu loendi peal iga elemendi kohta üheks alamülesandeks. Käivita N alamülesannet ja lisa uus iga kord, kui mõni lõpetab.

Ülesandelokaalsed väärtused (@TaskLocal) levitavad konteksti — päringu ID-d, jälituslõiku — ülesandepuus allapoole, ilma et peaksid parameetrit läbi iga signatuuri vedama. Tee need valikuliseks, et sidumata lugemistel oleks mõistlik vaikeväärtus.

Tagasikutsete sildamine: withCheckedContinuation / withCheckedThrowingContinuation. Leping on jätka täpselt üks kord igal rajal — jätkamata jätmine paneb väljakutsuja igaveseks ootama; kaks korda jätkamine on fataalne viga. Hiljem käivituvate delegaat-API-de puhul salvesta jätk ja sea see jätkamisel nil-iks. (Swift 6.4 — avaldamata — lisab tüübi Continuation, mis kontrollib ühekordset jätkamist kompileerimise ajal.)

AsyncSequence: itereeri for await / for try await abil. Kohanda olemasolev käsitleja- või delegaadipõhine API AsyncStream / AsyncThrowingStream abil — loo allikas sulundi sees, tee käsitlejast yield ja korista onTermination-is.


6. Samaaegsus SwiftUI-s

.visualEffect { [pulse] effect, proxy in    // copy the Bool, don't capture self
  effect.blur(radius: pulse ? 2 : 0)
}

7. Protokollid ja geneerikud

Ära alusta klassist. Ära alusta ka protokollist.

Töövoog: kirjuta konkreetsed tüübid → märka nendevahelist korduvat koodi → tõsta ühine võimekus protokolli → kirjuta selle vastu geneeriline kood. Peaaegu identsete kehadega ülelaadimised on signaal, et on aeg üldistada.

some vs any


8. API disain — selgus kasutuskohas

Selgus kasutuskohas on eesmärk, mis on siin kõigist teistest tähtsam.


9. Jõudlus — mõõda, siis vali

Madala taseme Swifti jõudlust domineerivad neli kulu. Tea, millist neist maksad.

  1. Funktsioonikutsed — argumentide koopiad, staatiline vs dünaamiline saatmine, kutsekaadri eraldamine ja blokeeritud optimeerimine.
  2. Mälupaigutus — reasisene vs välisvaramu; dünaamilise suurusega tüübid.
  3. Eraldamine — globaalne (tasuta), pinu (odav: üks lahutamine), kuhi (kallis: otsing pluss lukustamine).
  4. Koopiad — retain'id/release'id ja rekursiivsed structi koopiad.

Aga tee esmalt algoritmiline töö. Iga kord, kui kirjutad tsükli, proovi see asendada algoritmi väljakutsega. Suurimad võidud pole peaaegu kunagi mikrooptimeerimised:

Konkreetsed hoovad, umbes selle järjekorras, mida need annavad:

Asünkroonsed funktsioonid hoiavad oma olekut ülesandepõhises plokieraldajas, mitte C-pinus, ja jagunevad igas peatamispunktis osafunktsioonideks. Kuluprofiil sarnaneb sünkroonsete funktsioonidega, kutse lisakulu on veidi suurem — mis on veel üks põhjus mitte teha async-iks midagi, millel pole midagi oodata.

Hüpped peaaktorile ja sealt tagasi maksavad tõelise kontekstivahetuse. Koonda: lükka tsükkel funktsioonide loadArticles/updateUI sisse, nii et need võtavad massiive, selle asemel et iga iteratsiooni kohta kaks korda hüpata.


10. ARC ja objekti eluiga


11. Testimine — vaikimisi Swift Testing

Kasuta uute testide jaoks Swift Testingut. XCTest on endiselt vajalik täpselt kolme asja jaoks: kasutajaliidese automatiseerimine (XCUIApplication), jõudlusmõõdikud (XCTMetric) ja testid, mis peavad olema kirjutatud Objective-C-s või mis püüavad Objective-C erandeid.


12. Makrod

Kasuta makrot, kui kirjutad koodi, mille kompilaator saaks tuletada — ja ainult siis.


13. Logimine ja silumine


14. Ebaturvaline kood ja koostalitlus


15. Kaasaegne süntaks, mida peaksid kasutama

Agendid kirjutavad tavapäraselt kõigi nende vanemat ja pikemat vormi.

⚠-ga märgitud read on Swift 6.4, mis pole veel välja tulnud. Praegune väljalase on 6.3.x. Nende ettepanekud on vastu võetud ja põhiharus implementeeritud, nii et nende ümber on ohutu planeerida, aga täna on ebaturvaline neid kirjutada — kontrolli enne kasutamist projekti tööriistaahelat ja eelista vanemat vormi, kui sihtversioon on 6.3 või vanem.

Selle asemel Kirjuta Alates
Pesastatud ternaarsed operaatorid; kohe väljakutsutud sulund let-i initsialiseerimiseks if/switch avaldised 5.9
Ülelaadimised 1, 2, 3… argumendi jaoks parameetripakid (each T) ja for üle paki 5.9
ObservableObject + @Published igal omadusel @Observable 5.9
Objekti küsitlemine muudatuste suhtes Observations { ... } — tehinguliste uuenduste AsyncSequence 6.2
NotificationCenter stringipõhise userInfo-ga konkreetsed teavitustüübid (MainActorMessage / AsyncMessage) 6.2
Process + torud skriptimiseks pakett Subprocess (AsyncBufferSequence.strings() reakaupa väljundi jaoks; 1.0 saabub koos 6.4-ga) 6.2+
Käsitsi tehtud stringiindeksi arvutus Swift Regex — literaalid lühiduse jaoks, RegexBuilder struktuuri jaoks 5.7
Fikseeritud suurusega [String] kuumal rajal InlineArray<N, T> 6.2
withUnsafeBufferPointer .span / .bytes (RawSpan) / OutputSpan 6.2
Käsitsi Task.isCancelled žongleerimine kirjutamise lõpetamiseks Task tühistamiskilp (SE-0504) 6.4 ⚠
Sõnastiku käsitsi ümberehitamine võtme kasutamiseks mapKeyedValues 6.4 ⚠
@available(iOS ..., macOS ..., tvOS ..., watchOS ..., visionOS ...) @available(anyAppleOS ...) 6.4 ⚠
Rocket.SaturnV, kui tüüp varjutab moodulit moodulivalija Rocket::SaturnV 6.3
Üldine „hoiatused vigadeks“ @diagnose deklaratsiooni / hoiatusgrupi kaupa 6.4 ⚠
@unchecked Sendable weak var-i tõttu weak let; või väljenda mitte-saadetavust ~Sendable-iga 6.4 ⚠
Binaarvormingute käsitsi parsimine viitadega Swift Binary Parsing (ParserSpan, ületäitumist kontrollivad parsimisinitsialiseerijad) 6.2
Kohmakad testfunktsioonide nimed toorsed identifikaatorid: @Test func `fruits have a tropical climate`() 6.0

Veel tasub teada: Swift Regexi parserid on kombineeritavad Foundationi päris parseritega (.date(...), .currency(...)) — ära kunagi kirjuta kuupäeva või numbri parsimist regexi sees käsitsi. Määra lokaat selgesõnaliselt, selle asemel et süsteemi oma pärida. Ja kasuta NegativeLookahead-i või Local-it (atomaarsed grupid), et takistada mustril kogu sisendi ulatuses tagasi otsimast.


16. Olemasoleva koodibaasi migreerimine Swift 6-le

Järjekord on oluline ja sammude segamine on see, mis migratsioonid seisma paneb.

  1. Ehita esmalt uue kompilaatoriga. Lähtekoodi ühilduvus tähendab, et see peaks Swift 5 režiimis lihtsalt töötama.
  2. Lülita iga sihtmärgi kaupa sisse täielik samaaegsuse kontroll (Swift 5 režiim + kõik Swift 6 hoiatused). Alusta kasutajaliidese/rakenduse kihist, mitte selle all olevatest raamistikest — suur osa sellest on SDK poolt juba peaaktoriga annoteeritud, nii et parandamise määr on kõrge.
  3. Paranda hoiatused, odavaimad enne. Oota sadu hoiatusi käputäiest algpõhjusest: var globaalsed muutujad, mis peaksid olema let, vabad funktsioonid, mis kuuluvad @MainActor-ile, üks avalik struct, mis vajab : Sendable. Üksainus rida võib kõrvaldada kümneid.
  4. Lülita sihtmärk Swift 6 keelerežiimi, et töö lukustada.
  5. Liigu järgmise sihtmärgi juurde ja korda.
  6. Refaktoreeri hiljem, eraldi. Ära kunagi kombineeri olulist refaktoreerimist andmevõidujooksude ohutuse sisselülitamisega — pead mõlemad tagasi võtma.

Võid range kontrolli uuesti välja lülitada ja tarnida; iga tehtud parandus on tõeline täiustus, mis jääb alles. Lülita Approachable Concurrency ja rakendusmoodulite puhul main-actor-by-default sisse enne alustamist — mõlemad vähendavad dramaatiliselt nähtavate vigade arvu ja Xcode tarnib migratsioonitööriistu, mis rakendavad paljud muudatused sinu eest (swift.org/migration).


Kiirteatmik

Vajadus Kasuta Mitte
Andmetüüp struct / enum class ilma identiteedi või jagamiseta
Jagatud muudetav olek actor või @MainActor klass class + lukk, mida pead meeles pidama
Töö tõstmine põhilõimelt ära @concurrent func … async Task.detached, DispatchQueue.global()
Teegi API isolatsioon nonisolated @MainActor, @concurrent
Fikseeritud arv paralleelseid töid async let N struktureerimata Task-i
Dünaamiline arv paralleelseid töid withTaskGroup (piiratud) üks ülesanne elemendi kohta, piiramatult
Alamülesanded, mis ei tagasta midagi withDiscardingTaskGroup withTaskGroup, mida sa kunagi ei tühjenda
Kasutajaliidese sündmusega seotud töö Task { } tagasikutse sees tagasikutse muutmine async-iks
Andmevõidujooksu parandamine lõpeta objekti jagamine @unchecked Sendable
Jagatud mudeliklass mitte-Sendable või @MainActor Sendable + käsitsi lukustamine
Blokeeriv primitiiv üle await-i mitte midagi — struktureeri ümber DispatchSemaphore, NSCondition
Polümorfism some P any P, kui sa ei vaja salvestust
Heterogeenne kogum [any P] klassihierarhia
Jagatud käitumine, kohandamiseta piiratud extension uus protokoll
Kohanduspunkt protokolli nõue ainult laienduses olev meetod
Viitetsükli katkestamine struktureeri ümber puuks weak + withExtendedLifetime
Sobivate elementide eemaldamine removeAll(where:) — O(n) remove(at:) tsüklis — O(n²)
Otsene ligipääs järjestikusele mälule .span withUnsafeBufferPointer
Fikseeritud suurusega puhver kuumal rajal InlineArray<N, T> Array
Uus test @Test + #expect XCTestCase + XCTAssertEqual
Sama test paljude sisenditega @Test(arguments:) for-tsükkel või kopeerimine
Testi peatamine ebaõnnestumisel try #require continueAfterFailure = false
Ajutiselt katkine test withKnownIssue .disabled või väljakommenteerimine
Diagnostika tarnitavas koodis Logger + korrelatsiooni ID print
Optimeerimise otsustamine Instruments profileeritud testil intuitsioon