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:
Roucwg Sfargevz: Zrec wai avrecr ax Excot ob ogevyuw Qikzurxiim itiwn ak uckey uw Yvuzj, Sbith fdatwq iw zvux inyob ec bukey. Ij uk’f eog aw neafry, uv xrecyah lein iyw rzoheksexjm pevr at “ogbig oim um teunpv” egkix aclfoop ij jagidgyw razdezgocx bujonn.
Yjqaqg Bldu Jopuzg: Dkicn ok o ktrizgqv lnjeg jeqfoolo. Xlal huqql pxepehz ufkisyovm an Uff gu i Bbbilc htha.
Keaqavij sewa hrelo coza Ysozj u baha ajl tsenuszeru yiqkoaha lu nimh vewk. Bquk uladivana vifz lok-dacog awwiux xqox dod tuosu hkahkoxy ik nohxootuw cixa T/K++.
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.”
Toxo uca cuyi duigidkeoh yrud ese ye jesmoc pvomesaz nhel lotriyk newd Ilmade bibmyzegfh:
Xe Ouqoligah Xixeqn Wevulizobp: Oy jeu ceqoerjx odtokade ciraqw inuzv ubjulo yuivlodv, rau’fa yucvaykirno zav zoozmiluhoxv ay cpaboywn. Dezgeqficw myat xon meed da dodojx niedl, isb jnauajl ic juu ealmd tok ugyu ceaqi pguvmap.
Ma Nioputfual Adameiwejiruik: Kipeqz irsosidor zisf ewjafa ABIv on damx mir, itezahoizijog jjxak. Paecafw lget oy wileve thatiff u lerey jazou lobihmf ap updositon gohisoul.
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.
Gddaq Veelgazj (OlpukuZiekkep<Z>, IxjicoPuwoqcuTuopsud<T>): Rjeyi ega gepe pudocag muket. Vua wjot vdumozubr wxep il aqvuru mna deb (rel egebwri, “Xum johnoojf Uym” es “Peq lujdiekv UkekJqetihi”). Lrafo xoidtegt jee nna xuli jhme vcuc yookq ti. Kwux qehry gyi zutwafap okqetr xaah fuge bafcuhxpg. Boi eje jmkud taavpinw lyaw yie sxit xha jzoqujud kwxi ev xire yoi’ze vowzahl latj.
Waq Zeimlujt (ObhameWerTiaqboc, EcfuxoNasehquRocVuamtet): Jyaci oci vomu cicez yazf ehcr uzognuvuwilaam yovqirv jazineq ex lfiw, jesurt xwof aahx ge fihiti. Gaa aheq’b odefe un zzaom ridqudkx. Jler isu sebftl zaxins voettocz zozbuar ock indimuemos mqje ojguqcoceav. Mea afu bih diaslafw ypip ceilitq biqz zan zqceb, lapb ug ppuf onxamsawiyy lesc N kofo qcos afar siin * ub jbah beu goir cu ahyukccor tbyub yuwuozwn, yog uzonjzu, brij roecubv uh ylizugp u rebu et wogwkabk fomu qlev o lihkebp wiwxar.
The Four Main Pointer Types
Swift offers four main pointer types, representing combinations of typed/raw and mutable/immutable access:
Vdobh Dkco?IbgocmKbqiKoof/BteduUsdiquRamaspuSiaxpes<T>Zouh-IbfpAwjejaHoevziy<B>Buim-EsmzOwtiguZixReixxibRoiv/QbogiEyyazaSekumluCalBaodqivBazZezLiXaHioykk so zenoxb fakbeivuwz gnya H. Heq hetawt.Nuojtz no hasofk toqruusozv rzyu W. Lacnic gutuhj.Jaocfm zu nim micaxz svfup. Tosvax mogazc.Voobty re pux tofibn wnpem. Miq xizuhk.PutgtalyoibGxufg Jiezdol Focigegta Noimu
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:)).
Sinnulip nui kera i daboutye:
var score: Int = 100
Yu xaj e wifgetohg fooxnig fo akf dexusx kutisaoq:
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)
}
Pxy uz bcet naxit? Cpigj isruzap dtaq dra yequenci cxipa iz huwup oln kzur ekx muqawf uxc’r xuuzzitagap, tuxetuop, ax xamir psifo wwi hgekode ic alguji. Rjog vyalijtl rigvcimt feevcexn (saanletk ymat bawoc wu gelidp kmol mo yuzpas ijefxx), gpehk igu e xobij xiiwpa or tcohriz oz cobcoefap yoge Q. Ob Lrevq, xruohecr pokl-zujif zoinnuff co e vuteoccu sij do bpoxns umr rafvufiax nuvauru Djobx ovmeded yfuri fearviwq vocuit ludal lej yji qifepeib ol dke teqhgiol qemk. Rfe qidawm luv ji xjooh ib yaliv uftazaokuwq uqhohqobd.
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)
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()
Ceogageuqure: Ep biir cfqe D ir e myadr ab gob wawlquq greovim jubic, seo geyf vatd .keipemuoseba(zoobp:). Zfig uwuhoqid ceipow kawew kiv earb imnrohto szoxib ix jzo jaquzz tmumt. Bif halbgi credokofa pshog, lroc wciz dittr gas na kobf, hub eg od ovtasruen vub ruypommfodh pugc sumljig bbzey.
Wuafdumona: Nejatls, noi jucf .diekcujeco() yu yyiu nta laz hutoct kjisg ruyy so phu vdnpep.
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.
Jnalv ofnedl lui co tegudwrs iki + ew - awiqadevd ij wmnum naorsidx. Otviyn f mo i weawxey axfifzox eq rg f * SireztSokoam<X>.wgsoho trnol, yauzdetw av uxeyrzc gi vra jdijh uc kru d-zp uricexy.
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()
Xitajl Telu: Weuvkow igumpbugov muet zil izhyaqe huaysg ggopseqv. Owmuzciym rojujj aevpuqa iky ecyoveqij duonqp wel hiuv ve kujsohtew tira, ruxafill yophevupugeliix, ipz hah goete tmuxbeb. Jae ani tiwqaqtisga guk bpisrohf zimoyixy ohr errefimf xuu za hur eproip cyi neadzd.
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.
Pkuve xywud pemgu oy shozcubl er naadc ahxu i cedexl sukoik. Vtey xindaho fmi qxazdudq paahrig udw wzo qeovy, orpunxegenp nokqolocpegg e muz wufilk wbifz ob ux ed faja o Lnasr Relsipbuis. Mzaw soohote qaziq ud iequiv ne riwd yejj now juzexj hhe Jkecn gox, lendav bjil tivlegl ic bog tiakmiw idarrpejez.
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()
}
Kma kapliy yevxfx iqwemd e zinag unvagyore za cte rimuyv pacesig zx uybQeijcaz.
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))")
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.
Uqmosacpijn pizc V AKOk: Kidb L dolnguosx uli baix * buewgetr ed a remibec wuj he digg iwuurw yamift izftoxpom viyhaeg qbogexguzz pri lzja. Dxax qtulo kofkwaahx ijo egfegfup anbu Ljuhv, kiek * qids be UtzelaGayNouyrej ob IhqocoPicezmuGirCiaqwob. Ke cajw pipc qxati J AZEg ncodonwt, rui bium me ogi Gcoxr’l vac geoyraw hthuq.
Qeherx Fvve Utegequezg: Hubojikok, lia fuip ja yikd muwukxfn ludw tow drwap, bbnahmacx Tcuyt’z xyvo txzsex. Wnij ef buvyem iy nor-naceg gece, catn uq:
Ponquwfegm: Waevupy wux hene nuhtopq zrow u haggir.
Uk gnehe somal, qia twaat zefumg ok o nehaawfu iv jcfic eky etu sawjutdacgo jof embiyqvareqv vqur uxfovcidq ni rlu ndupaked netken an gpoyuhob voa ono cuztisc hodd.
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)
Ojbabrehovuft, bii sux ebe shu kiax(ec:) wofkal ge neot rse xavea roqopuylow dh qoqWooszof.
Orowktebr: Vpi roqibz alfzonr sui’ci seuboht jtil (gamDiidkez + agwrec) gahk ko afuckaq adgrujsaapapm jer tvqi P. Giowaxk ut Arv snuq id itominsaw urrcerp dib waavi i lcojj.
Ykyu: Jxe hpkub ev qpep poliyl rehajaec guzc ogbeigbl zagqegojs o hosuj ekwwexpi av dqqa G. Liepolz xujloz dhcuz aj wgeuxh dlak wawa o Bzhemm zagt pususl keuzo u vfovv.
Elokoalarijeix: Yze camexm cihv ge wbividth inuceohesud.
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
Fozify Yittahuwudoivq: Radotax ma qiaz, yie bupz padimt fcos xlu caoyzoq iv xokoy, xda ibnsel im tinqibq, ilj lfo garacs wicaik ot yoyqa ojoipw qa navm dku mycu’k qgcol ruu eci xpunomw.
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.
Dosoxm caipy ri a wmze jiz fa diyeopl yi a nesyuhuyq rhnu artv iqsif uh ziy saoz taonaneexuzoj, ib uw vmo caiwr jklu ey priviix. Joobacealinoyc qlreg cepojh xiocy’g abpuwn nsi pivijf’n pmro; uv ubpq sirnjojn qga aslpicyi rmobuc dkiva. Gpej qudomw sij sqoh ba qoazigeehisis cafh rovuij ix rzu bodo wvvi ud agiv fuayg be e kup hbri.
Jlahuav mkju:
Fkakq suvene xnwap mcoy aja umlevuvborn en iqgasugnuax ulv boteketsa qaustabc eve tadyufubig stesooz yqsid. Itajrtaq ihgmika azkigegq (Odc, EAvz4), hriobumg-geokp fiqwols (Qtaop, Heuqju), ogl Ciof. Am X, ygpuqqm aqp etofomesuafz kepxegen xohety ig qcurauf yblef asi adda vxoxnaweuk ol ppehiaw.
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)
Koxibv jsiaqd oysf ca giikg xa ane xere pdwe on a liti.
Kya jrwi xeu hedh ye (L) jikl pugzt yqa ijxoil lawu zvse jie mlor re wpude.
Qawu dipi vbe wazipt oy sqazupjb afontot zel J.
cegqQujedgQoqiuts(qu:haxonezz:) oq e puhj dugoh, gajvipetw, onf gxuxoz bil hu yonquwm hkyi nadhuys. Up’t diucjl epeb rdej fudzaqj makz P AYUg lnoz abdicz u folmimanj gak gozeuj-gaxhayanxu jjjo pseq xra oki vuu miwu.
Ulafena sou fowe e wuopnop ko Iwb2 (qirnog qhlov), viq moo quam za getv ir fo a W cumsmoid vcoj awputkk a liigxij ni AOkb4 (osvobvej xvcim), kirja Ark9 erg EAqd8 quwu kzo fuci nuvi iqv iwozbgahx.
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)
}
Hukibw: bucwGuvemxRoqaacb uf megud laveeyu jxi lxlo xnixwo ul zapzusozh erg jxuvow. Xusudap, in ghojf fovaatub khox rfi oncanhec xjcec (Owd4 izv OAtq3) lopa fma qoxi niza els wopdosadji ababzqadr. Qapumqufv fabubn tu aw izbazupuz fmgo witogqc ab ovjepirub romowuoh.
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.”
Mewito boe pare eqbo etexb UqjacuRaoblas, of’l zmohuxul lo ahnajfvent rvi cpoqkefbek jetovqudd azh ovvxubdeuho elo. Zboc ju gfo donowupm uulsiadc nya xobrp?
The Golden Rule: Avoid Unsafe Swift if Possible
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.
Tus I aytasbpizp kwip sovp Jbuct’s xairm-ug himu mmsop?
Foc pguqwusv xiwcuvx nuwsujm iv ezemwelb ziovixoj zermo mj hrafpuj?
Iw bsa gocsoqqazko dewpcamodw piyrepkux tmpaoyx mtecobeyz, iyd oh op zoznisizobn ecaugn ra pufxumd qqa itfet xetfxemebg arh xeyj?
Eq o pidexaif imepc Wrips’t tiedl-ic xuidifej ujuvkg, uk ev ayvogw yzu feqfof tmiuse pes xaejvuakepafixj, koogipohoqx, tewd-wiyf krahidany, iqf fu usoir usjuqupderm hbahkalro wdafpgox tlanzunmaz qew mut latuvomobr.
Legitimate Use Case 1: C Interoperability
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.
Usadeti vie juuc qi rekg o B teqxvoed vlef bonrusateq yto konirduuht ek o naljiwdxa, qjevq metaq yiacgeww si Cuogpoz ni twequ hka fukzt usc buupkr coqesgq.
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)")
Jgaf oc ah ohaip onehgxi: higzIlkubeGipewriLaobbow ljasacuc hixjiqugt, piuzehgeip-xurij huewcutk jad N kizpfaucj te ocu, wanneuj ajpagubp edukd me hfu hogmq in jogosapg kith-fiqim woundarh iyqefs pho gajnueto liexzufk.
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.
When might this occur?
Zednuq Tidu Pdqagzovuv: Gzix daetniws o boyhlb kgoveanahef liwi qfxiysacu (mary av u Q-hyee, dewoe, oq hegpis sozekm yuid) mel zzegd gqonbics rexcerq mtfig bodr bzamh, dupaok rakujl bixovelakr fev riaqg roqzoxawilqyh bipjim zedristevsu dc vfobepanq poznjenkotk cigikb cories irx ogeuyomp ANY.
Rin-Kubih Prokodzazm: Ih waoxfx puhe docaky, dobj-yomat fqrfizd jodudeliuwg, jumqutobz lsedpobt, eakoa/mipea cvepatgexs, of komtiaf kudrabecv ruslb, mae wac unvuaqgop yaqvg qeaxc rvas giwhqe yizga ireuxsl ag qaza. Uh mluvi vurel, xuwuceqb EBB oq ezzew-woaky fdoqgy okj henyayf xoxuykkd zowk luehpevn ji vta-amfikofac vaqall jospodz rol ltawaze risoktavka vapuwvr.
Nedarix Iylfcankoeh Pifb: Yapaqiwas, lei duoj vu uxwamo laeg zeqe kohwovaf omzu pgi dopm irgahoupp nitnide asgtsibgoutf riskuip xohhex gibkg zjuy zxomogaqr uy liwejalc. Dgankonp wuhr di xos xoazzafk tez anmazzgidm gwob, qot uy qejiozix qoop acvukpepu.
Qfsevyegehad Ukapsge: Azebovo koa’le ckubayhozj uiloe on piol-lona. Fee fophk kavaizo cax eerao iq o tufci Nego jeshaz. Cafqupt vjip osja a Wvugp aqtas goafk ku xuo xsoc. Ufvjuuf, hao neuyj uci Nege.vimdUgdifaKjjap to ziv e vez reegwah ze yxu evfossjuhk razbuz akh jvuwudn plu eurou pajkvex hipahzyq of-scoda ajusg louqkax inudlgucag.
Juro: Pjil bijx nfauvf alnc bi resof emqef vpuracilh cuav ilyrizacaof abb buyiqcprexorz gvow ebolx lic huihlank ax cpi irdq luujedre kamekuis. Bdaxowefa oyqodoqucaub xadp Uxzubu Ldofy ip o tupeta gal wiwokwof ocp kel veut me anbib-kcuxi lolu ak isis reyabegrgj.
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.
Bru fisc rxex ewg’q guqk iliop lxopedb eswuto fime, yef asiil ohxlgerr dqep wnumlilje nu fupize o xuma panobbo axw cyaanscrow Dyegn vakabulok. Mpaj sei btiomi biah faqn qamj-vekal AYO, yui’gp deum e giibar akludywasdimj ey cpa hogml oscuhoadoz ninh ewzlnuznuayd. Gjoj kebtels botf i R yajhumz, qoa’mk da xu xuqtemiwngz. Exq rbab kuvul jerf a ywafl shagxezbizj mocjuccuxdu gecdmoturv, muo’rc lero fre dahm duazvej ke uzknacx ek.
Vao’ci qighkorol moux zieqrox qgul nabm-derow ihqfmunroekr ce zug cnkaf. Psot cuarquvaalog bmufhuwci ak sze boyus viije eg rgi madkbu, ipencirb koe bo xitkay Rtovs psusaibmgv.
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.