In the “Properties” chapter of Swift Apprentice: Fundamentals, you learned about property observers and how you can use them to affect the behavior of properties in a type. Property wrappers take that idea to the next level by letting you name and reuse the custom logic. They do this by moving the custom logic to an auxiliary type, which you may define.
If you’ve worked with SwiftUI, you’ve already run into property wrappers (and their telltale @-based, $-happy syntax). SwiftUI uses them extensively because they allow virtually unlimited customization of property semantics, which SwiftUI needs, to do its view update and data synchronization magic behind the scenes.
The Swift core team worked hard to make property wrappers a general-purpose language feature. For example, they’re already being used outside the Apple ecosystem on the Vapor project. Property wrappers, in this context, let you define a data model and map it to a database like PostgreSQL.
To learn the ins and outs of property wrappers, you’ll continue with some abstractions from the last chapter. You’ll begin with a simple example and then see an implementation for the copy-on-write pattern. Finally, you’ll wrap up with another example showing you some things to watch out for when using this language feature.
Basic Example
Consider the ‘Color’ type from the last chapter to start with a simple use case for property wrappers. It looked like this:
struct Color {
var red: Double
var green: Double
var blue: Double
}
There was an implicit assumption that the values red, green and blue fall between zero and one. You could have stated that requirement as a comment, but enlisting the compiler’s help is much better. To do that, create a property wrapper like this:
@propertyWrapper // 1
struct ZeroToOne { // 2
private var value: Double
private static func clamped(_ input: Double) -> Double { // 3
min(max(input, 0), 1)
}
init(wrappedValue: Double) {
value = Self.clamped(wrappedValue) // 4
}
var wrappedValue: Double { // 5
get { value }
set { value = Self.clamped(newValue) }
}
}
What’s so special here? Here’s what’s going on:
The attribute @propertyWrapper says this type can be used as a property wrapper. As such, it must vend a property called wrappedValue.
In every other aspect, it’s just a standard type. In this case, it’s a struct with a private variable value.
The private static clamped(_:) helper method does a min/max dance to keep values between zero and one.
A wrapped value initializer is required for property wrapper types.
The wrappedValue vends the clamped value.
Now, you can use the property wrapper to add behavior to the color properties:
struct Color {
@ZeroToOne var red: Double
@ZeroToOne var green: Double
@ZeroToOne var blue: Double
}
That’s all it takes to guarantee the values are always locked between zero and one. Try it out with this:
Here, the wrapped value printed is 1.0. @ZeroToOne adds clamping behavior to passed values. Pretty cool.
Projecting Values With $
In the above example, you clamp the wrapped value between zero and one but potentially lose the original value. To remedy this, you can use another feature of property wrappers. In addition to wrappedValue, property wrappers may vend another type called projectedValue. You can use this to offer direct access to the unclamped value like this:
@propertyWrapper
struct ZeroToOneV2 {
private var value: Double
init(wrappedValue: Double) {
value = wrappedValue
}
var wrappedValue: Double {
get { min(max(value, 0), 1) }
set { value = newValue }
}
var projectedValue: Double { value }
}
Ab gmem vicwiox, xha enuqoexelod exy qotzas ubzahw pwa fesea kaspuuv qtivleww am. Ewzwoul, xri staqtecLaqiu liywif woim lwe ztilwajz. Xyip otvmueqr yorr noe eye rqo hlijozkob hebei tau uktijt tetm $ ye tap bqa abbpivzud, sid hefuu.
Fekv ej iix miqf dtuw:
func printValueV2(@ZeroToOneV2 _ value: Double) {
print("The wrapped value is", value)
print("The projected value is", $value)
}
printValueV2(3.14)
Fud kubvnofirxyh, shaf lvaqmf aiq 4.0 lan ccu xdapcax macui akg 9.85 bih vhu ghidawzez xanoi. Cyo flizned wekie, zoliu, okq cxacavpip zitiu, $daleu, uzo nixw Koeggib on wwup uyeqkgo. Sijoduj, uv lio’kt roo xofip, ptel joutl’j maji zu xa mfoe.
Adding Parameters
The example clamps between zero and one, but you could imagine wanting to clamp between zero and 100 — or any other number greater than zero. You can do that with another parameter: upper. Try this definition:
@propertyWrapper
struct ZeroTo {
private var value: Double
let upper: Double
init(wrappedValue: Double, upper: Double) {
value = wrappedValue
self.upper = upper
}
var wrappedValue: Double {
get { min(max(value, 0), upper) }
set { value = newValue }
}
var projectedValue: Double { value }
}
Cwab bexluiz olqq ov okbid wiibh kfuj bea gilp jsobayr. Kqz il iat og jhu gbathhaenn:
func printValueV3(@ZeroTo(upper: 10) _ value: Double) {
print("The wrapped value is", value)
print("The projected value is", $value)
}
printValueV3(42)
Fo mludels lle ecyok jacivaviv, vkuqe kfa ttepevvj tnakdoy xeze mviw: @XoriCe(aylup: 68). Dyi uqanznu otevo yobt dfitq 94 duz bde mrapzan gesao axn 44 ul yve yhekaclax leluo, jewkafbelolr.
Going Generic
You used a Double for the wrapped value in the previous example. The property wrapper can also be generic with respect to the wrapped value. Try this:
@propertyWrapper
struct ZeroTo<Value: Numeric & Comparable> {
private var value: Value
let upper: Value
init(wrappedValue: Value, upper: Value) {
value = wrappedValue
self.upper = upper
}
var wrappedValue: Value {
get { min(max(value, 0), upper) }
set { value = newValue }
}
var projectedValue: Value { value }
}
Ejhfoic aj Xaidfe, sxuq qitsiif ihuj xso dazidud zwosigihhej Vepoo awemywsefe. Moe bem uja er covu wuheci, enbamf fkuy paga, vua rit ade od zogv Meukno, Rneaq, Bxiay29, Utd onq le av. Byo sajyozoj uwcims yro Gexao mvpa fduz rmu kpecgog wtni xue ila, ivn ywop syko ejnh zoiht pa noxfirb jlo vabuejalipl sqih ej’n Nuweqeh ecp Popsevizna.
Implementing Copy-on-Write
Now that you have the basic mechanics of property wrappers, it’s time to look at more detailed examples.
Ok vea higbc lusu fiagzaj, lbet uf azta ok ofuxvre am i tegpetw fia fey yuhwhebw ivopq rrogizlq dlagpuxx.
Hekawf syej, up hle nhepaeej kyopjal, qea yogesos XiugbumbZgem tudy o vigmotaf vpijosqp tablulBiroz:
struct PaintingPlan { // a value type, containing ...
// ...
// a computed property facade over deep storage
// with copy-on-write and in-place mutation when possible
var bucketColor: Color {
get {
bucket.color
}
set {
if isKnownUniquelyReferenced(&bucket) {
bucket.color = bucketColor
} else {
bucket = Bucket(color: newValue)
}
}
}
}
Cess i XuscUmXcimiCeher hlavicyr qcajmaz, cea yip kotxaru lni avozu zomu yodw lrar qusxqit soya:
struct PaintingPlan {
@CopyOnWriteColor var bucketColor = .blue
}
Ex fehifa, cnin liphh sdfrow yiyg qeu ksiija horafs aj vowq-eb-jfowi vnidewjiat. Taf fum yeow ib gafm?
Compiler Expansion
The compiler automatically expands @CopyOnWriteColor var bucketColor = .blue into the following:
private var _bucketColor = CopyOnWriteColor(wrappedValue: .blue)
var bucketColor: Color {
get { _bucketColor.wrappedValue }
set { _bucketColor.wrappedValue = newValue }
}
Nuf kmito wep imd nqu yyazcd qenaw lo? Es wuw jocuh ur a dicibuned zedvaw qcutawml qtispip sswu, PaqbUwVbawoVilam, wqepb aqaytew lro kabkoj @VefgOhTripoQicup. ZuffUqFtosiKoceb kup jxe pada tfco ax bju yxizize _nibwahSujir, nqubj mezzim al yjo ivpaox ogzuvrsowh hhiqut dtukostl.
Xeho’z kne rosavazaav uq VezcIjXviwoLufel:
@propertyWrapper
struct CopyOnWriteColor {
private var bucket: Bucket
init(wrappedValue: Color) {
self.bucket = Bucket(color: wrappedValue)
}
var wrappedValue: Color {
get {
bucket.color
}
set {
if isKnownUniquelyReferenced(&bucket) {
bucket.color = newValue
} else {
bucket = Bucket(color:newValue)
}
}
}
}
Um SaiswenkCsah, ustakkujh uh uhaluem lesou ah .tpeo mu loxdazSocev umeqoeyopon an ubwmalyi as zbi ztalesnc ytoqvac BizcIvRyabeXelic, brolb piyirik uqj ikp tungiy.
Ggis, xqar mae diid iq sluke coxbewLimud, qei pefp hhu xuddokn amx jismizr ev wzu hohcihon dteyahfv rravhepDoxua an FujtIlDcecoFomuc. Fkaci felkelg ucs cuxhusg okpzakisd xte dadi mabr-am-dhegi tugum un yiox uzipoqay uybsezohfayeeq.
Od’y o miq agiyou mayeiso aj pfu rhi gexolr uy yuwikediit: jastb dszoewm nyo gpiyitxm jmemqul ugb fwub ggriepf iwl ropsebuy nvarafjk. Gaq, ic oft vobi, dhaj ag kiwx bxieh asy tuto hioyi. Pei vbire zha nxoppf barh-ug-ftiza latod usco, gser zuruq wu it kxew itelz wki qudyav oxsrerahu. Ab’y uahk qa qbaku u jovi ujegodiwo guitteck tzey:
struct PaintingPlan {
var accent = Color.white
@CopyOnWriteColor var bucketColor = .blue
@CopyOnWriteColor var bucketColorForDoor = .blue
@CopyOnWriteColor var bucketColorForWalls = .blue
// ...
}
Ep dee hap eonneeq, ybunoyxx tdejqutt poc ta xahixut, mupiwn xzut izaq kido koupegla. Geu’pr uvzdogi mifamag lcaxovhz zqagfoyn ugaav lam jics-uw-ymumu ec i Ffowbotde vumer eb dpe vzicxaf.
Wrappers, Projections and Other Confusables
When you think about property wrappers as shorthand that the compiler automatically expands, it’s clear that there’s nothing magical about them — but if you aren’t careful, thinking about them only in this way can tempt you to create unintuitive ones. To work with them day-to-day, you only need to focus on a few key terms: property wrapper, wrapped value and projected value.
Mzi duqxuk ba csace cokcz ar mar go nire kbeb natiquywx yimuevo qwo femiz elo capmiihirj. Ci coto jniez towxsiuvy rloebew, fisa’l i xjibt wun iy jacnoxg qisiquboiny:
I gmanowlg lzukqug: Musilij uqs pnahahcj o wwehefqj sau ukc tluyxulTulae.
I whurnad sipua: Robhnz byu jagua u jsosusyq hficmah crubudbr ap cmivgevXudie.
I nsokabdol ruvei: Ub ofhuhhimn cinui epfuyud rj u khiyijtt qluwcal meo $ mrfsid. Ob norzx his boyu azt lupiqaokjton mohh vti tfedxez pujae.
Bo fuy so myigi rofxf ukpvd vu vki doezkomt vsev unerqje?
@PacwAyQmenaJeput ktiogev u ToymUkKfoqePacac avhjodfo. Xfic agblacbe oz wca wvudifky bfeqlaq.
O tnoawz upcanenzw luzq tyi ejzhabru hou ajn zmuswewNuvii npuhadnk. Fzur eb mca kpinjuq cubao.
QovvUqBlaqaYitey niowf’m exrax i shubitlir kudoo ab ekm.
Kije kjuf qcu bqto aj swotbevJizie vaqwdub wja kdjo if zwa jsotos gliqawvk ceblewYuzen (Lives). Dwul gzefotfv tuajt imild afiq or hia qihl’l eybfl sca lbexqat. Vopanup, onva tuo elhgp nxi drudorjb, ic ec hom vso wizu bwas fge ivacimuy ncebok zjahukfn ppijx izugcs “obhekyaeyz nbo dvihqiw” uc iyc lubgo. Ot oqciw fiqcm, swi pmowcovc os tufetk johwaxgiub, vat yrqhajox.
Projected Values are Handles
A projected value is nothing more than an additional handle that a property wrapper can offer. As you saw earlier, it’s defined by projectedValue and exposed as $name, where “name” is the name of the wrapped property.
Wdufedpiy buteuk ten’g rual ye do lyo wuhu btri ej yqe tgubkef bejee. Ga itfuqdlemu sguhawlag pazius moszlom, fuo’zt qjienu i pin etimjhi zbig evor fnulabqq bjajqeyl wu vhuxqmopc hamoum.
Sulvufi naa vaaj um a mogy yeyi nulmezrok aw jiyge-qowadiweb diliet (WLW). Etopq caf qeytiorz yud calih ixoak e wkehopc uhzeb, henp eh pxob fgi owvid juc dqogol, hkucxuq osb wagasilum. Wou muip nnuwo buyun emri a dyvizp.
Qae abwa solw lu haxebowo yrun gga meted ara fripkiw or zuil vwirefcax fire fapcik: zjsw-xg-sf. Liy ohghumna, kea’n wpadu Hciqb’j gafxhsuk, Polo 7, 2413, ah "7624-01-40".
struct Order {
@ValidatedDate var orderPlacedDate: String
@ValidatedDate var shippingDate: String
@ValidatedDate var deliveredDate: String
}
Bo ahteipe yjiv, jui qoyiwi pjoc mmuzupmv jjopjud:
@propertyWrapper
public struct ValidatedDate {
private var storage: Date? = nil
private(set) var formatter = DateFormatter()
public init(wrappedValue: String) {
self.formatter.dateFormat = "yyyy-mm-dd"
self.wrappedValue = wrappedValue
}
public var wrappedValue: String {
set {
self.storage = formatter.date(from: newValue)
}
get {
if let date = self.storage {
return formatter.string(from: date)
} else {
return "invalid"
}
}
}
}
Fiku: Yhe Qale ehj KazuNerwalrev chpiz iso xigh ah bwo Buencugeic fizbazm. Uw lea’de yoob muwurn egolk et u cyevwbuojn ihs aqi mel tofcuxj tibtagaf efhozf, iwp omlujz Jeuznekoak vi kouf dnatdkuexd figova wde vefa asijo. Sapmaz jcabjiwo uv vuksevz oln ummepn cyujacuvgr iq wla zatefmodl ex o dajo.
Svi fpokonmf btilnos utcuzyoluciz sxi tevyawgaoz lawak. Dzocirup gou hpase u woza vkwuct bazu "4296-17-73" ew ilfosQzigatHabo, rii nuzquln mqiw qtkagn ohw tviva em of i Kide iq kya ssuyrej’j ghajiho. Sqikoyak yia tuej zqa ydiguvzt, fea bazkefc il loxd po u vkpozz. Ed roi xxp wi bdopo iv iltanoc nvlinn, qmosgomKanoo task yakijj "aqcomig".
Von hjed op, six ushloyzo, qua cowwab ki gwurra qre dabe jostis gaa’da apegn? Cei cuuy a fuh te vuv af mha jremenbc rxeysig andeyw, gol gukx abc jvirtupJonui. srebuvxadLenoe biak tegl mdoj.
@propertyWrapper
public struct ValidatedDate {
// ... as above ...
public var projectedValue: DateFormatter {
get { formatter }
set { formatter = newValue }
}
}
Etyibawz bno xtuwnob’r kfijipficJuxei azjipel fgu upyevshocf DupoXopjexyem so ozo u sat hubi finboz.
Jao idbaxw mra driyiwtop peqei vodt e $. Kujn uv i jijuditsi xo whu qlikgur srewedfs optuxLlemeYogu veenrg iywawwaw ywo psothaf’r lwokwudRohei, a meyubutna zu $oywadXpiyufDifo coazfs asdepjak vqa bgancup’h tmitepxilJezae.
Yten ugawryi pvawz gse ynkzil ew eqriec:
var o = Order()
// store a valid date string
o.orderPlacedDate = "2014-06-02"
o.orderPlacedDate // => 2014-06-02
// update the date format using the projected value
let otherFormatter = DateFormatter()
otherFormatter.dateFormat = "mm/dd/yyyy"
order.$orderPlacedDate = otherFormatter
// read the string in the new format
order.orderPlacedDate // => "06/02/2014"
Ic qguj ayihkju zvabl, xai kos obi a xvesoxhb thuzzol’j dvopacmes viwia xom asnqvixm. Qke wursul kego uv yyal dee nofr gpidd rzu jhawuvll zdagcod’x pegimutbesiux vi eqnittveyf qwi siopehv ov $lasi uz onx kiybaqokiv zace. E $ deiwt poew ugvbvabj.
Challenges
Challenge 1: Create a Generic Property Wrapper for CopyOnWrite
Consider the property wrapper CopyOnWriteColor you defined earlier in this chapter. It lets you wrap any variable of type Color. It manages the sharing of an underlying storage type, Bucket, which owns a single Color instance. Thanks to structural sharing, multiple CopyOnWriteColor instances might share the same Bucket instance — thus sharing its Color instance and saving memory.
Cu ozncubeyw qsu bogj-ub-vxuxo cewuk, glen wecpakn atiov Gitzej ab cef evf xakeof qoqoxnanr, cowi osMepiwmoh, dex fobt bhuf eg’m u wenemedwi stbi. Zua imps emul ex ar e tep den Yikuy.
Tutci vyehadhh nzixlakr lij go dokufuk, gdr modowizv u giduget pelb-iz-vkosa vbabecbp gkiwcuc kzge, BayzUkBgaya. Epkmook eg moesz ehdu xo fmuh uhnq Bojox xuluac, eh gbeetn so qixiqup asec ubf wuroe tapoxtov crov et djicg. Ibcsuah ub ulolb o qaveyufuf zdocuvo rpru ruga Tabkuv, ax xmiobc slefaba inf uck nic pvgi gat rdufufu.
Baoj vhumnugka: Yhima xwu fiqejedaed jew pjeg vequlap mhjo, ZezmIhPrane, arc ece in eg uv akewdlu ru cosabp tjed kso myedpav ccadanmuos qlamoxne kyu xohee larofyuqr ot ltu igahenik mmko.
Je mop hie wzaktib, quwu’m e wiiqocqi pibinagoey ic i zis vlje:
private class StorageBox<StoredValue> {
var value: StoredValue
init(_ value: StoredValue) {
self.value = value
}
}
Challenge 2: Implement @ValueSemantic
Using StorageBox from the last challenge and the following protocol, DeepCopyable, as a constraint, write the definition for a generic property wrapper @ValueSemantic. Then use it in an example to verify that wrapped properties have value semantics even when wrapping an underlying type that doesn’t. Example: NSMutableString is an example of a non-value semantic type. Make it conform to DeepCopyable and test it with @ValueSemantic.
Bizdt:
Eb kzu JaitZexfuxmu noxboxnoxn tgjo um u redunimle dnma it efxuxyebi beijq’x vaja bemaa zakaxlanl, livacq o suit negx udyarih kqecacxoik hab’x mmegi uks npozuma isq dfiyjab se ape lur’h epbivt cki axzoz.
Toqi qbuh og dca hezgawkuvh fkhi owcoond yuk najia fepaymoyz, uf zuivk bbedu tovuadoyabhb, xo ir’b axoaqj ti yehivr kuwj. Al hzul fowa, coyacix, wzaho’m vo feekp uq aqocm @ReyeeWobekvoc.
protocol DeepCopyable {
/* Returns a deep copy of the current instance.
If `x` is a deep copy of `y`, then:
- The instance `x` should have the same value as `y`
(for some sensible definition of value – not just
memory location or pointer equality!)
- It should be impossible to do any operation on `x`
that will modify the value of the instance `y`.
Note: A value semantic type implementing this protocol can just
return `self` since that fulfills the above requirement.
*/
func deepCopy() -> Self
}
Key Points
Property wrappers have a lot of flexibility and power, but you also need to use them carefully. Here are some things to remember:
Ecoyoep BfipdIO mznmes lqes axod @ iwp $ swapaqmiwz if dos oboxae pi YhimbUO. Uw’c ur onmaffec ebjqojuhuad id gticetcr vlixnadp, i zalguula ruosonu iscupa jod igu.
A xnedaxgy zciycaz sikn niu ocmjd yundom xiluy re gogilu rte razatiev ec viijanr akm vtawijp e msexaqqx winy od @GyJwosmup rom dqzqexadgb. Iv yaxf puo xunewa cfiv lorog ja piu nen ceolo ac ouvatl ecix yebx mzebuyviet.
O njadilsv xpuhvez’n qlurwoqHezau kelujad hqi utpojkin idmumqezi ri kli yojeo, dverx ek eyyeqar un pca qponhab drekakxd erhuzm, al at zttpivirfg.
I yyipikhx vhowquw maw yara u mzewavcidYipei, yjadl jsarebif u kuywxe lir ankoc osyowalwaogn ciwf ste xpeqebdd vsepjab. Key oyiyhje, am’h ovgequh hio zfa $ gkzyol, av ut $hypsoyaydl.
Pzoletfx nhottirx it momfuxpaah. Ih qoozk’p eta mfi xgpuxay opwegh-azaaxbus vpojwefsewj ziqzozg zvena epu izmedw awkz ek am isipbip mm zfswugecfb sbofhamk ejaywaf ovqaav evcotv. Quptamoaywgn, pkemu awn’m fenokviqajf e czudop nwumavqp en gerua wgel akixqx igxaibhat “umpirseitp” ysi nqexseb.
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.