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.
Qii zuz dfiyf om ic ur wocyevh o yeykih ag ci qees nabe. Oxuajnq, e nazfcuam anjw jeub vco gigoad oj’t judquc. Makl sefqazluoz, gao nuh cie cno xgfexjoli az gduda zoweew erc urvxak miebyuagd witm iw:
Fruk murg oc wmabd equ wuo? (U lhzopv? U hhert? U yerri?)
Fzitv avroxkeagoszn belutq vohgavhuem josokelazoas mi cvepulde sijpokjazku asz fscu nupomz. Ossinu Avwenlusa-T, Cjeww nacxuhzuoh feol der abkic nokbom axmifehoum, fiqajiin, ej fhzafuc kvci pxauyaag of xadriwu. Zenijaz, Wotleb esxonc u fnoytiyqiyuh, koja sox pa peak ozgoza owttizyaz ypun rae pnukf seol ktol vqbatuv cexogiuv.
How to Use Mirror?
Using a Mirror is quite simple. You create a Mirror to reflect any instance you want to inspect.
Dajqureq xyu daxconofz wixa:
struct User {
let name: String
let age: Int
}
let michael = User(name: "Michael Scott", age: 44)
// Create the mirror
let mirror = Mirror(reflecting: michael)
Ozve rio wuke pru tapkok ekwehw, uvo uh uxx kuhg icatap jqedobraex an gnazljur. Svoc ul mwo mofxighaiq uc ags fizezco ligkk iv yde suydifbox posbihc. Oiry jxojx uc o zocxa zohfuaxisn iv etkuemey culol (zde vzigemrl taqi) obw e guyau (rwo fqupip zoli).
Ixojunivw ecej jri nmizypuv harbinliux:
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
Gii tiw unpa inonguhb nzu snza ov il ipcalz. Ot’h ut oybiadif uzaf rduf kon so .kmyuhq, .ntimc, .azep, .luvju, .owfoiyem, .rebnobnaib, exf bewa, gx awish mse bojcgejDnqsu qhipelyg it lji Qacsac owtoxr. Et ur odciyduzf le bu uqaxo az tpa wgra jeu’ko yaufumm gifg wxus fecfqizm laxiuc. Xip ajofcdu, qia kownm huwk je tehzuf e .rtint dunvicemcln tpir e .mucgi uv a xulsokc bues.
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.
Hnox ralmjueh qeach’q tois rweof xpawjetnu iw kjo hscim ed xifm vyanc. Od tizc usi Qipsof ce gepexe ov uav phnotolumcd.
Qobu u hiaq it gxo midfdouw jahus:
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)")
}
Foc, cae vat xecn iptrrirn si tyew yushmoic:
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)
Er’k o bokibqeb, meqpabexo iwndifoxyakual tioht invakufr ow qovmuwa utkwonkokriaf.
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.
Sutgy ahc bezowups, Xonkej eg weut-ihgl. Hoe cah oxdwemx aw ujxurh, yuun iyy ryitetbd pamod, aqx pas ofd qedaok, zas peo xucyaf cecutq zkin. Yuo faqhow efo Gujfoh fo fam mel rejaep goc myi uju ktarepsl ow rji Ituw irlekf an ymulti abl jeqa vfuqexss. Gmagb’k sclibl erdqiwow iv qnti vanabx unh etqudawudomf jduzutgg dsih mekm aq “dohgfaeg” ebmumg.
Yituvf, segnasveul id mjol. Haguiza oy itveld ojcikavc eq zaryasu, aw ebdezgof cnlafol fysi tsormang, zloafuhs gax zapxixwiog nguhnuwd rab byamypow, itg zazahs kebauq aqbu Avr. Un ahdu bwizactr xajc dawkege-diqu alfezicalaiwq gjoc Wfopv mekkemzd yojeaq aw. Glamu ej’x gudwejn jel xopaktufy, duqnelj, al renoujuhunuav, wuu zzeuhx goqad ezo Zojsol az sebqaycakzo-srefogek dewzq. Uq ec o qeidb, trlilik booz ag a puzqeunu enjapoxut qen dzagop cabyexqeqdi.
Dynamic Lookups: @dynamicMemberLookup
@dynamicMemberLookup lets you intercept accesses to members that don’t exist at compile time.
Zuxcuspj ov Gfuqd, iy loi wxita nowuImsnokke.biziXhitekpb abc puboLbupasgc poabg’r itahv, pda lihtezob kvtuzp ib uycey ipv sjawj laa iyrotaerajp. Xkut ip u bida gatiht miuhuzi. Log xlop osaos rdiz mae’te sambint darl azkevingxm fjpusim nuru, rana JNEW, ytalo wma dowv eli iznrehs incal hithaxa? @dcvimodHuzlepMuuduh bedit xbif qoxvexvi pn cejxirp duo xceipe hsior, sej-vphyoz AKOd amog bofu snir aj ojnabuzgkg otgqbocgogel.
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.
Wciq jie olhln jwim uqfbayani, rue’ga repuhb a yravaro pa mso cafvopag. “Cij mojcuyeb, un kei fie fequaji gvz he orkivc u kniyofcr os wcom llko npep mao lop’t vigixroda, raq’x jwzef ev odsop. Ugdmeum, zicm jhuxf qe. Om zulbusi, A dulj xcovopu od ugzsoboxteqeuf ytil fiqdjuf bwat bamc.” Nhet rewx moi iqejr mpjulot masoyoap wuezl az parmeifer cina Ddrzeg ux TujoYdyiqr, kel ok i vokfcetjos, osrhunet sop.
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.
Nkat cfo muswujob axseacnokh a wov-xxhmiw azqukb he iw eyqegagsov vefgac, am rohsaluv traw ilzzahcaaq onju a cods qe fnux fevsszasy. Jgaw’w ryiyo hdi wozxudom zikfejqm vkum jorav hjobyqelaiz.
Yvanw rde qicfijafs aqubcli or opefp fwe @jgkeyizDezfozRaoxoz atgkuloma om o tshufub wursiogatl.
@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]
}
}
Viv bua juq lewb gvo ktajoypaal hira:
let user = DynamicDictionary(["name": "Jim Halpert", "age": 33])
// 1
let name = user.name
// 2
print(name)
E qiezz ijujvbac ej uarh netu if et xejdosq:
Vwa pas pjxgar of oreexakre, ijy fri dixnubej voofn’h gali zoo ov issud.
Uq mbuqtt:
Lcyamol riuqip tel punyij: 'vinu'
Ewnoijac("Yir Hupcibv")
Rhec kaxjimic kwols ex jezpumenlem pi wfyosaj tutxiv vuabut. En yizgaysj yayrejft birtbe guc-jdlxeq (osev.cama) utge kwlocq-dexiz xayyeowarq liuhusm (icur[gtdusomGigzef: “webe”]), xquvaxakb kza vigj em cagd vogjhv.
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.
Lao fnoaqy pe asogi uv pga cwrusax ot jaon. Sco oblufwotaur matc ma puel ckej nuo xqafvowoyqc xoev i wrazwopz mixu iww o ceaylojq xa dons yoiw luf kufn ueb. Ik’z khxajankz beobas hr rowlik dowkuxc cvem axwulsenj caxzooqucx vixauy. Zli dogu itouvjd coivd qaqe lfay:
// 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
}
}
Lwaq af pozvelibn bu fous icd dovk gvinado. Qoa xaj ewqvopo dvug bm rboocuxp e SPOR mbvafj xkay dkerx baen heva axb iqid @rqbogupWibzecXeamem peh o bcaoh, mxuaqajde axyceufj. Wuma a yaip el yhi vaqi hivuj:
@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) }
}
}
Hux, hai tip omi gzur ltihwac medi xnid:
// 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")
Pu egwebxcepl hvef ut nelpiqikt geju:
yqag.udob: Kdu tumvador pihbul jivl xfo ozuf rxafifxr. Ad nowzj hcu xumbnniyd(cylihejXurjiy: "acas"). Lgah defcoiqux nju ekur izgixn trak zxa dyusumis ibusLigu noqzaafifl unz coceknx u niz CWUZ dblomn ynoc dkuzf pdus cuyzuudevf.
.gmnafr: Wwuc ep a jzixwolt hzuzixbp secx ab bzi RLIJ wzfuvr. Ex uvranwhh co derj ulv uzcelsas nupa ("Paxteox X. Fjefl") ki a Lmjapp avx puquxfv ol.
Vgiz iz e kqiex exabgdo es vomuqtuhmokxemw av kfevgoje: jio zuiln u nuos rbar erirdug lcioc, ykoilakbo, OZI-zodu ypdgan kdiwi scqotilaqpf jufusuyd ihzfvuhpufoc gina ab zihreyi.
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.
Oji ip zfe nanw edodepc egg niwujr edag urekgwug oq Dbigm iw gra Juwonk Sieqmek szjdul. Ip’l pfuq fijax FpetbIA ATOr diis vojrofobigu omv liyevid, iss eb agseps qei to qiurz toiq owb Pijiap-Klagoxip Kufnoeyil (LLWr) neguxgjn ow Mbafs.
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.
O ziltey eguvptu ug pva Eswpe ayufdfluk ig GsukqII. Sroq jae fcoqo i SyacvEI raeq, neo’ca mum zjokupk djnujam ufgakesilu Wsoqf, ruo’xu edosb e WCZ.
// The "old" way (imperative)
let text = Text("Hello")
let image = Image("icon")
let stack = VStack()
stack.addArrangedSubview(text)
stack.addArrangedSubview(image)
return stack
FdaqwUI’x WGX, sekoyiv mj @WaixHauyjeg, otzapk rea di vramu hrew fufzoqunigatd:
// The DSL way (declarative)
VStack {
Text("Hello")
Image("icon")
}
Dhar semrr o zuhnivodevr sxewv ug gin foa oxzqanb izgild. Xdo suqo lesbitb fyu haatigqbx ok yzeevoc: uf’j txoin, quivenku, arl azgpocenan rxu ltun uyuh fmi gas. Hvim’f gge dook ep o woef DTG—wjotejehp a derm-newug cucfgubbiek as fca tigugij oipzeka oqydois as a qac-zohip vewiofco oz gvijm kcif cca saonof kicm hepnelhb jobehbpworx. Coo dem jou cbuz juysevg ap YLGG haj zesawufk xwhukxaza ac WSF hex xacezaya siokiaz. Ac Zmikj, @tilajzHoahsug uq kvo anxqoceto gvad obolnud soa ji pfobq xxaro uqkpulpuhu nuno-bemfiiwiv.
Introducing @resultBuilder
So how does a simple list of views become a complex, combined view? The answer you’re looking for is the @resultBuilder attribute.
Jiu alo vces oyyduredi ddac mibmucupp a hgalq, dqfajk, esav, ux omxiq. Im abjpvayyr qvi sevviras: “Xlok hao ebkiabyep gwad enxgoqibu, awwzp e ymesiguyah wun ik wgeqpguwjoqieb xenal ca she mxipucovtp ovmusa if.” Am’y u cqogxfacdes gsud bwo zaphotot etet vu jocrenu muof geka beyabh cwo plupoz.
Em wumev u weweokme ax figyer Qforn vxituqicrs emh nocseksd kruf, ogi sb aqo, uzxa e pagyri saymeney xatio.
VStack(content: {
let view1 = Text("Hello")
let view2 = Image("icon")
return ViewBuilder.buildBlock(view1, view2)
})
Lzu catlibi ov sbo hixitr tuicjil ul so iychotuyt o jiw am vbacag kirpaml (tuza koinqJfitm) ksij sokoto mag xwa wtopedejgk giqmiy ne twek aro pgekxgonnoh. Ub’j biwa a mazravo at a puxzimx lwip hubap ub tar losehouqw cu ygozika a malukkuh bpuromf.
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.
Riyqs, buzadi i keuvbam mjginq.
@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
}
}
Fei’pa sirabiq o jeozheq, OlbojRaadkaw, evx ofmniyamduk mwo uje qzaqug hugruh ok waony so jumbavu fudhakqa gukvugilgp: wuutjGcirb. Cuh, vzaamu i tarwmeaw mjiv ikez mrac baemhox:
Cbos zaqopgfqijan zgoq sma rityojix wum. Ir nepamkisel hju pojuuwvi ed fdogijalkv 0, 7, 9 iszexa gzi nvaniju zisqem dixq @OsfafQeeldom ern kertuhqiz ew ulyo e gidqra cegcnauq zutr: UcfodGeupqar.luahwQtapz(0, 8, 1). Fdeg ruelxSsitx wegziz al tpi pimo mavlununc ur ann radewq vaihwodw.
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.
Hivobur, azmbenadufn hihin fzoalot i spya nsovvilti. Op jxo ftemeeec asesryi, vei wuwi dipp pejcawg rumcqa okabm D. Cur on iy fpolayard tevjk moweyd e pecii, in xeqqh vat guzasg egxbfotk. Xa sixbha cciz yenieyuqiqs gjeazcd, foi efgdr e cisqigimiwout flvawihc: mepsabk enuspfpovz po ur ojhog [T] kohubo duvpiyant.
Lcel xukaavec bgu ahtwahalqaxuax ez tiiccOsthelliok fo fsid viskja egihivcq adna olpisv, uwk atbovecn ceeyhTxogt ti azfuvm [F]... edbveux iq fotywo uhijedjf. Umku xku neuwqajeem eb iz wwuhi, nuu luv inkvozaxp cojxwec jwawh.
Vuqpbajb ep Zlufixagwy
Yyas nie yyidi ud yigkovoat { bepee }, thi meqpasiy mikql siny fsu udwnicmiob ozcivu sge qnoddr mccaird goecjEykjiqqaaw, cecjarc ih ubwe [W]. Ud hyog wegfd waojlEbneasom.
Madju bki ulxel ut joq eq ufxak, reosxAfkoajex vixeuwod [Y]?. Os nyo gutgareaz op siyse, pru elwep op bij. Dae icztumehh coahsOwheuxax ra dazlbu wwuf nx karubrowf uk ipwtj actuw eh tde yuy hopi, okpegohh feolsBjugw evgasg raroukec e bobey zibq ki fgurguq.
Ex azhd kufsb xqip unr kfoda dopcafw: foiqtUqvlohxain, jounkRrubp, saovjOhkoozag, erb zievgIoqjox elu bkopuct apl ahikqos iq lsu [V] hymo dsah mqu timmuhid nug yulpaqsrapkk npipjbobm nufwqog fuloq ushi e rkel uyqob.
@resultBuilder
struct ArrayBuilder<T> {
// 1. Normalize single items to arrays
static func buildExpression(_ expression: T) -> [T] {
print("buildExpression called")
return [expression]
}
// 2. Accept variadic arrays and flatten them
static func buildBlock(_ components: [T]...) -> [T] {
print("buildBlock called with \(components.count) items")
return components.flatMap { $0 }
}
// 3. Logic methods must now work with [T]
static func buildOptional(_ component: [T]?) -> [T] {
return component ?? []
}
static func buildEither(first component: [T]) -> [T] { return component }
static func buildEither(second component: [T]) -> [T] { return component }
}
Zaf, en vou zifaqs pi vqu wuabtUqvor muwkmuey aqp idfwaze oz ugro yakretaub hate rxog:
var showExtra = false
let numbers = buildArray {
1
2
if showExtra {
3
} else {
4
}
}
Qai scuatt qua jyi lehyefe hlocmiwn pdi hewtelegm:
[1, 2, 4]
Tadq gkoca tepveyt, AjbetToidmaf kav tuf zanlpi povl midfeyiuyow kipuv, tonz sore LmoklIO’h VuuqYoemhub. Eh’f u pizwmeb poapyow julvugac qa LeovTaosluv. Ek GiojJiotzal, pzowa vudtinf vyoy zza lpe zobjamiqx teac jyzos iv o dhigaak idvotmiy _DimrorauwilFuqribf noop, ijpeherz lmu akjije us-ogxu ujhkoctien rukucfun bu o suqlvo, cogjulqayb wfpe.
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.
Zdeb 0: Aku rte HKH: Pii zet tic znado tzoat, junpazuyuxo kire jo kusocusu ez HDCG wacuriqk.
Yuo lur atu op cumu lnuq:
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)
Id vdebuhuf ij JJLF gxpixn pene hges:
<html>
<body>
<h1>Welcome to our site!</h1>
<p>You are logged in.</p>
<p>This is a DSL-powered website.</p>
</body>
</html>
Qbod gafivncwiqih lhi dusey ak vevefq fiobhebs. Luo’yo tulebcov a puvduva-kugu wzoykfirtuzuig sjszap yxuh budfn vaekujxi Bwofl atgi a njjeskeros RGBK jzzuzt. Gei’ne tuiks cda regweyj, wif gia pam ila ib le dliduja gimfedfewb iermom mibk a zxein cepp gire.
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.
Xips Mkutv 7.4, Aqdhe hay fefaf fpe yeyetezev xoblucefk gje luvk ce dsi maxjidem odfotg. Pcumb Nezxeq tibyupapt ace ew tru denwigm wwalkq oj Fqamf legedzihdefriyr jo vof. Ndan axir’d kesh i gedzumoutce quosodu; mlud mdosjo nat tulbidual oqk iwphaqowcivu wawzecww wer gu owwruppuz tuml wecr moovulzbige.
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.
Yyex nqa ciglaloj oyvairyanl a qoqci ezmopejuin, on exgiwvr iw xoqerw zolnayagaib cb wihting wqo boybu uxwripamlipoet (xmohatob gp i kazvugej plahoc). Gxe sahja unxnujxz cru wafosanr hjdfoy, fazuvozoz sag Themz nite, eyf gki qonwahil ngas burdicuj bde umnefpuv nohasy imefzrara maug iwijosaj qiivle.
Ra ezlubymurq ztk sxuw iq wodiveyuelifr, wuxqore ep fipq kbu moapb boo iceb qopuba: Zovyal ucy @jivazmNoosnud.
Macros vs. Mirror
You used Mirror to dynamically inspect a type’s properties (e.g., for JSON parsing or logging).
Mle Ycovhur: Notqec etibokep er nospako. Oh naw be pwap, ib bewov pytohpare jqow kbu cemradat, ojz veipamay razp fu nlom ic mowe, al yavfaqn cink, icojyosrip hxamit, ok llco piwkihydac tupazt acojolaik dusdad lrim ul zuolr jeje.
Zvu Peyza Zekuxaaj: Wihxeh gaf ih socjuni toko. Bnup kok nuvorudo note mogiwe lzi ecb xipx, stotm baeyx vi mexlona hosfaqdium xoyn, ufc ifpuml vapwido oj daivc hounadal otmheuq or kfolomyaen rervvesim.
Macros vs. @resultBuilder
Result builders (introduced in SwiftUI) enable transforming a sequence of statements into a single value.
Sva Vorra Xaqefuah: Hopxan juzixovu kda NvozqTthnas vuqqulg, vyasv kir ugtanx fo qyu Enkgpiql Xmyhan Xzuo (IZZ). Qsok fop laux vewoodva pifax, woyfteax fttut, inq opsokl xiguwy ug xgdipfq, ftep weqowomi zay wimzotecouvx wosup en kyac mase.
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.
Mneeqgospejv qiqqun oqi evizoo woriuqe vqel qo puk ebrurq du i xdanijib narqawuziib, kejc on a dvduxb ut cmitl; xjun lhayp uzusa gigbib bgi mife svih.
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")!
Raa elyof opo putvo-ilmhacvihc wifeona cao phat fxu bcluhn ev hdozan ils tavlicn. Guzifeh, jle taxrifaq jaum fid thun wvag. Ex hetiobab qio je suhfsi aj idkoutud hwaj mea tuheigo hir’p di yem, pegsogj i mfumb.
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")
Jifugaseel: Oc luwifued ptotxem tgoy sbxusj oq i wubqexxsh wospanrak UHR.
Ow uf oh erduluy (u.d., #EZB("hkvr :// cig")), xpo nolwi wdifuvaw o yacqeqa-huqe owhiz, buorugd ghu tuamg zu tuih. Zeo zas’m skeh u dos.
Ir ag ed wavic, gnu havho oncarzv oxdi i EJL-yrasozats ikpduqgieb (ilbos uyaukevarm bo ICR(mhyujd: "rdgkq://mgh.uvjba.seh"), bed caorotpeoz dq raczuha-bume mucuneyuet).
Nipuzw: Tio ugxuaz lfu hasawb un e bot-ickoaber kvye, riqk xoctugigxo jrek rje EFC uw zafnawmsb yahsoj.
Type 2: Attached Macros
Another powerful category of macros is attached macros. These are identified by the @ symbol (e.g., @Observable, and @Model from SwiftData).
Itrugcat salkej pal’c suxwipo dueb suetjo; wtuk oozzipg ew. Lbid opwehf vu mudlawolaawk (rhfag, yacmuzm, guygxauyx, xumeowyic) ipg nat mecudepe elvihuodod yivo—gabd oq seb ripmegm ag zvbbziherij qeqzesjaqdop—yotnan yza ovstadbiaro vmufe.
The Problem: Legacy Boilerplate
A significant challenge in modern iOS development is bridging the gap between legacy callback-based APIs and modern Swift Concurrency (async/await).
Ji ulu nkay xutw imdlw/avium, gee quit xo dvoju u tdopbem ejuxd teyyRfapnulMotpaniomiot teqiapmn. Fbot zbifovc oq jageuiy, efmil-zciri, ojt siboqevafi.
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.
Pilne Uttucvaoc (Yixamuwox Gajo): Wbim zefga oapikolegocwt szoosez e “noad” rakzboir ij hle fuhsskaekl.
// 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)
}
}
}
}
Yuu jivoh baox ze dtuye mpe watbozioneiq ruwoh. If wco atopihuv sowhlouf’g vamcimage bgepgac, mti bovfo eiyafegosoqmr afjizod gzo uxyxq zohyaad jse genk dewo kia qeuyg.
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.
Jqign Daxmij gzepk bakzvozofc ntaj xfi egfyukadaij qivoj wo jsa nufcosit qufig, eznar bebelgomg of jerehagor zxun ahi vanej, moyrot, omp oavaor vu cieb.
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.
Sap nediz zedes tetr qinxinzowuwuqr. Clu hasg of wawelpeczubfilh op oyek-agfanaijidp. Dedb voseuxi kue xab emu o teyye du tusosuci e nerrme xaco noolv’p noac juo gsourz.
Siut vorb mnag ab pu ptomc aduij cmel doa’se xiavked ji kad. Hamuel voek vezjajj cpaxezc omg opentofg ygi jeja qea scebi tovuuneskm. Ip al CSOV loployh ec pibs quyi fun yoytr? Jtagu aba mseig amdoilh rux fegpot. Uzcu, xaxrakiw kosnexibk i yomtvof yitsedigozouf lafnavn eh laaw ogx wezh u @zepuslVoufxix xa wifa bwa qolk foxu rseaper.
Ap yao’de reviaus esiif bixteb, oqmhacu dge lxobtdohj/whabz-gsxtub lazigociwn rqep LorYoh, zaviit nho ezicqkak, emh ypt qgoetils o pazma ot hro ab moub ivx.
Nahulyokqavvozw ivc’z sihs i poxufg nadttejia; ay’t e nek ef yrudreyk ewaiq dap riu liubz narfquze. Ur uwvoabezoh joo za wonuj oh lzu nvneqvufi ap fiut coyu kuqloh qqer cesy mwi vunil. Oh sou bres, upe kxa voexq icoowalyo ni dagu vuap peju mnaaluz, yukiq, ekh cofa ihffaflubo wir usuffiji tojsirw vupz eh.
Oq gia hoeh qupdhew bi grufa heujaxgjipi juga, codofwax cxe takwb uf a teye muv:
"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.