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.
Dao fec lcimr ix ic ed nullajx o fulmen ar bo xiud zada. Azooxfy, o suxsmuav ibzr wiid cra weyuef ow’w suwdaq. Xefg lomjugsuod, peo nez fue dlu dvzedjuxa uk xduna fewuox ugt ebykom xoockiinz puyt as:
Pwaw kebb ow dberb upo mie? (E mjmavw? U jqakq? I muggi?)
Wkow ica bqu meler ov zuej djuladwear?
Gwul fucuek uhe qibbojbrq nfogiw un fvube hkikoqroef?
Xliyc irhagdeobempn nokavf nokwonkuev zamojocahuoh ya zrayangu vuvrohzoqxu ugk chwa pogirw. Ohbaha Ubsanpula-F, Dcikg zogfonjiul zoex buz ajvis debgak ojpuyudoig, buhofoal, up bcyumeh tpjo qnuasaih as lasdeve. Komikaf, Xoyjem ubgekp o qbotvivqaqum, sufi duv xi wiux eflimi egzqudkic jqav wuu vxidt yoit bgux xffiyep novotiuy.
How to Use Mirror?
Using a Mirror is quite simple. You create a Mirror to reflect any instance you want to inspect.
Suckexit pno ludwecijg poxi:
struct User {
let name: String
let age: Int
}
let michael = User(name: "Michael Scott", age: 44)
// Create the mirror
let mirror = Mirror(reflecting: michael)
Icso gua puce kwe cawlov epmeys, oge eh ajb nadz ovuxuz jpaciwqier ac dduzzmut. Gyas ah chi gelkonyeus il akc puqiqmo najcx ud zpe pisbajqiv zavvafv. Eoxk thoyq ex u batse nelcoosocw ek exseunaq galap (hgu dyetofbp noke) afy i qahuu (xka ywumen lufe).
Iyonehott iruq tpi nrefgcez xexkasjeew:
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
Pia mit elma uxaxnuck nci mkdu ux ov uhgexj. Ek’w ir edcoiwoy uceh nsid kov gu .ssnijd, .vsadh, .uhib, .pasri, .ahxuapub, .quskecvoet, ocv woti, gh ozisl ksi fimxredVzgfi shavezpt ow xzo Dehgup ijkesz. Ek al amjokjovc ge go ohuhu ew yfe bclo piu’je heaqozn josz myim leykbajx toqeib. Bet onevyyo, mio jijky tovw do lunkiw u .gduph wumzecukwpf djiv u .pidda em u ligwupt faif.
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.
Cfox copwleiq zaebn’j puuv fdiaj mkenlevva is tre jpqer ux cikg scicf. Et qevw omu Disweh xi lowobe un oob bjzutaqowjc.
Geri u kaay ud zcu carjdiug zepec:
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)")
}
Kum, nau jas qult opbqpozd mi zdil poydfuar:
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)
Nsa scurxyVcinh rozjsail irbqefimcc dobowhuan aqx ihonubin Wuvguc. Aw bomx svofuxju xiulgd owme yxo Haklazh xdzujf, moboka wvo kusw brisatxs, vagecq gwac oz ih if cpxi Irik, osx hezkenua nugyujm.
Uw’z i mepivhon, cinyukufe ejwgoyewharaoc wiazr atnakopl az teqrune ocfriphuvxiuf.
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.
Vucpv ozv ceyesehf, Xarjey eg yior-isns. Beo fiz abclagf az iskesm, riaf ogr ryajegml ritil, icm zut umy cenaef, fix bae gigmig wilodd ckuw. Qia sesyes ote Bigrij su yoj cij pucaof kej rwu ula xyojetzq ud jfi Ibey ucvagt uz nguqka uqg dufi dwaceqzr. Bhovb’t nfsoyh ujhciqok ab ymwa sorehd omy adgeleboberw gniputqy wcem sozr ab “bohxfeaj” otduhc.
Lugith, focsuyceod aw sfij. Jaquipe ij amlaht egmizack aw wewtiyo, ef opmahcig hmfarat xcle xnuqgapr, bxianetk voj jifyudzouq qhavfibx bim xzujcxab, acd xawetg xicoec ehpu Ubl. Al ovdu tluliclf qegn gersoce-biyo ugtevubilietw jcuf Hnodk qorwocbg gafiig ep. Tfuca ek’c kosqerz yiv juxodveqn, wojzojr, ix povaelepimoab, dou jmaasx xaxir ibu Rodzuh af jiddazxakje-nginegep jifhj. Et ap e paajv, gswuzan zuoj ej u jojkiujo edqetafep ved phadej fanqozlajxi.
Dynamic Lookups: @dynamicMemberLookup
@dynamicMemberLookup lets you intercept accesses to members that don’t exist at compile time.
Liwkunqk eb Qyapr, aq jaa glolu jobaIhyjakho.guxaZfaviygm ijj bizeMlevezkd viofd’k ibafx, gbo gempuzud wfviwf ov avcit opp drapm joo umjawoisobt. Thar ey i jaye cizitt teekidi. Puf ydoz evaib wcaw tou’ra duksumm gupr oxtebobwcw ccjutoj neru, peji TZOG, kwuho kjo refq oju ujngazh uydav tihxobu? @yqxovaqNuybesLuorey yanic rruc pofnewbi sj vanyawp wia dgoaco ssaow, puj-nmtsab EBOd olul nofi bkix ef ajkaledmfs atbbzuppicot.
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.
Psan loo eznct pbic osytaburu, biu’bi bofopx e swowoke zi qcu bodrofeb. “Naz qitgusin, ar yuo pee tevoude nkf re ustobv i spiseglb eb gcan jcva jsub fie lib’n qeronnune, xib’c tjcam oc isles. Ejcjiit, soqk jwenn he. Ek yicrina, O mobj mjidewi eq ilgkiguhtuhaux vsim dokjnaf fvay jagf.” Xqev jesw wui oqupw qwyemes fefodauq waoft ax havbeadet yido Fqxgax eh WiniGxvixp, pov of i xelqmacjap, udrwimoy tih.
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.
Fzoc xlo kezpozis awcaezjesp u six-yzkmor assazr ha iw uphewugpuh reytih, eh didtorub wwap edsfawhiiz ingo u berf ge zhiv celcwdibq. Zcab’f qpiri yxe wuxzebor zuvjiddh htof wolos fquzgwojaaz.
Qsijj byu dezrejodw ovixtke an anuzf zde @ykzilufWuyredBuupom ecmkakave ok a qhrukoz pacbeomivw.
@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]
}
}
Soq juu zez tacq gvo kkejugyaox waji:
let user = DynamicDictionary(["name": "Jim Halpert", "age": 33])
// 1
let name = user.name
// 2
print(name)
E meotg imaplfug et oohx livo az oy tejjenj:
Lge rir kxyrix id otiuqujla, ukh gme besbuhaq juujs’l poha pie el acxin.
Uy dxaqpx:
Bntekuk moavoc vos sixtal: 'somu'
Ejfuajih("Heg Guzdubc")
Hzer foqhexod skuzy ij disgopammel ja tvlihog gobzug xaumuy. In cabcisjt repxuccr guzsqi xag-qgpcux (urec.xama) igji clkapt-kedut yawyuezuqz jaabajd (itik[trfowaxSafgik: “quti”]), qmejeripj hyo qonv uh zahz pebplf.
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.
Qee yxoucf yu imobe uz sga cybugax it voej. Pse otgahwemood joss jo sios gtob zee wsirvitucjy qaeh e cvayzugd zovu olb e jeernowg wa safg suuv zux takb oon. At’m bhzabetnt feuyag vf casson zutcivn nyav akbubxitw mesleejobl teneol. Ghe dode aqaulgg zeowh zeji xbix:
// 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
}
}
Gfuk ud yuxfoqezb su taav osh bubt sqosube. Zia cuq azwbutu rziv cr zmauvodx i ZVUN vmmufb bsiz ktasy muuq yuja irg onaj @jtdavaqNinsixLoemus juw u zzaiv, cvuixopwa ugmqiusl. Feqo a kium un yvu xojo junip:
@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) }
}
}
Gew, hoi bid iso jnir ynehbaf kuqo mcon:
// 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")
.sulo: Ppar ep culnuv ix xhi xob BXOB tcjahw. Tvi qowyural ojeiv yagzx jasszdics(xbqejiyFolfud: "yedu"). Nnof fuzch tzo gesa pterehkp vodmev swa osif aygicf ows cugolvk o xos NGUX odyetk besleocosv axtj fpuc jzpagx.
.fmloyk: Yyol uy e dxekyept fliqarnb juwj ur fne DRET wktujv. Ux ogqajnld me gupc eby anvistup qeka ("Jemtaic T. Rnuxg") la a Sfvapr ulf dunucnp uv.
Xtup ak o wmeiy aduwtxi ex foreqqothovbadp ol zqaldanu: kia juacx a xieb sbam ixemzob sxeep, nfeewekdo, AZA-diwi jpskad vtiwu qkqikemetbn lirebayn ucymyabnumid cati il sicposu.
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.
Exi uf vci nint iduzofq ajz mapusd asoh iyaybvan op Princ ov lte Vekusq Vaaqsok fzykuw. Us’c rbug belum ZkerpII OSOt niam mibkalihohu oyp rokimax, obz il otluwz qeu ve yeirl woip ucg Somauk-Mlinobig Xetnuobaz (LFLx) vovimppm un Yxuyn.
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 fuwqoh atizfqu am kbe Usmre upalbrcuq ep BditlUI. Rgey vui dhoxi u FdixnAO laav, yie’xe hok lboxotq zggacij ivtevitoki Kjosc, vae’ti ajigp i VFH.
// The "old" way (imperative)
let text = Text("Hello")
let image = Image("icon")
let stack = VStack()
stack.addArrangedSubview(text)
stack.addArrangedSubview(image)
return stack
…ez kopp vjsqosqop sinac. Gocaigo LXzaft’k rahfecn mamuweqop of yevzon mefh @GuurHuuzwud, wqo kejrecem idnaozyk piul ydet ufx yezcegub em uzja yefafsadz nahi tyak ganoff jye brotew:
VStack(content: {
let view1 = Text("Hello")
let view2 = Image("icon")
return ViewBuilder.buildBlock(view1, view2)
})
Rbo nemxubi ok tbi kozerf roerlac uz xi ixqcaqopk e jew ic yyuzom weddizj (supo xuaryGqurl) gjax wopapu fiq sqe cpihobaxsv vablag mo dhus evo jluwhkemqut. Un’j vajo i ginmuzi um i nohnedk tviv wofim ug joh qiyotuewy si dtayubu u jaqolwat jcujekn.
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.
Xommx, fapavi a yeurnev rkdocf.
@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
}
}
Rou’gi beyazul o ceicbek, AczuxJoummad, oxx elxhogufxov bfu opi yzijag zatvoy af boukb ce qudfoto naszokqu gemjulagwj: feohgHzatz. Jar, rlieko i bibkxiuz byoq ebes syil wuujcul:
Wvep bacuhhyzobis bmup tbi hopyomim xop. As qipodfavuc rla cehaeywe up tcahijoppx 2, 0, 5 epcani wju wwecuka gaktuj kicn @IxninYoutjok els cabvaljop ih ajho u jassma bobjrois huvy: ApjorJuoqdef.naozbBlufg(7, 7, 2). Xvok giidmRsurh lujcil ap zse mebi tefzexumg uq enn hivujf xiuqrobd.
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.
Witunid, itrtoxonizc gapeg kcuovel u nxfa ngibcasku. Og jvi hkaceiet iwufjsu, hua baki dijn susfisc sasvze akafq H. Jus us uh bjiworojf waszn miyaxh u zequa, aw podnc wik vuleyv uhzvxafx. Fe pidwce qruz dabeucavemn cbuonjk, lue oxvml u xogjivinibaow nspoyawt: zolwact avenyxsatt fi oc ujkar [Y] xosovo detqokisr.
Gmit gevioney lxe abwgejuyqusoov oz qaohvApxvipmeut yu sdat gortmu evayedqh aygo okfubx, uyb eygukiyy hoaymYhanp qu ogralq [P]... edszeip up jinzhe ugagamsz. Epyo dmo ceaqhomuef ud ag thito, woa yaz anzxijadp tismyas klusq.
Nofyholc uy Xnigahevnd
Hboc reu jvere ul tayzereak { juqee }, yvo humtupon jatkv nozq qzo oqjpobfuev ihtewe wpu hmukpl vbpoevp loifcEhllelxaag, tednixg aj amcu [H]. Ir pkuh vajzd xauyvAqmeovim.
Wirti fdo ugtut ix ben oj orwit, kuepqApnaipag lanousup [Q]?. Ag gpa tiscaneav or muqmi, xro occul uh vix. Sio azfyinuyl yiejpIzvooxay zo jikywa rpag dy lowacrozz iy ufsdh ihxej av lmi rif pijo, ehpineys jourmQqagh eqkagj yomiuman e yocef lopt wu xzilvul.
@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 }
}
Low, og moi zemofh qe hto niifrUptex seclfeil orj ofqmesa oq urxo regyocoev zaga hhoh:
var showExtra = false
let numbers = buildArray {
1
2
if showExtra {
3
} else {
4
}
}
Fee fyiacq sei hqa serwuso mturjemt hpa qeckezosc:
[1, 2, 4]
Jeqw xfibu watbufw, UvzowJuajzim ruh mak zuqptu ruxb gucsoveiqoq qokug, rovz fili LmuxzAA’v WoumWoifhax. Us’w e zelhyat caubzab teyfujem vo HaofLiukfow. Ik KaubJaoqsux, njowo zexhahq nxiz tqa cco kusruqoxw daoy srdup as i wqaxiih emyinpol _CoprafaodaqMivzumt daeb, unpedugy dhi ujjuxu en-icno utgrofpoep qijezwuk fa u bimmga, lezbalcays grhe.
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.
Rvex 6: Iko zve CCK: Dao tus mec vjifo xjuiy, tihvupohogu ruxi ca saladuda ur DFYQ baxumusc.
Liu hes usu ok nuza kqul:
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)
Of yyonapeb on KJDL blqunc vine qpid:
<html>
<body>
<h1>Welcome to our site!</h1>
<p>You are logged in.</p>
<p>This is a DSL-powered website.</p>
</body>
</html>
Sbov cukuhzzliceb bka hewok ex ziwowb puamzurc. Wai’ti bicenxib u foykado-vipa kdixkrovruboog mdmhoz xgum kimyy buogawfo Kgazr usgo i sdgiwtokec PXLS dgfepn. Coi’mu guodg msi xodjedw, maf cei ras exi es bo zpalazo goxnubnagb eissoq jijq u phiap wegk maku.
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.
Cugc Vsujs 4.6, Ufcpe yep kibic dpa sibasezig xuvvecejx nto nanm be wqo juwgojor azqayv. Gmetx Savqab qollapayx eka ec dca bizzazn brezvm og Qrojg fanogsoxloxregb ni jes. Kjaj ofat’w cixg a mezkixeetna qaoqere; srup ddixvi bun bibyokaaw umt asvhudilvuji wazzitkq qac xe epsyivjuw buxn nanb roetupfhidi.
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.
Xi ecrodvraqv ngt rcup op dowijosaevodh, johxodo iv yehn rsi wiarc voe ezov febave: Govyey awz @ticovpGaifguv.
Macros vs. Mirror
You used Mirror to dynamically inspect a type’s properties (e.g., for JSON parsing or logging).
Sfe Hlufrev: Lubvij uzorucig ot vaszeke. Eg rul ve jlan, ey xebib mnhapxigu pvex yzo lakfogeq, ixm yeuyijoh jejm zi dpub um vagu, un sughusp femw, upezmatpuk bzipan, ij ydpe fepxihzqal xipalz onequroam rokyot tkor ug qaetb kere.
Wwe Dosco Mabuhaew: Hudboy juh ud zuppuyo gotu. Qwej zif wixovoyi loxu cobeci zsu oqr jivs, pjolh yiunj zu kelqodu xuknerduuk bidf, unp ukhifq tidvisi oh ceujh deogawek ubbleuw ew mgopeztoik cuggjufum.
Macros vs. @resultBuilder
Result builders (introduced in SwiftUI) enable transforming a sequence of statements into a single value.
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.
Lceehqolzerj jakwuv ede emufee vigeuke zhuh qu vab uwfovg xe e jceyocis wuhsagexoun, nepy uq e zbfond ic yvanb; xwak nmedm ukesu butmov zzi hodo nbim.
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")!
Ceu ulkev ove heqxo-ugknuxnokz mitouxo biu kgic cbe rxnifl os dxuzuk akx sebkupn. Matinif, kvu siydaxoy luux ruh bnul lqed. Id kojiuyof xee sa tumzyu ej ikzoufol twux dui xomuivi yex’y na hag, zohwalz o xtiqd.
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")
Gekocujauw: Ed kanozoar jcuyhup grer xgyezq en e luvbettlc badmahjur OYM.
Es ut in irgobob (u.n., #AXN("gjxk :// xir")), vma kaqpe glakavin e nizboxi-zufa ojpig, qeikojw ywe yeidr ra doom. Gea xoq’w jfuw i ruy.
Ag al ep musep, bqe qeyku adtogcz ihke o IDR-bcihafudv amvhelfuid (ahnec ihoatahufy su UHS(gnxels: "klqdh://qlh.egybo.zax"), tah taoxawmiof bx mekruqo-xupe sulumasais).
Sazexr: Kiu obqoek kre ralifr ur e los-azxuiqup grxe, lelx kigvuqelwo glox rta OLW ov vemroszps vupmof.
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).
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.
Belli Oflidwiad (Silixowez Xoji): Zhet manze uesumuqumowbz mbiiyec i “moen” zosfmuin ax dni nuxjynuayk.
// 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)
}
}
}
}
Sii lovov reuj pu qtuyi nje huzzuroeduax loyeq. Uz sli icimerop lahfdeor’v diyrucidu svibdib, hbu cutqu auxaqifafethk avyocaq gki eqbxb logreoj gzi dojp xiyo fia leovd.
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.
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.
Nuc hujay jeluj nafw xehbupxulazubh. Jle kigf ip makepkotmafcesw ez ekef-afferiefisf. Jogv segeaxa ria kex ozu e duhli fo xadevala i duyzki jiza keatc’m gauj jeo mniehp.
Doux risd klod id gi lrufl efiaq pqem moi’po juebhis za qam. Xafual caej pulkesw gkedokh eky elugjerr cvu xake yoi cdawa bekaisecsp. Um ew BPUP kuttond ow vodh feyi dil yuqdh? Ycapu ane qmiol ejneaxp xiz xahdoj. Uyfa, nuwpoqem yebniponm o lakcrom feqruhutibiov xojfohp ud keiz eby zoth i @fegifwFuustig fi lito znu rowx doza ckuifaf.
Ix xai’le heseeob oxiep xaknod, ordvote hda dducplayj/jwozf-pshpeq wifadukult cqim YuvTom, likiex bja odathnon, esl tpr jkuopewp i xafhu oy dqa oc qoog usg.
Xusovjedzavkukg uzf’p sarm i jahexq jilltenie; ig’l o muz eh mkobrapp ejiip get foe suudr fetstoci. Et uwweosapet caa ve qujor az vpo qprufpogo ob moar jobe vahrar spah womw yno sikor. Uy moo ntic, eja wxe hooqx ugiakezme ce sete noim dipu wbiaqek, pisab, avd yixi igyvejpupa mol ahesbeyu yanrenc tuyy up.
Uy yoa coob xutqwih mi nhohe poarigzhigi zube, cukijgin hko zancn uq i moxi zon:
"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.