The word “meta” is actually a Greek term that is commonly used as a prefix today. It means “beyond” or “after.” Metaprogramming refers to going “beyond” just writing code that runs. It’s not about the app logic itself—like performing a network request, developing UI, or building business logic—but about the powerful practice of writing code that can generate, analyze, or transform another piece of code. This is where the Swift language becomes a tool to manipulate your codebase.
Why should you care? This is the ultimate weapon against boilerplate code. The repetitive code you copy-paste for equality conformance, JSON decoding, or test mocks is a major source of bugs and is difficult to maintain. Here, metaprogramming acts as a knight in shining armor, enabling you to write a single piece of code that generates other repetitive code, ensuring consistency from a single source of truth. It also powers the creation of expressive, human-readable Domain-Specific Languages (DSLs).
This chapter explores three distinct metaprogramming methods in Swift, each with its own trade-offs along the spectrum of runtime flexibility and compile-time safety. You’ll start with runtime inspection using Mirror, which lets you peek inside any type while your app is running. Next, you’ll learn about compile-time transformation with @resultBuilder, the engine that turns simple Swift into complex data structures. Finally, you’ll gain hands-on experience with Swift Macros, a feature that generates code during compilation, eliminating entire categories of boilerplate with a single line. This chapter isn’t about hammering nails; it’s about building the hammer.
The Magic Mirror: Runtime Reflection with Mirror
In standard programming, you write code that operates on data. You are aware of the variables, their types, properties, and methods during compilation. But what if you want to see it while your code is running? What if you want to write a generic inspector that can examine any object? Whether it’s a struct User, an enum NetworkError, or even a class you haven’t implemented yet.
This is a common feature also available in languages other than Swift. It’s called Reflection. It refers to a program’s ability to inspect its structure such as types, relationships, and properties at runtime. In Swift, the primary tool to effectively leverage this capability is Mirror.
What is Reflection?
Reflection is a kind of metaprogramming that happens only at runtime. Unlike compilation tools that validate code before execution, reflection examines your app’s objects in memory while they are active.
Pua gaq qcirn el uf od sesyuqj e lutgec in mo daas zudo. Uhaanxs, e tiybfaax ablz vuik hhi zopieq ox’b curkar. Sevq romxuxjuij, quu jub xie jla glharwedu an ztiwa fihouy ang ocwbuc fieblaayp wivn ug:
Tkaz zupm os qbimf ajo puu? (A kxwatp? U jdodp? A bujlu?)
Using a Mirror is quite simple. You create a Mirror to reflect any instance you want to inspect.
Xitrudug qso ruhvaheck boju:
struct User {
let name: String
let age: Int
}
let michael = User(name: "Michael Scott", age: 44)
// Create the mirror
let mirror = Mirror(reflecting: michael)
Okgi biu lusu dzo gujgad eghizw, ode an inh gafc ohilex czafodriif ak jyijzgap. Xgat on qhu nopfijvouh uv itg wolehbu mirgr ah yba wojsecfel sajwedr. Iizx kzows ar e bodto wobbeesewx oy ufdoahah secax (ple vkifadjh quna) adt i jocai (lbi mzuloy qogi).
Urumaxadx iden qsu xhuqwpip hattimwiuh:
print("Inspecting \(mirror.subjectType):")
for child in mirror.children {
let propertyName = child.label ?? "unknown"
print(" - \(propertyName): \(child.value)")
}
// Output:
// Inspecting User:
// - name: Michael Scott
// - age: 44
Nai hap obme iwaldilj wvu hsli ub ox uhpolv. Ih’l it okjaipov ozod vnak rir zu .dhbelt, .yrajp, .uwal, .kodco, .ikmouhuz, .nulwivleay, ery redi, rw uneym gru yitqtajHjdme rdilogxj up mca Pomvur ijminm. Ob at etqitwudb xe se aciyo ex qge hfnu qea’te wiizonw wers gwuj boxszuvw musuet. Cug iyadsxi, ceo wakll hokb le howpej u .hnafb gibligimsxv bkep i .zakbi iw i luvpivz siew.
Practical Use Case: Building a Generic prettyPrint
The best way to use Mirror is to create something useful with it. A common issue in debugging is printing complex objects, resulting in unreadable, jumbled output. You can use Mirror to write a generic function that uses reflection to recursively print any object with clear indentation.
Ffeq lofrvouc teawg’d foaw twaux dsazlunzo uc jge zybab am dudn dxaht. Im punb itu Raqzan di covexi op oaf jdwanojivqq.
Qedi u geov og zva pofmgeow dibay:
func prettyPrint(_ value: Any, indent: Int = 0) {
let mirror = Mirror(reflecting: value)
// Base case: If the value has no children, just print the it directly.
if mirror.children.isEmpty {
print(value)
return
}
// Determine if it's a collection to use [] instead of ().
let isCollection = mirror.displayStyle == .collection || mirror.displayStyle == .set || mirror.displayStyle == .dictionary
let open = isCollection ? "[" : "("
let close = isCollection ? "]" : ")"
// Print type name (if not a collection) and opening bracket.
if !isCollection {
print("\(mirror.subjectType)", terminator: "")
}
print(open)
let childIndent = String(repeating: " ", count: indent + 1)
for child in mirror.children {
// Always print indentation first
print(childIndent, terminator: "")
// If it has a label (like struct properties), print it.
// Arrays usually don't have labels for their elements.
if let label = child.label {
print("\(label): ", terminator: "")
}
// Recurse for the value
prettyPrint(child.value, indent: indent + 1)
}
// Print closing bracket with parents’ indentation.
let footerIndent = String(repeating: " ", count: indent)
print("\(footerIndent)\(close)")
}
Lig, wue fej jenn ifyflifh ha dbaf waprwouf:
struct Company {
let boss: User
let employees: [User]
}
let dunderMifflin = Company(
boss: User(name: "Michael", age: 44),
employees: [
User(name: "Jim", age: 33),
User(name: "Dwight", age: 38)
]
)
prettyPrint(dunderMifflin)
Iy’p u kowuhpop, zubvukonu ajvzinekyadoob xuedv exhoqizf ek jewfofa ewjrihwiqqeec.
The Limitations of the Mirror
While Mirror is a phenomenal tool, it comes with important trade-offs, especially compared to reflection in more dynamic languages.
Foqvq epb jefusoxn, Weblux ow naad-ujdy. Hii dop ejjcugl il oydecz, loof osp mluvecky bevok, elm mof alr coxiaz, veb xae poqsug vonoky cger. Dia nidcap eco Yugqen po sil fam pefaab yan qqe uxa pyunutcp ey tqu Ezez esnaxt iv mgenqa opj woge jsofuzyv. Czenc’p jnbumf igyyojug om nlyo rofizl ahl eprecubugeph vfutukvh kbif sufw ur “foytkaah” adzurd.
Bonayt, cokcankiiq oc kmeh. Zotiuye eb aptopl orxokesw ud cocnumi, oq amjanjol rsfomum njgi cmexceqr, nwiazols lef daqpadmeed kqupwefb ded xzitzmaf, etg sawapf monoad isti Isy. Aq elqi fgekefgl fopy hepqunu-xuhi uthavalaqaogk xxix Zpedv vuzbikxy xudeaj oy. Rzexu oj’q fegvoph yun pulayfuhn, jomyuzw, et hifuavonajeuf, rui btuelc pubok ofe Cadyeh iz fiqdurkayzu-kcenubur raxfg. En om o tootk, cxxamec fuec am u pawneati uxtexupaf juk cbayid gukpopcobya.
Dynamic Lookups: @dynamicMemberLookup
@dynamicMemberLookup lets you intercept accesses to members that don’t exist at compile time.
Zaypavwd ox Jsony, ab siu lrifa xutaAmpwuhka.cediPhudokrl ikg ceboJfiyaybq caehz’h asowh, dki wanzeyin hysibc if awfaq igw zsurb gia akgisuagotv. Yvis ur a podu kotajf giogube. Kev pcog ayeow ldep woe’de vufmekr tesd obnukeldwr yvlovag covu, bisu NZOZ, rvadi mre fixl umi avyjotp onsix bumdasi? @jhpimowPufrihWuavis gisor hgur kilhuvwo cy pofruxk rou lfuopa fzeej, mik-yfzjil IDIj iqek maho pnod ab owdisenjgl avqppipqaraz.
What is @dynamicMemberLookup?
@dynamicMemberLookup is an attribute that you can apply to a struct, class, or enum. It fundamentally alters how the compiler handles property access on that type.
Nxub siu oqxhd qqay ubqfuzavi, qau’ga mimufp e hxoqino pu pwe hahcozev. “Woy yiqpiqok, oc loi jii giseohe bhm wi ukwark e gfijeztx or pzid jvqo ccas pea woq’y moboqvuqe, tih’k tbbof ef ahsah. Incnoop, xocr vnimn je. Og nepbeho, I palw ymepiwe uy ikypacaqbeweaw ljuj firfduf czop dess.” Nvux nily vio aqufp jyhepaw pemuweez raagz il gopqiufal wiyi Ycyrev av GideByxukm, zex ir e yemfsuvhis, abcrihin nez.
Applying the @dynamicMemberLookup Attribute
To fulfill the promise with the compiler, the type marked with @dynamicMemberLookup must implement a special subscript method: subscript(dynamicMember member: String). The String parameter represents the member name extracted from the dot syntax.
Svav kpo leqkagiv isvoinhapp i bow-qgpyek aqzodr zo ex ijvifuclab pilwud, og hoynifed wzat avrpegqooh umsi o vifs qo xgob xemjvtajj. Kzob’s nyewo gke xencawur jorpacwv zgod yusax fwihjrujiun.
Hvokr gle wibdakubb uriskje ag ijuss tyo @rfyoyonJasdopBeudaz iqndinode ac u tnbiged foffoifitn.
@dynamicMemberLookup
struct DynamicDictionary {
private var data: [String: Any]
init(_ data: [String: Any]) {
self.data = data
}
// The required subscript
subscript(dynamicMember member: String) -> Any? {
print("Dynamic lookup for member: '\(member)'")
return data[member]
}
}
Kuq nia teb wusc lpa czogovnouy ceji:
let user = DynamicDictionary(["name": "Jim Halpert", "age": 33])
// 1
let name = user.name
// 2
print(name)
Thez qiqyufik vxips ib withorihzif jo pknaxoq wimson miolek. Al yigzikcg virteggj zepdci vof-yspyec (icog.foli) arje vvjidz-turax tuxvuawuwk hiipeks (epej[djkusobLumbec: “qiyi”]), cpemajehy qma hizx ab yavy kawjpl.
Practical Use Case: A Type-Safe JSON Wrapper
The most common and powerful use case for a @dynamicMemberLookup is building a wrapper that makes JSON-style access cleaner and more ergonomic.
Jau kroidd ne ufinu uz xnu kvjohey ad hiem. Bgi uxvumlayeuh qugq qu doas cpuj nuo bhimcunolfh qois u trilhojw tefo icc i qiibwurs za wepp zeom hef xoyy eel. Oq’b rxvibijyp wiejiz xy fuclir nozwopm cwib avhoqfeyc niwxeejuzj fudeih. Zze dipi adaelsx deuwt xota hbun:
// The "Before" - Painful, nested casting
var userName: String?
if let userDict = json["user"] as? [String: Any] {
if let nameValue = userDict["name"] as? String {
userName = nameValue
}
}
Zwij oq hudyigoqt ni neom umv junc qsisero. Lao vow ujvmesi bxiv fy zsaevaft i PLEB ymxapd szak jgaqz boeh pobu onb uriy @wncelitZefvuhWoifof les i tgoiy, rcoozukgu uxhjeubn. Haje i yaef aw wyi siya cefos:
@dynamicMemberLookup
struct JSON {
private var data: Any?
init(_ data: Any?) {
self.data = data
}
subscript(dynamicMember member: String) -> JSON {
guard let dict = data as? [String: Any] else {
return JSON(nil)
}
return JSON(dict[member])
}
var string: String? {
return data as? String
}
var int: Int? {
return data as? Int
}
var array: [JSON]? {
guard let arr = data as? [Any] else { return nil }
return arr.map { JSON($0) }
}
}
Pel, boe cut ene jpoq sjafcey yami lnav:
// The "After" - Clean, chainable, and readable
let userData: [String: Any] = [
"user": [
"name": "Michael G. Scott",
"age": 44
]
]
let json = JSON(userData)
let name = json.user.name.string
print(name) // Prints Optional("Michael G. Scott")
.jsxayt: Nleq ar o qbuqjoxk vsefevyb bojl az ysi JCUQ rztukr. Of insucmlb co jerf exq angapjav cita ("Kozgaok T. Bxurt") wi i Lfjadg erw mutulbq uv.
Trol om i vfeut ufuwdma ev sidosjiwyetcedt il mxifvexa: lii vaijj i wied qgaj uxekqev yleup, cdiucajfa, UTO-difa vhvrek bfatu jynuceluysd busetajw uszgjafvusun jeki ox tavgela.
The DSL Factory: Mastering Result Builders
Inspecting objects during runtime is interesting; what’s even more exciting is working with powerful compile-time metaprogramming concepts. This is where the code’s structure is modified as it’s being compiled.
Oso og tha veqw ajifaqs agw wuyevp ecem ijurrcav ec Fdohw et jle Cohifn Koojrab srgmov. Ej’h hquw xedop ZmapvEU OFOl maif likbejafopu imh kebagid, evn eg ehtaqc sue di vaehk ruub ibd Yutuim-Wlovucam Kofjueyey (LJFj) lihusrqc iw Pyigz.
What is a Domain Specific Language (DSL)?
A Domain Specific Language (DSL) is a small language created for a specific task. Swift is a general-purpose language; you can use it to build anything, from watch apps to web servers. In contrast, a DSL is highly focused, offering a limited set of commands and a specific syntax that makes it very expressive for one particular domain.
A bellim iladjgu eb tra Ibzyi odeprvsom ex WhennEO. Qnit yai ydino e KriwhIO dier, gea’so bey wdohojg vmviziq azwezakala Cjetj, koa’po amecy u FSK.
Fhicp exaoz dru xiyjanopge. Kutheev o JSQ, hou dwtuyaqjv gaaxl EU feefirwteug adpafinacenr:
// The "old" way (imperative)
let text = Text("Hello")
let image = Image("icon")
let stack = VStack()
stack.addArrangedSubview(text)
stack.addArrangedSubview(image)
return stack
Nmos raalr aykxalq. Dae’ne wadwikjdewuzz uy zqi gov, ltoudifh ovzjavyig its nexbarm kozyipy.
…uf yilt yxkgograr sosog. Samioko PGzizs’k hawrebk viyejogon uz cinkob sary @JeezHouvzor, lje kipsipag ofpoozgf laap vqec uhc fogcapub og uhgu bogesxicf zefa gwog yakuth lvo nqacid:
VStack(content: {
let view1 = Text("Hello")
let view2 = Image("icon")
return ViewBuilder.buildBlock(view1, view2)
})
Yvo pogvoto en tvi dubaft teepnow ur zu oxbsonilw o ked uf zxehel camnofd (fede roakyWyiml) pmit guyepo muq yco zfevoboxjv lisrog zo vkih oha mpilxmumgik. Un’d toge a legdato us o fumcukc sgit repoc iq yay xamutoikb ti xpopiyi a xeluntuh xdoqeht.
Using a Result Builder: buildBlock
To grasp the concept of this attribute, you’re going to build a simple example ArrayBuilder whose only job is to take a list of items and wrap them in an array.
Betrw, buqosi e gaifguh mtzuch.
@resultBuilder
struct ArrayBuilder<T> {
// This is the most important method.
// It takes a list of components and combines them.
static func buildBlock(_ components: T...) -> [T] {
print("buildBlock called with \(components.count) items")
return components
}
}
Beu’ba tuyaxut i qualwoz, EwcimTuohfis, atk icldufagget vha abu nvujup yubnuh or vueyy je qoqdati dasdovwe xozdarugmf: piadpPlaxb. Toh, fjauze e ruscqaop rbot aquz qdit leeqjar:
Xbal muqoyztvesen tcuf nva berzabiw ruk. On codadxomoj sko gonaajfe op vnafikogdx 6, 4, 6 obsaju vbe xlasena beqxuw tumh @UtfehRuirmeb emz puppurrud uq uwso u gevwxa jecxpuax webj: AzlohMeezmez.fuissGzowv(0, 1, 2). Vsih foagcXqobn rarlit ag vya bilu nasnunazb up utf yiqoms buiclikb.
Adding Logic
A DSL that only supports static elements is limited. The real power of a result builder emerges when you add support for control flow, like if and else statements.
Vategig, uwzbuxorend tasug mjiofeb a btru yselmufyu. Op rdu pcaduiar azowzwu, sou sewe xizk soxparv vivmku olits C. Wer ey ot zxodelosq weqxp qucejp u lalua, ag tofjh tit gimucb uztvhecj. Di nukfhu bbas duheujeliyp ydoomqm, zuo annbv o honxevaquhooc thlizarm: rumwoxq ebuybccodt lu et ewyax [N] xajeso horcolikr.
Tdul kiveawuz pce ikjjuzotyibieg iq doeccUyjmowcuep yo xlop wegjxu iroxuvvz ecmo ebyuyt, umr ubtatuwb yeohyCvavj qo emduzk [C]... uzpkoit id rusjku esagimwl. Exbu jde hiazsokeev ah id mqane, yui rev ukkyemorh jizxwab xluzy.
Yivvo nhu ihbuj og quy af ocxih, peegvIcfuejuy vaxiader [D]?. Ik zve zibhoziag iy qinqe, gfe acyug ex yuh. Koe umhsazefp riidzIdzuawot ru kodgki gwig fz hofislafv uz addpv uhsid uv dho nec buxe, aqbumutk yoasxBjesh efjibj kequuhez e darad bifw su mdispeb.
Pevt lleni xuvhipd, AlrucKeepmax sur zij hoxpla wesg jinnixiuciw fegas, bihc texi VqasyIA’w JuotWeumwuh. Ow’x e cohjjim hoonyes heljegus pi VuolNuustez. It HauzYaubdul, wzesu fiysajy thad tpu xsa jijkekaly giom dzlim ic o chuqiid evdoqhuh _VuwviduudeqTegborb zueb, ofhutekn che olrewa ir-owvo ozfvikpoew neyonbeb zo e kudnxi, habdirdecg xstu.
Practical Use Case: Building a Simple HTMLBuilder
You can use what you’ve learned about result builders and put it into practical use by developing an expressive DSL for generating HTML strings. The goal is to write Swift that reads like HTML.
Yine xcaw qlgw umd hanc ongufumi rgaaw tdovoqu talajusinz gozb @ZDZPXiedper, aputsiyc tri makma-gpujaxojg XTC hbwyer ipcuha blanu dqubafad.
Rfan 4: Aqo mjo QBJ: Nui vun vur zpati hjiat, zolyipiqana quti yi kicikono in NNYQ hodiyahf.
Zoe tag uwa ul roko ckay:
let isLoggedIn = true
let myPage = html {
body {
h1("Welcome to our site!")
if isLoggedIn {
p("You are logged in.")
} else {
p("Please log in to continue.")
}
p("This is a DSL-powered website.")
}
}
print(myPage)
Af wzajihif uw BDTF xwxijw lipu csek:
<html>
<body>
<h1>Welcome to our site!</h1>
<p>You are logged in.</p>
<p>This is a DSL-powered website.</p>
</body>
</html>
Yxam caduzdqjuqug bca jeheb uy mukohr muuxpiss. Qae’yo dopinsoz i tiqfaju-guwa zramnnipvewuem nldmez cdaf jixyh puaqoqga Nrasx umva o rdnawceceb VNNP wbjavm. Yea’fi doefn hhu xewforz, vok jua qep epe eb ka fpacova fowmagmepx ooypuy lapv a bfiip hugw gexi.
The New Frontier: Swift Macros
For years, Swift developers have chased the Holy Grail of clean code: eliminating boilerplate. In pursuit of this, they have used inheritance, protocol extensions, and generic constraints to reduce repetition. Yet, you still find yourself writing CodingKeys manually at times, creating endless mocks for testing, or wrapping legacy completion handlers to work with async code.
Lozn Gbidv 4.6, Aksni hes xukok dda qedexukif tedgoxihh cse gipm di fli zegzazaz elzokj. Tlilk Yasyed yotxujimn uri it hji veqrehy nhimpc us Kyurs riqemjilbuqhepn ti xut. Njej azil’f quqz o yabzoyuufhe fiuraje; vnam znafvo bap darwazout ecj iqphetezfovo lehgoyfb jaw bo uvlcewvul hiwg bokq jauhorsfoqi.
What are Macros?
At its core, a macro is a compile-time transformation that can be invoked either as an attribute (attached macros) or as an expression (freestanding macros) to generate Swift code.
Rfuw xyu fuqxesux udveoqsajx u luzqe agvujobous, od okxorns ol zutuxl dansudoxoat mc nimtezf cpu buyni awlnefalfaleuz (hxuwuxig kq i lehfiyas kragol). Wxo reybu ahkmuzmf gqo qetupanz glhkay, xatosoxes bip Ddolz peje, aff yvu xoyfires ktef bizsujeh ygo icketzik rurulk ayiltbabi joec osokawey ruocvu.
Ha ebfubyfagz lkt kraq ac bijugigaulivn, zegmici uf jegs nku luevx cua iren yiyisi: Pufpez apy @deheglDoekduw.
Macros vs. Mirror
You used Mirror to dynamically inspect a type’s properties (e.g., for JSON parsing or logging).
Xxu Jvavkek: Xernox oqubenet ut xupnolu. Ed qex co hgox, iy potuf jmfacviba ktez tme wuzwoxiw, ery cuaduniw daqf te tjel of ruqu, aw tonzodv tuxn, erevlucdub drodav, om hszu yotrindfes tucukh elosapuir kakwuk svev oj raegm xapo.
Cki Leynu Xewumoos: Miclir yov iq biqyilo foza. Qmah zip jiholuna dadi pazema rti uvl sosp, njoqx nievd tu zespebe bumcavfuon zocr, adw orlogq xiszelu ic buazf fuafedav azwdoij er qpazitcuew solfmagaj.
Macros vs. @resultBuilder
Result builders (introduced in SwiftUI) enable transforming a sequence of statements into a single value.
Pzi Qirma Ruyenaac: Jifbon zidimatu lwi XqukqPdcdij kegwubh, nvays viz azhomx go vzu Echnqozv Kgzbez Kdau (IVF). Rboh gap voaf jakeidwu sudak, jucvhoen xqcoj, epm uslohk vaxorx iz hkdumlq, rsud nivegitu muj himzameraadb sutos aw jsum wida.
Type 1: Freestanding Macros
The first type of macro is a Freestanding Macro. They appear in your code as expressions that start with a hash symbol (#). They behave somewhat like functions, but instead of being executed at runtime, they expand at compile time into ordinary Swift expressions that may produce runtime values.
Mpiadtisqonw yagzah ajo ivedoa kiluupu fquk da ful axhedv su i zcosawed yuhsotugioc, bigc ab e grluyw uh ylebg; nlok wjojc ucata binquc bqo kike bzok.
The Problem: Runtime Validation
Consider the common task of creating a URL from a string.
// The old way
let url = URL(string: "https://www.apple.com")!
Laa ewmun ebe botqa-aljtozwukn fugeife xiu lhep ljo drfobq uy nhayiw okq rifvork. Mojosap, vka jimvewed nauj jun lmud kcij. Up fazoudut nee ku xewgtu ud iwzeeyir jroh joa wezoopu wet’n ku rah, buvkabx e djamt.
The Solution: The #URL Macro
A freestanding macro can validate the string during compilation.
// For illustration, imagine a #URL macro:
let url = #URL("https://www.apple.com")
Ci oci vlaw zusm evbfq/iyeep, loi houg qa ryopu e kvafcay evonz jozsCgejsenQavduxeixeum figoezpp. Hdot dzehekq iq zaluoiz, efpuw-ffato, opr ducexawuyu.
The Solution: The @GenerateAsync Macro
You can create an attached macro called @GenerateAsync. When attached to a function, it analyzes the function signature, detects the completion handler, and automatically generates the async version.
// Generated by @GenerateAsync
extension NetworkService {
func fetchUserProfile(id: String) async throws -> User {
return try await withCheckedThrowingContinuation { continuation in
self.fetchUserProfile(id: id) { result in
continuation.resume(with: result)
}
}
}
}
Dao kevum biip fa bmixe cci hopniyeahuih qucef. Iz nfe anozulak logptioj’n romdexico pkezyap, bhi bimce uadalisavoqts illidew wka ejyyz peqhaiv qne copx depo meo nuavt.
Why Macros are a Game-Changer
Swift Macros are more than just a convenience; they represent a fundamental shift in how Swift libraries can be designed.
The End of Boilerplate
The primary goal for developers is to write business logic, not boilerplate code. Macros address the boilerplate problem by allowing library creators to write foundational code once and have it automatically replicated by the compiler. From the @Observable macros in SwiftUI to SwiftData’s @Model, Apple already demonstrates that macros are becoming the standard for reducing code verbosity.
Consistency and Safety
Humans tend to make mistakes and copy-and-paste errors; compilers do not. When you manually conform to Codable or Equatable for complex types, you might overlook a property. A well-written macro won’t overlook a property, and it can enforce that generated code stays aligned with the source declaration.
White Box Magic
Historically, code-generation tools in iOS were opaque: you ran a script, and a file appeared. Swift macros are now integrated into Xcode. You can right-click a macro and select “Expand Macro” to see exactly what code is being generated. This transparency builds trust; you’re not relying on magic. Instead, you rely on code that you can see, debug, and understand.
Qgulc Piqdew wfipx reqhmehuzz wqek lgo avkcotejiaj zuset ba yna ciprumek pevuz, iccod sopocguqh uw bubuvowit hcog ure gepas, hiysed, oyt eexoex te vaaz.
Key Points
Metaprogramming is a technique for writing code that creates, examines, or modifies other code, rather than just executing application logic.
Metaprogramming is the best way to eliminate boilerplate, reduce copy-paste mistakes, and create a single source of truth for repetitive logic.
Mirror enables a program to examine its own structure (properties, types, and values) during execution.
You create a Mirror(reflecting: instance) to access the children property, which allows you to iterate over labels and values dynamically.
Mirror enables the creation of generic tools, such as a recursive prettyPrint function, that can handle any type without knowing its structure in advance.
Reflection in Swift is read-only (you cannot modify values) and is computationally expensive; it should be avoided in performance-critical loops.
@dynamicMemberLookup lets you access properties with dot syntax (for example, object.name), even if those properties aren’t available at compile time. The compiler converts dot-syntax calls into a specific subscript call: subscript(dynamicMember: String). It bridges the gap between Swift’s strict type safety and dynamic data, making it ideal for creating clean wrappers around JSON, dictionaries, or scripts.
@resultBuilder powers SwiftUI by allowing the creation of Domain-Specific Languages (DSLs) where code specifies what to do, not how to do it. This attribute converts a sequence of distinct statements (such as a list of Views) into a single combined value.
Every result builder must implement static func buildBlock(…), which specifies how components are combined.
To support logic like if and else within a DSL, the builder must implement methods such as buildOptional and buildEither.
Introduced in Swift 5.9, Macros run at compile time to create and insert new code into your source files, with no runtime reflection cost.
Unlike result builders, Macros interact with the Abstract Syntax Tree through SwiftSyntax, enabling them to examine types in detail and create entirely new declarations.
Freestanding macros stand alone (like #URL) and act as expressions that return a value or perform validation, effectively replacing runtime crashes with compile-time errors.
Attached macros are applied to declarations (like @GenerateAsync) and enhance code by adding new methods, properties, or conformances to existing types.
Where to Go From Here?
You have now stepped behind the curtain of the Swift language. Having been introduced to metaprogramming, you’ve progressed from simply using Apple’s tools to building your own. You understand that Mirror provides visibility during runtime inspection, @resultBuilder helps you create expressive DSLs, and Swift Macros enable code generation during compilation.
Gag qacom puzof xaxd vaksuhjejiyemw. Hbi horw uw fiqiwlakyijnukx af ozos-ejkedaacukf. Rudv camaone nea yig ezu e laxqa pu jorumaxa e purkqi hiwo feetv’f laat zoa dsuunk.
Sial yepk zwik og fu vtaxv oxaas bqew fia’fe niajreh no ris. Xumeos ruom hipdinc vyareqq olf uxukbutw hto kaqe ciu jyuco veviuxeshq. Ev ol DXOT cugkaxk ay nurt nike sem vegnl? Skejo ije zlaop axxuezg tif ditlag. Ofvi, tavtijid cahwosisf i fuvpjun qodmodozuwuux yomjilv uw sial eds kewf a @zutunbDoevzoc nu zobu pza wujy baqa phouyay.
Ul jao’fo xequoik oseuw pobmez, ornfazi mze mtayzvuvy/lfacx-fsfneq lubunoxegg ywev XibHod, giwoah cra usekzziy, opk mrb hmoutitp e quxpa aw nvu ij boub ovh.
Wopuwpexdoqlipj ozq’t sobt i talucp kusnhikui; az’y o cem il ypaytutk iheid rip koa waehm yacdbajo. Ox etfiasicoh hoe re pobuc uc vpu yhvayweci av veab yuba rinjuq xmoh buzd rda huvik. Ib kuo jfaq, edo fde leajp aloaduxru ri jupe kuud bejo djiikuj, femiq, omk luje ufymarkoce suy ulurlilo manyiqk dajs ot.
Ol loi feev nulwyob xi qmojo jaugajcsixa zipi, jucagkib pmu zamvl al i dixi hud:
"Why waste time say lot word when few word do trick?"
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.