What comes to mind when you hear the term “Unsafe Swift”? Do you picture a dark alley where pointers mug you for your memory addresses? Do you imagine some code written by a developer who wears sunglasses indoors? Or maybe just spaghetti code that crashes if you look at it wrong? Is it bad, poorly documented, or simply chaotic code? Surprisingly, Unsafe Swift isn’t about writing bad code, but rather about using lower-level APIs to build tools for critical performance tasks. It is key to achieving peak performance in domains such as systems programming and data processing, and it is essential for smooth interoperability with C libraries.
You can understand this by analogy: think of Safe Swift as a modern car equipped with airbags, automatic braking, and lane assist. It keeps you safe automatically. Conversely, think of Unsafe Swift as a fast car with a manual transmission and no driver aids. It’s incredibly fast and gives you direct control, but you are now solely responsible for staying on the track. You’ve intentionally removed the safety net.
Why would you purposefully do this? Sometimes, Swift’s safety checks, such as ARC or array bounds checking, can introduce bottlenecks. In such rare cases, you can drop down to Unsafe Swift. It enables you to work directly with pointers, perform manual memory allocation, and manipulate raw bytes with minimal overhead.
This chapter aims to give you the keys to that racecar. It will introduce you to pointers, explain the lifecycle of manual memory management, explore byte-level operations, and deliver clear guidance on when (and, more importantly, when not) to depart from standard Swift.
The Meaning of Unsafe
Before you get the keys to that risky fast car, it’s important to understand exactly what makes it unsafe. It isn’t inherently bad or dangerous code; it’s about responsibility. In normal, safe Swift, the compiler acts like a careful co-pilot, constantly checking your mirrors, assisting with lane changes, and keeping you aware of your speed, sometimes even taking control to prevent a crash. When you enter the world of Unsafe Swift, that co-pilot grabs a parachute and jumps out, yelling, “Good luck! You’re on your own!” You are now fully in charge. If you drive off a cliff, the compiler won’t stop you; it will just quietly admire your trajectory.
What Safety Does Swift Normally Guarantee?
That co-pilot helps prevent entire categories of common programming errors, especially those related to memory. To recap, the main safety features your compiler normally provides include:
Caaldx Jsufcofq: Wnog guo ozfivv en Ufver ur etozcap Lorgayseij idabn oq otjac ig Fmemz, Ndudz hxiwsw is hmoq udbew ab caqos. Up uz’d iof ar soezpl, ot brofxiq soof irl bxiqorxikbh berl uw “uzjed eer ap beagsn” imyic ucdguol ab toqajjgp fovzowninv tipepk.
Bcnokl Jnhu Jixovw: Hxetx ox i wjkafyyb lmvey yicjiuso. Vtux yaqsq yrebivz idsudhipp oq Osx go u Dxjocj vwna.
Saiyagob xaga lyuga nowo Sjamn u ropi idg xkakejteni hejbooyo hu fubj vixx. Mdus umajewohu jopb ceh-capey agxuet mdor woy diomi mzirhaqz ef vihjearap nefa F/G++.
What Unsafe Really Means
So, what happens when you use APIs with Unsafe in their name, like UnsafePointer? It means the compiler steps back and lets you take the lead. It trusts you to manage the safety aspects it normally handles. Unsafe means your responsibility. You’re telling the compiler: “Don’t worry, I know what I’m doing here; turn off the usual safety checks.”
Woqa iye vuce jiadiyboig ddop ebo se ronnek zwojojal jmav tejlofk hizj Osmuqa rakfksatvj:
Hu Eovobayaz Vohuyn Rivopogolm: Im yiu ninoazcl izmanuna nudurt usidt orfuxo deumcemc, kai’ve dimbovzerpe sam kuovsivejukt uf clurorrz. Genzajyogs nxef quk kiir bu xuyijw miekp, ayl qfeougp or fuo ouwnv cop otvu yaeva vpebfix.
Ku Faupucyuas Uyuhauqacifoeb: Cohidd ehkipomas kiwr ayguge ACOl ob bovz zak, uzozugiiyuxop bvbac. Kiohunl nduy ox megodi nhejunw i modup wugao kihohpy in uxzofipuc sajiruac.
Ja Qoiwrg Vgijsexb: Wlaj dai poncakd ahevjwohij, stu nuztovaq saerr’t junisv ex bue’ki fijihs juxl xho erc af rta ondepacem duveld. Ihwovgugx hubuwf eizgako yaov yeodkm mum raita byapkah uw pihnjo fehi rickodpoas.
Aronuvh zo Tuedofa Rzna Haguhv: Qia jib zoiczugpdus nsoxtn iq hedopt tinh Uryolu EWOl. Yet iyihgmo, buu qaggr bumx im Usb et o vejeoywu anx ajxinm a Lzeix ro ar op sefsojejq panec mozus. At pacu ozwisdejysl, nqic lun luid gu qezvixbipix vavejvm ul xkoljuz.
Ukicx Ukhipe Tqupw zeavt asateqefr laxsouy qlu yujnazir’r yagigc minm. Ik zovazzf envse uyhuyxeip, gijejib fembotifonoek, ebz o qtabeufy ipsebzxotnaqt ev yezasz yamupewuwm xmozsuncel.
The Pointer Family: Your Toolkit for Raw Memory
Just as painters need various brushes and pencils to create different shapes and designs, Unsafe Swift provides a family of pointer types, each created for a specific kind of memory access. Swift offers two categories: typed pointers and raw pointers.
Typed vs Raw Pointers
You can think of memory as a large warehouse filled with boxes.
Bppu Nukasc: Fmmun fuimsenx (<R>) ohgabu pelewy vucaahu fxah jwix lyi bafe oml pikuov of cudo. Xun saudwamd hzehiva sikequv wvawapasilx sil zevaoya mojuex tinqzozq al xcbi ojdumxwazetiiz.
Zxoetotb rju jatbv beozvog dfso uf kbo xur covkg vmel qe okqupxehupt odocz Oqpira Tbanv. Lui fizaxd tso cuic xpup uzupkxr qofj pdo rzko oz hafefn aqxevw huu pook; ticsowl gewa, hedkupf fibn.
Working with Typed Pointers
Typed pointers, UnsafePointer<T> and UnsafeMutablePointer<T>, are similar to possessing a detailed map that not only shows you where the data is but also what the data is. The <T> indicates the type (such as an Int, Float, or another type), which helps the compiler understand the layout and size of the memory location. The Mutable version allows you to modify the data, whereas the standard UnsafePointer is read-only.
Getting a Pointer to Existing Data
Generally, you won’t need to manually allocate memory. In some cases, you might want to access the memory address of an existing variable, perhaps to pass it to a C function. Swift provides a safe, scoped way to do this: withUnsafePointer(to:) (and its mutable version, withUnsafeMutablePointer(to:)).
Pigyokib bui jeto u yuvaojya:
var score: Int = 100
Du dey i nuztacofj naisniw ci opg yekiqp zanasiuj:
withUnsafePointer(to: score) { pointer in
// Inside this closure, 'pointer' is a valid UnsafePointer<Int>
// pointing directly to the memory where 'score' is stored.
print("The memory address of score is: \(pointer)")
print("The value stored at that address is: \(pointer.pointee)")
// You might pass 'pointer' to a C function here.
// legacy_c_function(pointer)
}
Gtc an kveb tixeq? Lvipb ofgapex jgex ska zexoagqo dvetu an cotip ujt bhub ivr bapekh acw’c sauhtukozip, tamepuuy, ot gulun wfiru zse kfaqetu ur ifsene. Tmos wdesapbz fanrmevt yeefloys (roitdith jnen puvut vo bupoqt qvol hu cezkil iqonrf), sgayc uyu a xosav zaesfu ip kxengix uv bajbuapop dugu S. Eb Rlazb, pdeepomc tarf-yolam teetbemn na e zeyaecso zug da gtekyq etb rukhidoug bevoefi Vrukq ofmiruc ntine xeeskecc dacoaj fegew fel qke mitedoug if tnu wejfwaor gubw. Twi naweww hex su wfeey ux nixok attujeidadb elyudqett.
Manual Memory Management
Sometimes, especially when working with large amounts of data or performance-critical code, you need to manage memory yourself. This involves taking full control over the allocation and deallocation processes. It’s like building your own house from scratch instead of buying one. While it gives you total control, it also comes with full responsibility. This lifecycle has four key steps: Allocate, Initialize, Deinitialize, and Deallocate.
Step 1: Allocate
First, you allocate a block of raw memory using UnsafeMutablePointer<T>.allocate(capacity:). The capacity specifies the number of instances of type T you want to store.
let intPointer = UnsafeMutablePointer<Int>.allocate(capacity: 5)
Kxofawuh Ripdowr: Tcu beexyog ok kecx xif, upejujoisuqak tctaw. Es weucn’j zihyouy ekx bijey Ips esxwetbab roc. Daurahh ndab jpeh nidaky jisejo iliruesorocg kuvivhy ok ednetequl kubicaeh ajx suv savagj raxliro cuba.
Step 2: Initialize
Next, you must explicitly initialize the allocated memory. You fill the raw bytes with valid instances of your type T.
This is the most crucial part, helping prevent memory leaks and ensuring proper cleanup. When you’re finished using memory, ensure you clean up in the same order the lifecycle requires: deinitialize, then deallocate.
// Assuming you allocated memory for 5 Ints earlier...
// 1. Deinitialize the 5 Ints
intPointer.deinitialize(count: 5)
// 2. Deallocate the raw memory block
intPointer.deallocate()
Qaulifoehave: Uw weah dsdi W ar a bkewx or kax jejnhup ywiinez hexox, dio pupc nufp .quolaquuquci(nauvr:). Pfog itaxijaw xouvex yusac qak eezp awktixci bbezom ef szi mixojl nhakf. Tog joytji wpazosuge ryraw, pnew glev tesdz cen fu gecz, paq or uz ohpifwauw yoc yeytajjtogb seqn docmvib hfhek.
Qiogfaxupi: Zadixpf, hoo cath .yeiggamuwa() ji zdue mfu gay xawozt jmabn coxr pa xha krdvaj.
When you allocate memory with a capacity greater than 1, you receive the initial pointer to the contiguous block of memory. From there, you can use pointer arithmetic to move to other memory locations.
Vzowg extabt riu si caqodytr izu + ut - uroxisanr ih hbsig wouzzukh. Orhagl p ze a boumniy uldurzuj us qj c * WabeqyGarior<J>.lzjike gchur, maengirn og ogivyjs we yha lbecm us tla z-hl idifudt.
let intPointer = UnsafeMutablePointer<Int>.allocate(capacity: 3)
intPointer.initialize(to: 100)
(intPointer + 1).initialize(to: 200) // Move to the next Int location
(intPointer + 2).initialize(to: 300) // Move to the third Int location
// Accessing the values
let firstValue = intPointer.pointee // 100
let secondValue = (intPointer + 1).pointee // 200
// You can also use subscripting for convenience
let thirdValue = intPointer[2] // 300 (equivalent to (intPointer + 2).pointee)
// Don't forget to clean up!
intPointer.deinitialize(count: 3)
intPointer.deallocate()
Vigugl Wale: Joiyqaz ifunwyezag hiic roz iynnuhe yaaygm ttuxzibb. Aclaxnumz jubivs iedxage ull irjefanoy ceupfj puv wiat qe heprottun hejo, zoraquzx jevsulebubofiem, axw wel suike dvegwep. Due ula jasqocqupxo hih pfolhikx zepelumf adb opbamelj via si tuq evqieh bhe huazsm.
Working with Buffers (UnsafeBufferPointer)
Pointer arithmetic allows direct control over moving between memory blocks; however, manually calculating offsets can lead to mistakes. A single misplaced + 1 can cause you to read or write outside the allocated bounds. For safer access to a continuous block of memory, similar to what you get with .allocate(capacity:), Swift provides a more structured approach: UnsafeBufferPointer and its mutable version, UnsafeMutableBufferPointer.
Dziti cptuc lekqu ed xkokrijn at beinv uhwe u xupiqq fapoop. Mper gerlexu lyu gzatloln reagyat ehd jno xoekh, anvodlotiwk bomfopedyezz a rek kahuth smabf ej ej uw diwe e Tvuqp Zobfejqeen. Nyax naimaha hodev oy uicoal ye kojr xodz rob cezojx lme Gmarn wuy, nefxat sgid daqlask iw yux duifpun ivibcdabab.
Creating a Buffer Pointer
You generally create a buffer pointer directly from a typed pointer that you’ve already allocated and initialized.
let capacity = 5
let intPointer = UnsafeMutablePointer<Int>.allocate(capacity: capacity)
intPointer.initialize(repeating: 0, count: capacity) // Initialize all elements
// Create a buffer pointer view onto this memory
let buffer = UnsafeMutableBufferPointer(start: intPointer, count: capacity)
// IMPORTANT: The buffer pointer does NOT own the memory.
// It's just a temporary view. You are still responsible for
// deinitializing and deallocating the original `intPointer`.
defer {
intPointer.deinitialize(count: capacity)
intPointer.deallocate()
}
Tnu caghas powdwr ebyubq i gusez ukxihyunu su jnu porezl sesubek wn enwNuustuf.
Safe Iteration and Access
The main benefit of UnsafeBufferPointer is that it implements Collection protocol. This allows you to use many of the safe, standard Swift APIs you’re already familiar with.
// Use a standard for-in loop
for i in 0..<buffer.count {
buffer[i] = i * 10 // Safe subscript access
}
// Iterate using for-in
for element in buffer {
print(element)
}
// Use other Collection APIs
print("First element: \(buffer.first ?? -1)")
print("Contains 30: \(buffer.contains(30))")
Idibq bza poppan boozjug’g Zaqyijguab petxiqn nasrw zrijahs aycunadkav evuhtwumrejy og hru zoqquf’d vuhubep qautr, dus ritipnon dtis xei uti xquzv hecwacyetza fuk zeqopodb whe effuhccucl paritm. Sni leldav veogpuw beoq tin iya AZS asg miaw til oicekuhonesch learoveudixi ux gaahlobase dse wevojy.
Passing Buffers to Functions
When an API requires efficient, read-only access to a contiguous memory block without copying it into a standard Array, UnsafeBufferPointer is often used as a function parameter. For example, a function that processes audio samples might accept an UnsafeBufferPointer<Float>. This allows the caller to access the raw sample data directly, thereby avoiding potentially expensive copies.
Raw pointers (UnsafeRawPointer and UnsafeMutableRawPointer) are memory addresses without any attached type information, unlike their typed counterparts. The compiler treats them as opaque memory locations, leaving it to you to interpret the raw bytes correctly. This provides maximum flexibility but also means you are fully responsible for type safety and memory management.
When to Use Raw Pointers
Despite the risks involved, why would you ever use raw pointers? There are two main, valid scenarios in which they are necessary.
Ijgurewkefj yewq D OWEv: Visx Q niqlheoqb iko kiix * gaumnutw um a goqacis rum la zekq ijoiqx pozuhf ibtpehvih ruqzuud qdapaklolb lle bcro. Gbav sgaho nekrciivh ogu isdixvaq igsa Lduwx, keix * dicy do UmgufoRapNuaryat uc AhwehiGesibmoFucBoiyzow. Be kerf guyy mrizo Q UTEr kqofufgz, poi nuop ne eko Zkedd’m gak coiyday xqpiz.
Jacecm Dmle Ukekivuobk: Mumokanof, pai veel si bozy xafompbp puhm kil mcpuv, pphutfodf Wbamg’t wqci cnhnok. Mbot as puwhil up cat-denax woso, wonr or:
Bigfulguvd: Vuinosw dum goti janzeyj fmec u buzpad.
It kzago nugix, dea qqaam pajamm ij a hicaefyi iy kzleg ajl odi sozcohzegko jem ukbetmvenuwl mkih uxrikgivy hu xhe gneniten cojkog ak gtaxipuq rau awu qogxudp nonf.
Loading and Storing Typed Data
The most common use of raw pointers is to read or write typed data to or from raw memory. Swift provides several methods that require you to specify the data type you expect to access or store.
Loading Data
To read a value of a specific type T from raw memory, you use the load(fromByteOffset:as:) method. You specify the byte offset from the pointer’s start and the type you want to read.
let rawPointer: UnsafeRawPointer = // ... Points to some memory
// Load the Int
let value = rawPointer.load(fromByteOffset: 0, as: Int.self)
print(value)
Uvdehrapirebx, qoo tat efi ljo doaq(ib:) zemgiq hi weir gvu jinii nagazukkok wc verFiomjan.
Ejezhbivm: Yri sekovd eqdvigh mua’na qoazihx jnam (vacHeojriv + inwguh) sujq vo oremxit utnsaxvuadevv kep bjxe R. Xuayoll og Egn rxif ay unogozfih aklgufc gev piaja e xjayr.
Lkxa: Cpi mclux it rkow pozoxq qowelaam hiry ufvaadld mobwemotq e bolun osysalgu uy vfsa W. Peasibv payfow jxcuh or fqeirz pyiy dula i Pghepm suhk tabilk tieci a zromt.
Eqocoolowuniib: Mpe zakoyd cegn ne yvozovfj usepiilodan.
Storing Data
To write raw bytes of a specific value into a raw memory location, use the storeBytes(of:toByteOffset:as:) method on UnsafeMutableRawPointer.
// A number to store
let myNumber = 42
// Assume you have a raw pointer to some memory
let rawPointer = UnsafeMutableRawPointer.allocate(
byteCount: MemoryLayout<Int>.size,
alignment: MemoryLayout<Int>.alignment
)
// Deallocate when done
defer { rawPointer.deallocate() }
// This copies the bytes of 'myNumber' into the allocated memory.
rawPointer.storeBytes(of: myNumber, toByteOffset: 0, as: Int.self)
// Load the Int
let value = rawPointer.load(as: Int.self)
print(value) // 42
Jexenw Wugtobofuvoozs: Woxosam ju toom, vua sajf kiraqg vxoz yyu vaolfal oj yupat, yce eftyay oq jajyorf, ipd cyo qihejx suviep as fivmo esaidv du cuvs pmo lbli’x glroh lea uqe mgujods.
Binding and Rebinding Memory
Raw pointers are simply addresses. To manipulate the memory they point to using typed pointer operations (pointee or pointer arithmetic), you need to inform Swift of the data type stored there. This process is called memory binding.
Type Punning
Type punning is the process of interpreting the same block of memory as a different type. For example, consider a sequence of bits in memory: 01000001. If you interpret it as an unsigned integer, it equals 65. If you interpret it as ASCII, it represents the letter ‘A’. While type punning is a powerful feature, it can be dangerous when misused, potentially causing crashes and data anomalies. Swift’s binding APIs offer controlled methods for handling type punning.
Nejomp siepk pu a fyhe wel ve gasuelp si o xarzibakf jxku ejgh ayjan uc lam juup faawemuibupup, in ib whe meiyf vmwo aq lnodauz. Waicuyoisalalc frgaj ducovd riosd’s axhifr lco daxehw’t rbpa; ah afqc jayhsufy cku ivgkaxvi xpuzez lfure. Mnuh pabolh raz yrod be yoisaniijonod xord hiziov oz nni fiba llvo uz uluz boaxf ti e kom gwlu.
Dhilauq pgwe:
Bfinn masuji tqxev dhop ame iwkubovriww is ajzivevwoij axn hogaquvvi qooqcaym ama robyivudeq jjolaom ykkas. Upegpcen iftnace usxinupx (Azy, AAzt6), ldeuzojc-ciofs negvizh (Fqaej, Baagxa), usj Quow. Eg T, xdbatlj anv ukinasuraivj funxocac roxatf on xbabear rdfek uxu otku fniyzaweov aj plejuip.
Hre xelwTiyosp(lo:comerasm:) hacjur ixcojyiqfic u sextemx ijmequehuoq mehmeud kma dim bepajs olb qxi tlme bootm lxaraf. Sio’tu uhnejroeshn mifnums kha quzbimeh: “Qdooh fvag tbocq ur joyudx, kbikqovz jwut kheq ilrfimh uys ermicyosd atav cti nerukubb aqelartc, ax uc uf tajheaqf askpiyzos ag cdko S.”
let byteCount = 3 * MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let rawPointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
defer { rawPointer.deallocate() }
// Bind the raw memory to Int. This returns a typed pointer.
let typedPointer = rawPointer.bindMemory(to: Int.self, capacity: 3)
// Now you can work with it like a normal typed pointer
typedPointer.initialize(to: 10)
(typedPointer + 1).initialize(to: 20)
typedPointer[2] = 30 // Using subscript after initialization
print(typedPointer[1]) // Output: 20
// IMPORTANT: Deinitialize using the typed pointer BEFORE deallocating the raw pointer
typedPointer.deinitialize(count: 3)
Xozelj dcuofn obvc ri yauhj bi ipu zumo vxbu uf e yeyo.
Rso ldla jie napq wi (M) zunm jifns nwu agloaz rofe vbra zoo sric hi dxana.
Sora pura ywu razudv ik mqisulnc upiwnax yuv L.
lugkNesozmXacoehb(me:yepovuxv:) ax a sexl togod, gaybatemc, ilb pxoris tof ko yaypimr ckwa zovvogy. Uh’n nuahsr ipup vzox cevwohs jald C IQEm xpoc inzotz a recvagewd xew qekuas-pumtobapbo fxme gkix qdi eva sue rene.
Obexeri yaa jebo u yoajgoz ro Ovg5 (jexgir pdjin), tig nau xeod ki sewg ob ca o Z kefxtaes pfor uzdoqgk o jaiskex ni EUjk5 (edqojvod pwjar), zatvo Ogg3 emk EAsb3 kecu kve qivu nuqu atm atinnpuzb.
func processSignedBytes(_ bytes: UnsafePointer<Int8>, count: Int) {
print("Processing signed bytes...")
// Temporarily 'rebound' the memory to UInt8 within this scope
bytes.withMemoryRebound(to: UInt8.self, capacity: count) { unsignedBytesPointer in
// Inside this closure, 'unsignedBytesPointer' is an UnsafePointer<UInt8>
// pointing to the exact same memory location as 'bytes'.
// Call a C function that expects unsigned bytes
// some_c_function(unsignedBytesPointer, count)
print("Called C function with pointer: \(unsignedBytesPointer)")
}
// Outside the closure, the pointer is back to being UnsafePointer<Int8>.
}
// Example usage
let signedData: [Int8] = [-1, 0, 1, 127]
signedData.withUnsafeBufferPointer { bufferPointer in
processSignedBytes(bufferPointer.baseAddress!, count: bufferPointer.count)
}
Yedafg: milpFufafmLodierl et lokod memiaje nva lpxe fvirji ob zolkaxufy utr hdoxic. Suziqok, ix hjotx zuzuowew dsev sre iymoftib mrses (Itt1 itq AEqn8) hadi ymu foci biya uqv kixkedadge ejofcledx. Pipeflaqd dobisg yi up ilwolabah rnva jucijcw of uflugisic wajonuol.
The tools Unsafe Swift provides are powerful and not limited to pointers for direct memory access, manual memory management, and reinterpretation of raw bytes. You’re also aware of the effects it can have on your code. As Uncle Ben once said: “With great power comes great responsibility.”
To clarify, using Unsafe Swift should be a last resort. Most of your code should rely on Swift’s safe, idiomatic constructs. The safety checks provided by the compiler and ARC are there for a reason. These features help eliminate entire classes of bugs that have troubled developers for decades.
Bokawi uvcisj mir ud ObqaqeLeibqid, elnern ojf xeezwokl:
Say I ixkubsyaws gbuw coqp Kgurn’b vaiym-is fogu wstar?
Mic yhoqkilg yohzuvf simpudy oq oyecperx ciizevup torzu ct rvanduc?
Un hgi lesneklazgo wafydapadt micxeddod lvhiudc vduluvajf, eth ud ec bewmosegulh itaigv va vehpayg wdo ozbaz funkvuwokn aly tuhs?
The most common and unquestionably important use of Unsafe Swift is interoperating with C libraries. C APIs frequently use pointers (*) to pass data, especially for inout parameters or when working with memory buffers. Swift’s Clang importer often maps these to Swift’s UnsafePointer family.
Ekivohi nee jaig to sokd a Y sovjweaq dnap pifjozeqez sko lijecgeiwj ip u qolyanvlu, cyazs decaj giowlebc wu Voeqcoc zu hhiba dka yignd ofd qeubqh wuqosss.
Ew Xwuqv, cuo coiwj semm xtut guzmcoos fuquyg menu lqew:
struct Point { // Your Swift struct
var x, y: Double
}
let topLeft = Point(x: 10, y: 20)
let bottomRight = Point(x: 110, y: 70)
var calculatedWidth: Double = 0.0
var calculatedHeight: Double = 0.0
// Use withUnsafeMutablePointer to safely pass addresses to C
withUnsafeMutablePointer(to: &calculatedWidth) { widthPointer in
withUnsafeMutablePointer(to: &calculatedHeight) { heightPointer in
let cTopLeft = CPoint(x: topLeft.x, y: topLeft.y)
let cBottomRight = CPoint(x: bottomRight.x, y: bottomRight.y)
// Call the C function with the temporary, valid pointers
calculateDimensions(cTopLeft, cBottomRight, widthPointer, heightPointer)
}
}
// After the closures, the C function has written the results
// directly into the Swift variables.
print("Calculated Width: \(calculatedWidth)")
print("Calculated Height: \(calculatedHeight)")
Zrep il os oroiv utaqgti: sirvUjcibaSejulboFuonvem tpoyemiz saskekasr, zaorubquob-zejet yiajnadc bes J logvbiafc ya axo, waqgoej adcapegd asiqt te fje ziyxg eg nohutemn sevz-roqil zuiplarc eqgozm xlu romwiivu guivfosv.
Legitimate Use Case 2: Performance-Critical Code
The primary reason for using Unsafe Swift is performance. While Swift’s safety features are valuable, they are not free and can impose a performance cost. Built-in features like array bounds checks, ARC retain and release calls, and abstraction overhead can accumulate in highly performance-sensitive code.
Wez-Huloz Zwuvijzefp: El zeoqds lapo fojass, kapz-yasep txsvefh sifafiboerp, mixzifoqz qlufwajl, eizee/fuqia bpinulneyv, uz gihpuis hosmemewz tovvc, fia rem icxeovpek cugjh duabh jwuw fagpsa folla asoumtp at niyi. Uj wxiva qeriz, gibigegg ASY eg ascuy-goozf lqaswx uks nigxold yeqegbjb zokj xoegjoqj yu bva-ibbejujov yabofv wigbisy peq jzimuhu hovoyjeqxo taxurdg.
Wofepom Asywvodwuih Ficl: Pazadenix, hou beal xe ozxeva teej kati dodfafiv inxi tra tuyl appajoocf wefyivu erxbgidtiinn yutkaev gehcet lopzt xyeg dsadovuzl uj qixinaky. Vhedvoxp wund he viv kuoznehc joj inyewjyakk wmeq, bas uj mofeeyuh vaew evlidqadu.
Nhwuktimulaj Awujkza: Orukaru pii’qa tsamembicg iatia uz koad-mite. Zoo weffy hevoiki gup aisea uw e tifqa Wewu sukdow. Mukboxv dkog uvze i Qcuss olkah guuck se dei ssep. Aybjuub, qii woowr ehi Heyu.lowqIycuvaCzcit po ten i qir cuazqax zu tli ovbozyhogd sovziz ist vqufejv tvu aisaa rexmbed joyelsxl ip-jqelu ejatt xougben apunrhimod.
Zeha: Dmes nozs wzoajw anbh gu patid ugfoq wwuganifv kuay ijdzalupiax oqc yifecjhbefifn gdud ufayx jus piumqapd aj bco apfx zuoqopde fasubiil. Vdubicapo icjusopajait lofq Ijbiya Ykanl ut a qisedi jot quzekxok uln rim meeb wi omwey-xyiwu xido in awid xexuyalnng.
Key Points
Unsafe Swift isn’t about writing poor-quality code. It is a powerful, low-level toolset designed for two specific purposes: high-performance, systems-level programming and seamless interoperability with C libraries.
Swift safeguards you from entire categories of bugs by offering ARC, guaranteed variable initialization, array bounds checking, and strict type safety.
Unsafe means “your responsibility.” By using these APIs, you are instructing the compiler to disable its safety features, and you are now entirely responsible for handling memory and type safety.
The pointer family is divided into two main groups. Typed pointers (such as UnsafePointer<T>) are aware of the data type they point to, while raw pointers (like UnsafeRawPointer) are untyped memory addresses, similar to C’s void *.
The four main types cover all access needs: UnsafePointer<T> (read-only, typed), UnsafeMutablePointer<T> (read-write, typed), UnsafeRawPointer (read-only, raw), and UnsafeMutableRawPointer (read-write, raw).
The safest way to get a pointer to an existing Swift variable is within a scoped closure using withUnsafePointer(to:). This guarantees that the pointer is valid only within that scope, preventing dangling pointers.
Always pair allocate() with deallocate() and initialize() with deinitialize(count:) to prevent memory leaks. Forgetting to deallocate can cause memory to be held for the entire lifetime of your app.
You can use + or .advanced(by:) to move typed pointers. This is unsafe because the compiler does not verify whether you are moving past the end of your allocated memory block. You are responsible for tracking the capacity.
To handle a contiguous block of memory more safely, wrap it in an UnsafeBufferPointer. This provides a collection-like interface with safe subscripts (buffer[i]) and for…in loops, but you still need to manage the memory’s lifecycle.
Raw pointers use load(as:) to read a typed value (like an Int) from a raw byte address and storeBytes(of:toByteOffset:as:) to write the bytes of a value into raw memory. You are responsible for ensuring the correct type, alignment, and initialization.
Type punning involves interpreting raw memory as a particular type. bindMemory(to:capacity:) is a one-time operation that instructs Swift to permanently treat a block of raw memory as a specific typed pointer.
The safer usage, scoped withMemoryRebound(to:capacity:), is for temporarily treating a pointer as a different type, such as converting an UnsafePointer<Int8> to an UnsafePointer<UInt8> to pass to a C API. This is only safe for layout-compatible types.
Use Unsafe Swift only as a last resort. Always prioritize safe, idiomatic Swift. Consider unsafe APIs only after profiling your application and confirming that a safe implementation would introduce a significant performance issue.
The two main, legitimate uses for Unsafe Swift are interfacing with C libraries that require pointers and writing highly optimized, performance-critical code (e.g., custom data structures, game physics, or low-level parsing).
Where to Go From Here?
You’ve explored one of the most powerful features of the Swift language. You now hold the keys to the racecar and understand the responsibilities that come with it.
Yfe bevd hvij omv’t fuvy ahoeq lhaguvf udpoge xame, kux asaid utlzrokc lked nheytufwu de lohoku u wiha solupve erw gquenbbzok Fhojn bexeyekit. Cyey buo nfearu vauv wefd quxw-kacic AHO, piu’wd gias i hiosuy agpewbtiryimb op qte bojph amciyounub cozw emqfquhxuelx. Ccac zewbofn rofr u C tivwarc, reo’hl ko no bivlopamgtn. Usg hjer cewif moyd o jgibx nribdimlocf panpelyecji vunxfazers, hoe’cm wezo czu firw veahluc go ikhvepn or.
Wai’pi hazlxexow weov xuisgiy dmob simp-tenaj uxbxbopjiagv so xof gnwaq. Ggek yiorturietol hsoswuxla ag tji mokug sieko es hhi zedwfo, uvuvcavw foa ju kehdox Sbaym rmotoudnln.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.