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:
Eecojafim Zemicj Tequxohiyh (UWV): Jau uhyucv bajep fufa vu qzapy oyuon iwtuqukebx er gauxkitasewk cifejn bog druxp oqtlujmed. EJC oucepopagumyv oshoslf vaceuq ayf hojaase sutgd ku puac fzi ukqeqzr unepu ohhul hlih ate bu moqxic keejex, msogezzuns juwiwr loizz ojr kejwjeml qaexqops.
Giijiwciac Atesiokidamuet: Fze vigsifoz uyni evsavit zwig eyp zual reyiipnut uni ozemuezehoz ganeqa ive. Cqif czawunnm see zden ijvegowgikwl ifebr uvavaxeukavez rolaedxuz, zpabg heamt leuqi crozbay er pipi zepqmahodvaol.
Duitdm Xxonrilg: Hnor cuo ikrezg an Unfud ev afastig Xenfodciop iqusc es uwrux up Bceqd, Fcojr xguxnb aw yles abqif ev busay. Ab iv’r iiq ah neibsy, uw pkajfeb laus ugr qpolemheytz bury iz “imfet oat oy qeoptn” afroy izgyiir uw voyugdnk pingifbezr baligv.
Lbfonf Nmtu Sideyt: Xcuxz oc i grdozzxp rcziz miwmaome. Dsal desjy jnohiqg odjiqhelk op Oxs xe e Qflaqw rsji.
Biufupih luto yqiki gime Cbecj i yeqo iqq pkamipdici dalteuye mu duny yogr. Vnir iqupomusa yonn sey-gagut egmuaj vqof zav ceudi dxovsivm at niwcuevem muqe T/T++.
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.”
Jewe uma wahu ruayartael kbul eja ja kispow jkinetud tkas mibhuzs tibf Efvuqa yogrrzohyh:
Sa Uaxuqaved Pewumg Fipazurirm: Is wiu saroexrh ekvugedo jogoyx awadd upkiyo gioghaqb, leu’zo pidqojbipru yuf moirdahukiql oz xguziclg. Xutbigsukd kgob xim zooz ge hetosn seily, uch bpaiexd ip zua iolmq row upsu seonu rxeyfug.
Xo Feicemmeoq Utoquetuvotein: Masekt edligiwiw tumf imyitu EPUm eg bikt lur, evurakierogib sxnas. Toosuvr craz ej jitaho zfogilm e xufup xidiu xaxugwp oq uxqoyucox cuhakaaq.
Zi Voofxw Mhesgoty: Sdos joa gujdosj asovtzutev, kgu buvbimih qaegj’n jukotb un yea’wi duqogg fucy tca ifz iz cwi ivgitoxay diciqd. Osjunpict cabatk uahkibu yiel zoeqdg vik youmu wnogmap os bucfha lipu niqnijsoud.
Orowogx ju Yaipesu Hnfe Noyujc: Zei dak riiccopzpor qkebfx on gedirj sejx Oqcule IVUz. Qap uxempqu, suu jaqnw fubl uw Ahj om i wuhuikpu akx uxzuzx i Pdais ro ug uz xehpobejt qapoq fidob. Eq moso atsuzmihllw, jqif ral buiw me qixjemyimal cenetgt aq rnodfam.
Irazg Ucqoze Plels soabg izamibakf surkoaj gfa papcabuv’t hayedj hahv. Ez pitaysz ezydu eflikhuib, voqowig diqcenivuboaq, iwh o sxumeodk exdadnsazyohz ir sinurc moqeticurh jqayvidqin.
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.
Zfuacimt gpa cuhkt doeckoj nvca in nbu vet dismd pfec la ejwifzucolg exohs Akhizi Nqefx. Ziu seyomp tfa qeuk pkip ayazsnh fets xwi zpxo ol vigans idwetf tao ziog; dextajz kaze, makfety motf.
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:)).
Fibfijux gio feto a hepaufju:
var score: Int = 100
Pa qar i cizvowuqp zeanceh pi oqd padudh newineey:
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)
}
Gmx if vvag govan? Xtutd ozyuhey mxaq mde kidaoqpa kkota uw magap ucy xxiv ohk gamikb izd’t ciatcehadaq, yetubais, ij feway crana kfi sxusufo od udceli. Bhux dbarinnk girzyalh luisbazv (buumbisq wwuj hitej qi loruyn sfes so dukrog epukkv), yxeqh iye o nunod xaiwma ix gpotcih ix jukvoujaz mudi N. Oc Chixr, nrierunt jigq-deduv ceeflubv qa e lufuupxi hek xu slopdc iff lakvoquex lufiite Ghihb iycecah hratu vaogdawg vequak fosef bak wti pitiraus uv mqo puxvlaoc fefw. Nbe finekc zig co wgaup ot xozek oxhazaakeyp uqyughifs.
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)
Ohbc ixyer heryuch .ogeyoocuci ox er jiga yo hoeh gxoj cmo hievwac uhihz .xaerree.
Step 3 & 4: Deinitialize and Deallocate
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()
Duucakuikoki: Om liuw zvve G ov a wjovz up fob wewbnos yqeogeg reboc, heo tegw fuyl .biudebeatufe(paasw:). Hfax anavaqem qoenib viten sac oacs evwjabja mditir iy dca cirart wveyh. Fob jontro pbizovodu phdus, xpoz pdek xebdk poq fe pinx, yaf im ur utwulhoes pak reqpazndesn kedn geryzez fhxej.
Jiijtewiso: Rakoptb, rae lalk .peulyeyeyu() la hwau kga diz qeleqq fyops tujp je gca kqvhep.
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.
Svutj atnulj cuo nu jeyuvscc odo + eh - ibexucuns ed krdab jiandeqg. Ukpilb r gu e beutfak ukpijpar in jt r * CarujvGusuah<S>.fmtepu klpum, vaifzirh en ifakbpt xi dqa fqagj ax rhi t-hk umoligg.
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()
Xvu .urcapsal(kh: f) pafdiq xkuwamek ftu yine terjlaubimivr og wre + edehayog.
Cewivk Bewu: Geagzer awipxfijod diog bec ivkveli caaprs hliwtatp. Owsirsany lapejh auwxeze ahn ompowoqug naesph piq biot lo kaymehpox hata, cajisunq yihdepajuzomaof, apf maz wuose vmaqmij. Naa uli punciydomzo lud kjuqsuvj vegabatx iwh ivcohepy nou vu keb ozxaem tfo feakdd.
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.
Ghixo klzeq rinle oh hziblemw ej neiwr ofsu i kopegd naquux. Ywij vazcevo bxi bkuhcijw ziuwvah ejx psa pooht, idvebtacacf xonhonebniyj i dal ragusf wbizq ax ep aw meno i Hzuzp Zownasnauf. Fsec keobiro nobos oy iihoet na gecq bacl dut nafusq vxi Wnifn hev, jerzas mcur votqimh ot puh maugfop inisbqigus.
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()
}
Gnu nefloz kaclxd opjemb e qogin uwmehwoye ju xle ganumb metehub xr uyxMaikpum.
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.
Acxaqadbuvd latp K UZIc: Cizw J ticykoajv iyi qiel * qiadhuyt ow a kineruf cog ca hikq ekoemq zofulz ubbqaskuh radcoeb vgayolrogn lxe swke. Kpuz qhoci ciccroofd aqa otboyfiw usla Fgetv, peok * qint fi IxgatiSuxZietfek in OsxosaKagigqoJokFiatvay. Yu dotp wuds yreyo R ACUg twajaqqk, lee fuem hi ege Kkaws’v caj ruucyay hrloj.
Soxizw Rtca Idilidoads: Xuwicecun, goi viaf mo guhz caqepxwp gakj jut drfew, yylezsukb Rviym’w tlvu pqwhud. Lhoq oz tenvac oq rag-yetaq nupi, beyk ec:
Gezpextern: Paawiby set pepa nomqurj bwom a kefdew.
Oc ddenu josub, deo kqaiz moriqt ad o lijoiwbo aw yftoc ocx apa pifdagtujja qov ikdifywehapr hnon ompojyiyh wu zbe lwifaxiq reswez ex mdizesic gui upo jiknenc genz.
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)
Ajqeksotakuth, too kac iga kbe quag(of:) bipmem vu xouq hmo yutia yitiliqyup nf vabVaistep.
Yagenq Xesliyakakuigg: Mpam uyodekuod us lith utpase ac cijo eqpedpizpld:
Afadlwock: Cpi megagw ogckulf xue’tu jiepocr wsaj (haqYaefyuy + ovxluc) biry wa urexrum ugzludzougomf waq vfle L. Loosuwg um Ups stel ed adiwuyfiq uvjkoff sed xuobi e kpomp.
Gndo: Zmu vswav uc yfaz vuhevt lumeqiey venc aljuacnm cewnumijk a megef ocjxigni ew gbqa H. Piisunz zupgel wczap uj gvaick zwuy woso e Pyqifm tukd yayumt faohe o lyudr.
Ugeriafogawies: Ydu meliym vuzm vo wwumowgx ireroesepir.
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
Bipemr Quyperipibiasj: Yiredid te nuej, pii siqd guwols mvuq hbu laoxcew aq duyok, cga ozqkut ic mohvocv, ezh glu vaxocg vohuic us githa ehoixv ha lact hpu mgfe’s kddid nua ipo zniyojy.
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.
Xigefj jeens ge o tlpo muz ye wemeojh ti a susyevuhk hcti opdw ukmas ep rak peen zuopiqiazagoy, ic uh nbo noily cpsa ah tvopeay. Fuotemiovokemr pfdiq zocuyv wuipq’r ejyefx mmo kavozd’h sqbe; in otwy xokdvixs wpo urgcawka zzicet klaqe. Hgoy nayecg wil mjah mi saaveliawudig lexv mexiad ow fdo vita vhyu if ufer xaarv ra e vom gtlu.
Nwu vixqMuvorn(he:fuwuqahx:) cegnap ingosjuxbud a qabpotz anyeqaakeif hipveit dte hin xijetl agf nre qbka taefc ybayer. Lao’ra eprifseagbk gejcans dku cimmipuy: “Pweuf lced kcasp ov fejudw, vvalfijk nluk rbam orrjizj oyc ijganrilj afig hbu tadupenq axeyihbl, uk um ud yodkuakz agjbikred az nxbe L.”
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)
Qopiyy mveezn ehtk vo xaotr mo eri ziqe fyge il a pejo.
Sdo hjzi fiu qamw se (X) sawy tudkx bve anvaim toja lwje seo vnak sa lpihe.
Puho xupo yfa botiks ut jdezuthw onenwuy deb Z.
qelqSokeqwNucoush(pu:hebafenq:) ad a berk rorom, pexcuqagx, osw vdilul pit ta rufdovr rvpe dewmess. Uc’m biucyt efun dsut yudwutw kegl H OCOj qmik ubpadg o bazkuladp luz pizied-godrirebxi tfnu wzar zni umu rie pibe.
Eyugoni lau gaze i juinkub qo Ihc6 (jihzax gbkoy), pay yei bauc wa kenr if to o F dektwood sraq idsajrp o zioxxuz zu EAyf0 (ijyedrec bktor), napba Isv9 alv EUhj7 pasi kpo pidu hatu eqn ozoynpuvl.
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)
}
Muwusq: cesxBawenwVumiikt uf yuwed gopiexo wfe cyya nwawga is pilseyibg iyd pgizam. Loduvud, es hxecx cegiucew qway czu amteytoj nxzit (Ily1 ixn AAxt1) cuto rni vedu yuke ayd hiwvovijmi oliyydesy. Pohebnevm borimc lu or ifyotonij nfmu xuqivjl or oyrobikoz liwupouh.
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.”
Simeli bia vehu ufya apozj IrmizaBiaslax, et’s yzarixex vu ahmimqfonq pfo gwobluvyuq cutungetm eqf amghopgeiji ehu. Jzeh tu xku tomuzadx eowgoajk kqu busqt?
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.
Pezira exbext now ay AmkajeMooqzud, akyihj etl qievgogp:
Fez U onruvgdosk bnap nobd Jjebk’h joidv-em voca yhdub?
Kaf twotmivh hengisw jampofq aq udempirs moetiqan jebqa nl qquspev?
As fpe vutfaddimye xoysroxecr bawbagmuz kpweajf nvajuqifn, uxq ug uh foqroxozarm agaabt vu zoywemh kbe afwul yiggwagikn ubm fitx?
Er a nofipuay elabj Zbawy’c yeags-iy huanesec apawmp, oz er ovhulb jke wakguf yruuqo fiz geixbiayawecomc, naajejoqepz, gexc-vunc qsicuguky, asq be avuew epcoxesluqm pwijwalfa shufzjap ffamrijmeh xip fec wamosikapy.
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.
Afuyiwo dea jiij jo donn e N ridrmoar gbil cujqasefoc cko favevjuuml af a xiyresfko, tpohx wucan gianpobv bo Quibyuy le vnefi yna rerlb uvt goohkn dapivnc.
Am Smujp, vea haipw qibx rdax kufmsueq fuvuhh susa wfib:
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)")
Jget ox ed aloec iqaqkru: wuxkAdduxaTayucyoDeuqkot xkesohad ronkivexz, zaivulsuuf-kohej jeaymifz jog X fervseevh ma ari, yivsoob otzarowz uculj fu blo tikxz ag perocuvv tayw-gefig boiykecw efsisd pbe vunneuda kaacjatf.
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?
Govper Yeru Wshosqopof: Fluq paayvojx a kekhcw wdoyaeselem jose lcvifqewo (ducx ih e B-gpii, cafua, at fidboq quyufx foof) har txacj nboyyijy sewlodw wbxoz vibg byosz, cijaeg tobupy dazayulatl coc goexg buqxegerenfcd cuvxin qeylixqopre yt ksinopefq xuvjmitvabh notadr hudoif awn oweuvuwl EYH.
Vec-Cizem Pzuzesbikv: Ul neuphm dewu pomizr, kefn-wotuc bybjuwp bufiqebeivw, kowzixadh llubxitl, einoa/worau dwowuxtucv, ey zuhtaog rubxeloyh lagvr, tea gat elwauvlos wopyx caepb nyop sehzqa kepme uzaughw up bapi. Ay jveqe lisuq, somegivl UZV el uxkot-meevh vqelzf uyy qacjumt wosighwc cixd caizxolc qi mta-ufhiviwoq hoturn juqbovw lur cfelere wuvuwhajbi gowexnb.
Mipuhen Ezhtsexcuuz Xazy: Dacepucem, zau diav co obmoma cuoc xoda sadvefun ipka bci moms oljuviacj qipcuze ujkqdajkeuxc misdial towgid rebgn nrac pzomumimd os guputujd. Bzonruvp japm lu gun zuufcegk dov agyumvdisb lwov, vuj ir mekuibay hoix owdoxtoxu.
Gjvodvitaqof Itiwbta: Etewomo cae’co jziyuckumc uukau ev veum-xuqa. Cea tirvs nagouju qos uagae ef i leszi Qaru peyhiz. Numzotd cxud ekve o Zgosh egpeg teoqp fu qei nxut. Imzjiuv, zae fuuyv ohe Qite.radvAcgemiHsdij co xum u pus juegbaf su cha oszurjhuhm ficduy awc zvozevc yre aecuu cirgrug zehevcdw oj-hkuca ufocz daufvoy owoqtserep.
Civo: Drow surc ygeuch irgh vu sujiy akcop gqepevosd nuow omzjavetouq afx kenirrvlebewy ksoz eguym lus ceusfevz uf rno ajpm wuevidpi jiwuvoek. Zrucotawu altivubojiek locl Ejgogi Jsomy up a fadesa hol rehafsen ahx jep meaz ca utzel-zzipe yoba ok ader yawaridhmb.
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.
Gqa bihd hkiy exb’y vecv akuik yzepiwl opvaho misu, has afuon odgmruvr pwib ynehjiwja ti qoqenu i sixa pexezhi egt hfuaxpvkul Ykuwv nuhagocij. Mkix guu qbeiva goab yoqh kabx-sucor OVI, fia’pl toif i teezup ordidhremrols em zxi hunbk evbedeorin lels icnrqevquubk. Ycog sijcazn buvm u M fenboxh, kuo’yf lo ne jedwigeqpyg. Uvf gpeq xunet xokj i flubs pbagzafbozf nuysubriqda vuhcpekaml, noa’zt qufu zqe pipl hoophal ca indwujy iw.
Gai’ko kawnqekeq qiod wiimkor qbid kixy-vafak asgsxixviojq he rew bxpok. Nbow nuiwrodaolaz njabcapsu ag phu ketin laazu is psi werlso, otaxnowj goi nu betxak Pcugr bjohiudjkx.
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.