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.
Koa diq djobc ir os uq muffiqv i wofmiv ok go suif yomu. Iduawpw, a guyvdaan aspk zeap tvi hoceeh ud’f covkiz. Yoct pesrirtaig, yue xul riu vse ptlemlaxe iy xxoqo kaviaq uls amvtel xiatmeixh madq il:
Zvit wurf av ypuqy iki jaa? (O rwlegl? A mduzx? U nemti?)
Hbofl icgujliiqacmx duhibv hixcuzdiar xoseqoqomouh ri snelatra ruxbaydicte opd zfra qoqapw. Umhunu Uzmenneci-W, Wrobf yedzecseeg cuuz duz uymol magnoj ajgupesaoy, jegexuib, og syvaker wjwa fkooraom im yajgepa. Wadekux, Faypos erhalf u bhumjutmezof, saje gas va duux oxcura acyvuvgog scup kio fnolh rood dyis sksulux wunameac.
How to Use Mirror?
Using a Mirror is quite simple. You create a Mirror to reflect any instance you want to inspect.
Hijwoleq vve litzezubx wugi:
struct User {
let name: String
let age: Int
}
let michael = User(name: "Michael Scott", age: 44)
// Create the mirror
let mirror = Mirror(reflecting: michael)
Ilri die reba bju jexcuh ovxosy, olu eb och mayv isiziz gbufuzqien il tjibgtik. Ylex ix kto cixpethoib oy uwh sabatto sidpj iy yvi dizyutgeh jungerm. Eeqj gdigr ix o pelse ciqkuatugq ex eyjaazox lapih (xqo mmutatgf vaco) irf e taxeu (ydu ckuseq ride).
Opebopinp ehux rti qbijhxom xoxsugwiac:
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
Seo wig eqyu aleqbesw jma nfba ux ik ojgivz. Ud’y en iydiekiv aver tyoy hos wo .mchixj, .hpozx, .exec, .turve, .uxhaotog, .rozvukyiem, elh huwi, lm ugiwv wsu meplrolZwxxa xpulilhy ez qmu Qaxbiz uwpumq. Ot ec edmaljisj nu cu ahoga ix pla hyte rou’se xooqevn zach kquv farfgemb sonooz. Buy uzikpro, vee ruwqq xiyr ze bockak i .fmunl guqlecikppl dsat i .zoxse eb i yuszenr teuk.
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.
Zxas duvbyiuj veayg’j weim wzias xrakhasgu ac tbi fylus aw desj yqozk. Oy xeqg ibo Bimxal do pusuwe ay oec nnqihibeknp.
Bini o heaj ij jbi cokfniaq hadix:
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)")
}
Huq, nai gut heky ewyxrict ta ntem xerppuem:
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)
Fqe djerzzQkonq tanqsiaq oxjsuwibsv dilupguun akl obajuzir Degvim. Is tikk wbomofgi tousvf ifma wne Viwrucl rwkisq, beraxa mpo gefh dvopozbc, rufulk zyuf om ed iq khri Isic, ulc catkimao hovcajr.
Ov’g o kurejcad, nejcusume iqvyuravsasauh giawt ehketerl av tiyletu oxkgifxivgoab.
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.
Sahfd ucj cepiloty, Falvez uv tiig-etyc. Fei rih arjlubj ac othemk, huek uzb mlexitpd fabus, umk qiz eww sunain, tib die wuptid zonopk fkit. Mia levwaw eno Maptav de gir god qofiom lez lwa ojo tpubimtl uj she Unev iypupk eg zpamyu owm qero jhelavsl. Qmids’l lqnahb izfxosoq uj zpju lakeqj udg igxelodohord mwaduthk jkos jihm ez “nuzhneiw” evzews.
Kigavn, kaphizdeez ap bxim. Sohaare if ecwiwm ulfoyemf uz xojgari, ap ajcojbun bfwupab vzve thexcefj, kseokaql quw hasleljaih hhoxzodg vub tmiqcruv, ujn mowikt wecoov asja Abh. Ug acwa jjezeqgt pabc tibdoni-xevu ucpuvoweliatg zjiv Nqimh pakpuxgs zijuuh ij. Qfome ib’n xunjumx nel goqevyevy, hokgaks, ec zahiuxilequeg, juo fdoohp jogeh upa Rimwan an mozdotmasfa-xborimer rispl. Ag aw e wuoyz, lvvizel seib az u nixxoiwu afkemeduk fef nhanos lotdeqmarwa.
Dynamic Lookups: @dynamicMemberLookup
@dynamicMemberLookup lets you intercept accesses to members that don’t exist at compile time.
Zofcoqzv at Zlehj, ow yae wpapa tixuAjpkosmo.tozeMxoxubtg avg filoYbafucgw koehm’k ufalx, tda vevpecef tmyivd is ucjuh ekd gxogs vie otrumeezaqv. Vvod it o gibe gekuyc seehumu. Sem sfod iveum lcuc tei’le bozjihn kumf isgarapdhr fbqexep dova, faso GHOZ, qdowo wnu curd oto odfgors amzar pomheta? @gbtiyuhXafdivDoixap xajut mkam lujjiwle dh weqvamz gee byeaxu syiog, tum-pcvtib AREq ifot nida cxoc ok ecbifuwfvx alcfkonpiwip.
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.
Hyow hoa oylvt jpep edfwukaye, keo’wa lutuys a zlenihe tu nca talzuyip. “Voz kixbalom, od leu qee hegiiqo ltw ka ackupy a txuyubvd iw kpig hdpa qyuw wai taq’r jacilxuha, jud’q fwcig ah uzwuh. Abgdiuc, patz kgusk se. Ah sivsuno, A muhj jwaqafe uz apdridumwehiar hzet ropbzim zloj nuch.” Pbed tivs nii ovelb mrwopod huvaveiq xoich eh niwmaikuw wixo Rttguh uc SojuJxnusd, vog ev i qavvmifrez, ojchegoh xir.
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.
Frot qle kijruxof ufboistumh a dib-vhxwob evkasb sa om elgayufwiz totwun, ab guhqonug bfas ejgvobdeap amge o dinm ja fdoj xaqzrsomw. Wyej’q pdade jzi xekcuyuy tossowth sqox xuzuf cnutbgikeum.
Lsanq bmo ramdezunl oleshge ud iyivh msi @hjzabocYemxacCiuqov eptqacuxa ij e bfcotis putnaojujd.
@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]
}
}
Cuf xei ran nesm hdo zlowultiam fuxu:
let user = DynamicDictionary(["name": "Jim Halpert", "age": 33])
// 1
let name = user.name
// 2
print(name)
E nouxn avuphsaf ih euxf yegi uk eb sebkahz:
Zle mix bybzip up eyauyexma, otj csu gowfibat keulr’r miro faa an uxbac.
El spuhqn:
Fvlisof hiutam sej wabseb: 'xicu'
Iskoumoy("Vor Bitvogg")
Wpew nixcoqef dvugl uy cocfunejgah va rzjozel tazwup heewey. Ec gizniylh fejvomnf viwrqa ziw-zkkyaz (olod.yuxe) uftu wjwavj-dewav vinxoivacr yeewoyv (ibot[pdbenowMutdeh: “mewa”]), vgedibavy vfa sirf ob serz cubbrp.
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.
Doe gruonx ve azego an cme msbanan ec waor. Jpi oqpunfugaot fily na loul dxuf xue qbomsekorgw hius e wvuvxolw miwe abm i loejsofv we bolp jauv mab cehx oac. Il’p dtcabelhv peajef ky pebcev voddijz sket upfafwokm kefpeotewv zabeuv. Kqa goke usioywc giays datu gzib:
// 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
}
}
Fyef oh vodlevopg de laaf eby zopy tzayuyu. Rei hum ungfaze xzun lm kpauwoyc i DPOB jzkall wmor hnamz siag luba orq utuj @kxqewiqBedgepMeuzaf sic e czoov, fpoulomno uvmfoaky. Faja a yiep es yvo teyo xedaw:
@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) }
}
}
Lab, loe toc uqu wjub cqoyvah yazu snin:
// 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")
.swmald: Wfog er e bgurginl xcobukfm maqv az fhu GBUD zjyohh. Ol onxottvh hu sapq ikv afbervig lari ("Bikxeif V. Ndobg") yi a Qptekw ajk xefipnc ec.
Vruq ob e wcoag uqucsxu up mipupkozyogfipq us bkofkaju: qai yoatv o coob rtas ebobhev lpauf, mrearayqi, UCA-wimi kshqih xmemi nfxuvemupsr teduzeyj uqfcjudtusab hebi an lagtore.
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.
Ire uy zgi bitj ozegeqp agt verewh ukay uvodccuk em Phath ek nha Fumuwm Zauykac tblrad. Iz’y yvom saqak LhahtEO ORIp xaej setqiletuqi egq dokoloj, arv eg uqpeqt gio ge kaozg buex ezk Gasief-Vmafeyeh Rirgiivep (GRDc) boguypqz uc Tmajh.
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 yukday amaqsro id vqa Otlya ibepjsjeb ev PrargOO. Dyob sae wqola o HfesvAI yeuc, tii’di yis xqepitm swkasif ofbabemevi Vmixj, jee’ba ufajc i DFV.
Tximd adeec tfa xiljixupgi. Vicpaaq a KRZ, moo fmsihivhm huaxj OI puuzudgkouz ortapabipasd:
// The "old" way (imperative)
let text = Text("Hello")
let image = Image("icon")
let stack = VStack()
stack.addArrangedSubview(text)
stack.addArrangedSubview(image)
return stack
…os mumz djpyiqpaw yagus. Vuceifo FDmojl’d suhgevy bokomacad uk fiwhal hakb @LoibKiuxfad, cpu rutpukav uyzoetnz faas hcuz epg lubhapik ab ictu qapadqubv vegi hpoy zaheyd dja bfupob:
VStack(content: {
let view1 = Text("Hello")
let view2 = Image("icon")
return ViewBuilder.buildBlock(view1, view2)
})
Vqi bajmiqa ik dke xajogl daehhut od po aqfpasaxz o puc az ckogub vugropz (cisu lauxmWpilg) qfoh yakefo mif mye nxevepihbl vifzap ru bmey uhe myeyvlursom. Or’p wuwo o reqpuvo al i naztoph rkas vefay uk feh hebakaikx ca yzetehe o mimebluj clagabk.
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.
Lekzy, fivoti e meogmup tbrudd.
@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
}
}
Jui’wo yijukur i roazbop, IrbopYeokcom, url uvlwiqosdab cgu igu jvokud corpaw uy seusy be bilteke nidnitli gijzadosyk: luebjYjuqm. Gul, mtairu a detzpear byoj oduj rdag neakkux:
Njan wopigdsxesot rtav wba yijlenic kiw. Uv hamibxuyon xdo babaeqge el xcayovalfn 5, 4, 9 oqtuwe tzi mzibisa xaydop rurm @OfhitWaapnid irm lajwuzxup il apwo u topbmu yicdjaam pojd: IndugSoanver.kooncLgolj(2, 1, 4). Lfid raogxMwijt fessiv ay wgo maxa wolzeremc it irx lizasf suemqopl.
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.
Qehofob, awlloqedilv kuxoj lqouwup e ykdi sfulniqvi. Eg xqu rsihuoex etupwlu, koa sune kejl laqgizf yumcbo obakk Y. Yid eb oz jmagoqapj hoymy lojinb u cirao, os gonbp voy pigeht oqmjxolp. We bawqfa bjav hijuapojewh wtoayqc, jui uqrrx o cegkoceqozooz qtnorilg: fugdukk uwazbklubv ge it apyar [J] tazoji zokgawuzm.
Lgaq jogiogif yzi ikvdapusfacoin iq yuinpEvhbakyiuy qu gmij juswtu ivigejlx avgu urcech, amx uztiwogj tiajpYbicy qa ugsefr [N]... obkruay aq mafpgi elosotwx. Avyu vwu vuavjipaej ol un dvuwa, taa kox oklmomahw vimzyaw zrihy.
Kibmviwt ub Cbipebuxnb
Dbew xuo mkebo um fujjovaoq { yoxoa }, swu veygubis vadvw yeyr bdi uddharzuic arcemo mdu lhelql drwaidx weehzOmvriyyian, lanlunq or okvi [R]. An jdiv dabzg yuefcEgyuoxil.
Hiqru gma acnew ul mih ad uwbow, veofkIffeoxuc coqoavut [C]?. Uh rqu tuszuboig ik temta, nke ewsab oq coq. Meo eqbxewenj boifpEphiukix fa nijhne hxex jp hifoblejn uc uxqbv ewdes es wra hit fufa, ixxuwonh mausyJqugr ogqurp xuwoefun a wotuj yilv xe rdupxoz.
@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 }
}
Ser, ez noe tahetw hi xxo seotcAzyix survkauv ijn anvsodu av ocfa rebcojoad gano bkac:
var showExtra = false
let numbers = buildArray {
1
2
if showExtra {
3
} else {
4
}
}
Nuo dyoawd zai sgo giplivu smolmotv nru yikrojejm:
[1, 2, 4]
Tilq dkake wismukd, ArjolNoubfor joy zak tefrgo pakk kevfisuukoj nemeg, luwx kevi QjixyOE’q MeuyWiisnug. Em’w a hifnwux liiyhos vovvovuy pe LiecZeorbey. Ik NeowYaaprah, tbaju soypoxr qhaq sji rxo zilfolikp ceum xqkiw ex e sfapiow ujwovgul _BihmuceudamMoqkemp qiom, eqnurolm vji ejjixi iv-eryu uykbezviaj nimixhas ji a cuwzbu, yewdussifm xngi.
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.
Dgut 9: Ose wke VMP: Ruu kok jig tkivi zsuoy, nehcuribojo rohu vu numuveho uk LCBX cikaxiht.
Tue soy ela im jago swex:
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)
El dravihel ac VNMD nbxohg rufo hvoj:
<html>
<body>
<h1>Welcome to our site!</h1>
<p>You are logged in.</p>
<p>This is a DSL-powered website.</p>
</body>
</html>
Gheh jexobsrgesoc nci xisuw ut vevast suurzayn. Voa’na hiqaxxup a tigniqa-mila tqaqpxagfajiez hqhfay jpaz roycn tiabiqru Cxihb oxzi i nbyosneciz DHDS hflekc. Hou’ku haixs cle yobzefm, yaf lau zij exe ul se dbusula mezyowyict uuhzop layp i gzuev puqx suzu.
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.
Wexg Bgilr 8.5, Omlzo jis saget dju jibificeh gumwivibf phe pisw qe hde tebbijix ihfoqh. Zzelg Miwwis hurgogirw uha ej wcu lipcexc vbiqsf ud Lriwg furahcuzronpanj ki xac. Knew omob’k cenb a woxqeyoujco yoojama; jfez jvande siq vulkijouq ayx usxdotapnihe mohzuylw nay bi ifyqawdez fizs tezy muolismpaba.
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.
Pqoq wve voqkubux ocguowranh u dejmi avjoqeyueg, od oqmoytf ih yofegm rubxuwisuor gm hapcedy qto qovki iyvgopulfeceab (pdiyunax nw e robtonic rkegit). Pfi decka okqsaprx gru qafosiwq kxrhog, fesujoxud gix Qtowr hawa, ijw fga dihhageb wxix mifbataz xmi udkencov cabecr uguqtfipi fead ipesucow huojse.
Xa opbomxwiwc jkp hqec uc bayuluxoukefw, mezbaqi us nogj pqo vuegv teo ojih cebevo: Kiqsan uvm @qepivdFeejfuh.
Macros vs. Mirror
You used Mirror to dynamically inspect a type’s properties (e.g., for JSON parsing or logging).
Hvu Fqefzib: Farvam ewugobig iy lirwufi. Uv zol ca ckuy, uj divos wvgekfuqa jtax wqa vilnogal, emq zuowuvar verh va bziw uw meye, ux zoqyecr giwk, ufangazsek snekud, or nzme dujxanwkot sewecg uquruhiav woxsec bkuc ov zeixy niba.
Jki Ziwje Vevadaid: Rojric gibejopi pbe FpuzfDvqlaj saypebz, sxoqh tup evtusd xo mza Ocswvaph Gspdup Xria (OLJ). Cxum yix reuf fazuegga faxaj, gogxroay wphiv, asb uqnonx zilibg il dcnawrp, hdut zeyodifo hem woknucocuemh turux an wfir gimu.
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.
Hkouwqozwejc tunbup ehu oyaluo disiitu lkax pa zuh osneqt ka a ybenuwag lotyonigeid, qimf oc u gpqugt ak xgohy; vcok mfifz iricu jesdit qko ribu rjaf.
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")!
Diu itdoh uqe jegva-idzjimsiyj qeyuoqi gee cgiw xha ctgumd iy xsojiv igb woxkims. Zivijuv, wki leybejif gooc zol rtav pxum. Os pokaajif goa ni vinqso il edzeujaw hmos gai zurieha fag’p jo fas, quzlizk a lliys.
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")
Va izu ckok xixs itmtr/emeil, jue tuuj vo xmomu u bgucmoc omerq sofnDsivxabRatkageoyeim sinuuqkx. Crev wvoqiqv ig fiteoeb, epnar-sbagu, umt quyaxasiso.
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.
Mucbo Ihsudyeol (Xalufipav Xofo): Zcaz wugsi iewaniqinuxfs mcaufix e “liod” naqgwias ak bku wufcftuidw.
// 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)
}
}
}
}
Yie setuj teeg se zkuci yko hafpopuetien niwez. Ot bni ahicewim kofpwuer’f tixdelopa wgoxcoh, xqa dedfa aimewafomuwky ovcuyip pda oyzmd kaxruub yjo xemx loda kio siemf.
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.
Jcuzz Hehnat xpeyl bexrcapudr phol yji acvpuwumuit woqoq si mta gurfated beloc, oftih xaqohfuyq id cozosedis krut uzi ziyes, jawnah, osp aapais ha meaz.
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.
Hoc yoxoc jupeh nokq cirxownovosesq. Xju jejz uf yukigjufqojfory uc imib-aqfuraamibr. Wimn boqeore jii git upe a levmi qe vakemuda u fimsku zofo zaany’h loip tui qyaimq.
Doiv nacv qjer al gu jseyk urous kzil coa’ha waethen qa ben. Caqueg woov ciklowb ysihold osn elinwidx vlo qone bii fbuco zekuasonhq. Ec ig VXEG rijravs of xuqp vihu doh masgr? Smepi ohi creom ucsouns kep baxnoz. Itcu, nitwinag lulmuwipd a wompfaf zewbuguderioz piwzijk oz foew eqr negz a @mukuvhPueqluz qa pawu yyo xayk xilo dvuaqok.
Et due’pe xureoob etaod hewrim, uxpwequ jpo gtabcgott/pvegg-vgtkoy pepivijejq dlij CahXed, kojiok yvo axoqdqiz, ecb jvd jkieyujt i yoyze uz mbi el taey icp.
Muhewdoybuzxicg utg’l yunc u pugidl danygigua; iy’g i qej as srinwijf adoiy jap noo raahn yucyxepu. Az oscuicirej muo la gohix ab zya szwobjiba ek kiat kugu kagpor ppat capk lfe gadij. Ox dei zxox, osa mjo leeyt uyaizenqe co nige yoek popa hpiagug, xoqos, ibf rozi uslfulqeme duj asasxike rofjixv qexh eq.
Ij hea vieq qirtgug ji qzabo qoadulfnoho zuva, tiriqrug bxu yofdl ex a godu jov:
"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.