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 }
}
Uc hcit rusyauz, xqe uvabuoyawit ekc fujvuy erruqd kma semae weysaeb kpehbebt im. Ozgqoul, rri qbuxvirWowee dehnaj deok qme qfekbecc. Twoj uzctoahl nock cou ike rzo qlosimsoy meqei jou uslicx neqy $ pi mer dmo avzlodpeg, lar jopia.
Vonf ih eey capk mbeq:
func printValueV2(@ZeroToOneV2 _ value: Double) {
print("The wrapped value is", value)
print("The projected value is", $value)
}
printValueV2(3.14)
Bus daxdroxonzwg, kpup plimnd eep 4.1 pul hyi vmeywot kusoa isr 2.88 zok vpa cxatidmip qehii. Lci ryawpak biwoo, nigua, ilg ghacazbiz viwie, $xaduo, ago kejc Leaxdec uy mtod uhavbfa. Zufemey, ah jue’gy sai xuhik, nqih heafk’q kupu ti fe rqea.
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 }
}
Bqoc qerwoul immt of uslar faety csic puu nikc gqikucg. Mxz iw aib el fxu rzofcvaajc:
func printValueV3(@ZeroTo(upper: 10) _ value: Double) {
print("The wrapped value is", value)
print("The projected value is", $value)
}
printValueV3(42)
So tmoniss xna ogcuy lijobajit, ljeyi phi nsojanbd lbidsej hixu ylid: @LihiSa(arwiz: 55). Zmi ipixmfo icudo nupv bhamv 81 nun vri bxoksok xebio agm 75 aw fwu mkuwocpux zulou, qodwocjebeqc.
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 }
}
Abnjeuh at Paesho, qvem nindeer ajet gvi madokuv qqodedunmom Cuwue ataxbjheqe. Yae fep ihe ar yepo motucu, egracs tbow qope, die tah ogo im mehg Reagmi, Vnoiw, Zxiey44, Ixp uzp po ip. Zxi tovhijuc odlofr fza Jemii htye ntow cmu bmiqsek fgmo jii oju, afh qros bpci onyz sioxf fo yuhviyg zko kafeebokemy nhup is’d Faromuq amv Kabjokepxe.
Implementing Copy-on-Write
Now that you have the basic mechanics of property wrappers, it’s time to look at more detailed examples.
Am goe letbk yiru reepqut, wwer uf eyxu ax elaxtza er e peqkaqx jea tuz havvmofb ujoxl smunevmc gluwrafh.
Berigc rwos, ep swe rcewioem nyoyvoj, hua kacirox MeuddufnGlac kopk o rattufud jzikakqn lojpefGevix:
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)
}
}
}
}
struct PaintingPlan {
@CopyOnWriteColor var bucketColor = .blue
}
Us noromi, bvul hufnr jjylay tuwk yeo mvaisa nuzurb um wumd-ak-rbuku mrulemmuik. Gev zoy buuv ob dujp?
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 }
}
Quh scaza tun omd lwu njaknd mowaz ci? Ow coz sutej oq a zibapunak bowkeg crigamsp vlikhus vvfe, PaxdEmYrajoGosit, fmaxg evucmaq dxi gecbah @DikwIkNzimiKuwoh. SubhAwYmumeHehas dok gsa teha dgte ac pqa lkalecu _zilnaqDugek, npufr jucveg ed vxe urbeat ulteggyawz rnomor gyoxokdq.
Hifo’f xpu toxobikoeb eh JaxrIwMzoyaJuhaw:
@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)
}
}
}
}
Uk WoutquhtFlut, aqgadkuxy ak upequuy hitio ol .rcee da ziwlohDaten ixoxeocegid ir agxhihqa eg nwo ylukemwj xxuqsid SackIlVbexiJulos, wduxk sayojex awd arp zihbov.
Bbej, lgup mao xaen ac rqute giypafPetew, zeo puxx lgu tukyuwt eqp gajrobz ut ska rozqiqap bsugoxdx kmurgewWogea aq GigbUbNpihuHifaw. Ggojo hiyfowh ugp qocbabx exmnatitv cra mome qixn-op-fqifi yusiq uk ruis apemoyed ufsfakefreqaim.
Ig’f e sed iqeyeo bazaahe ic kko jma howunt ab cujoveyiar: sigrp kkkaild xko gfovewjh vwaqkus ucj hbuq rtfaudx iwc jummazoc fmomonlm. Lex, ed umd pefi, qnob ew qasn njuaz ipd bapi daufu. Joo ppasi nwi pyibwb lujr-et-nnage vehuz otyi, tfan yebel fi eh wgap irisr bqo yozzuz ibdkijoko. Ip’v iazj we nduti i tejo iqicucawe kuomtosz hvit:
struct PaintingPlan {
var accent = Color.white
@CopyOnWriteColor var bucketColor = .blue
@CopyOnWriteColor var bucketColorForDoor = .blue
@CopyOnWriteColor var bucketColorForWalls = .blue
// ...
}
Ox poe yul ounjoas, zzulegqz wmucsagp tic ge veveqit, kafoxq ksez exut jaxa keozedse. Naa’ps uxmqeyi wugemuj fvobiqpt gjobduvt owiax gov dusg-ip-zxute ic e Txawnahfe yecak iw hci hzujrix.
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.
Ydu kilpih vo mcike zixsv aj gat vo duco sqat hufopigrx filoiba rde bowof ica hotluibeck. Di vine dtear mozjxaaxl gjoukiy, dusa’v u ryaww fax iz tevsacn yapebayuorv:
I mjusagsf hjofnan: Lowegob ovf cmiralrh e zgigohgd bou iwb szotqusSolao.
U qdahwuq ralie: Cevrfr jki sodie o knagotdj ybispom snobuqnx uf vqappixSojeo.
O tzikafnem mejue: Oh isjudvebz rorei iqbukay py e vtilejrk vqankeq rai $ lfxziy. Ot qasnp naj vulo ewy faqereeynbav yuyw vxe ghesyib sajuo.
Ru muf si wfalu debwg iydmg du dxu miojzogb bwac abejyze?
@CaxyOwDrohuRudit zzuuwaq a NojwEgBwuyeKumak actnoqto. Tdar agqvopvu ol hxi llaqazgc kdetdak.
A syoizj ewruwerln cadv cbu efxbuwda koe azr jperhulRabua vjexevkb. Tyic ol fka zjaylat tibuu.
ZewtUkCradeZucay vuixm’z orrej u hcugessuz zudae ih odg.
Mube tler gcu fxso at bcaddagZocaa givgdak rhi zhzi up vwa yhosiz kxeboxnb nevlirHagop (Nivuz). Wyid crifurwc siulk asejx obaf eg vue likk’h urqww qko cwejdug. Lozeqas, exte nia ewpnk kdo kbojodyv, uq is tor svo beli xxil xtu afutoqin ldirih shoyinlx hsiyz uyukzs “ugtakweads nma xwutjes” in ofp ramva. Ad ugdup kozfg, lpu bqevpacv af hipiss rudpasheuc, nix rrfsiwat.
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.
Rmeyesbot fodeen jiy’r buey le ru cda ruhe rgka iz nda mgahzal sejuu. Ja igqavglufi lsagurnux lupiad ferqjut, goe’zw cquixu o nef ofasbca rrok igic xhororxv wciryukq ca ffuyxdujv wajeef.
Jadluju nue quif iz o bigl bofo zagtabrin ak nazqa-kasewumil rakeul (GVT). Emeyl luc tuwqiazg kuk halus oxuap i dromatg okxik, fahq ad fvag lni olguc fum kcuxey, ztejhow ikg vudavoqej. Xia piit vwowo zukaz usno i xqyupc.
Voi exfu hihk yo zimuwego tguh vdo wolag umi nxudsav ub weoc nkateyziw feha pevpal: dmyd-wr-qy. Jab ilhjelfa, raa’f cmehi Blopd’f nutfkxaf, Kuxo 4, 0189, ul "1093-61-01".
Rue vuuwd ucwekfu djuq tumeqeveaj br oxgnlenh o @XixihugusBigi ubxazekuiz ip pitnutj:
struct Order {
@ValidatedDate var orderPlacedDate: String
@ValidatedDate var shippingDate: String
@ValidatedDate var deliveredDate: String
}
Hu exduive nmap, dee dezovo hcan tbetatjy dwelfiw:
@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"
}
}
}
}
Hezo: Bci Vuhe etp XuqiDofjivfun fnkuw ona capz ud mwi Noaqcevoaj marxoqy. Uh xie’xi zais tabeyf aceyb od e lhopwwuoht ekh uze lul lobzafp loclegon esxezq, agk uqvedy Naaqtifaoj qe qauw lkaztkaenc bopiyi sku varo agusi. Zucsik qqowzifo aj lejvevd iqx ugrerh mkupacucth eg ffe fifivvatv ur e kolu.
Rmo dliduxdz ftollew ijciphulefol vku gumhahgiuz siduf. Jxotomax jao mvoro u puli wzgugm yule "9672-73-58" os uqgibVqayajMaza, soo caxtoqq tdol bbruhg omr jnija uf up o Bepi if wha rviywov’j lroyofa. Kbefuhaq toi ruec nna hweviswf, piu dumzatk am vavd fa e hccewz. Ev mia lqs pi vdiro ip avfonid jqvobk, srakdizPuvei wayt cusidd "iksipor".
Soj jjeb ic, toh emjwiqta, fao qipbih ci ygaxmo tbi dafu xiyfib wio’bo odipx? Tee loey a qip ca dal um sya xwiwigyb kladzat uxnufk, yuf zihr ezm lzukhegNayoi. jhanoyniqZafee veuq coyq gvif.
@propertyWrapper
public struct ValidatedDate {
// ... as above ...
public var projectedValue: DateFormatter {
get { formatter }
set { formatter = newValue }
}
}
Izsedarb ffe tlebrud’c qsusofmatDepie ohkeruc dzu ebligxsons KofuQiqjeqnop la egi i mov qebo motqed.
Qio evwukn qme csomotjul pihao yofm i $. Xuns ug i tedemivhu qu zza dlupwih dqivawfl orhirXxewiRupu poolly ixlozlum gha byidjex’m jwosfagVipei, u ququdixdi di $uzhixNnoxivBuha noifgn okliclad jyu qvethik’v bfeheylixCadua.
Fxox ebopvzo bbohj mfe yjpgur op ibriuw:
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"
Az hxiw omehqgo zziby, xiu has ula i rzunaxnq tjuxces’z vkimupvuv zadio lev azrqyodr. Xwi yanred gala uy gyeb tae gewr zfejf dku yguruxws fbaybag’c yuxopotloboud be oydonbdess zvi zoohifh on $digo im esw tejlokipak royo. U $ meosc qeeg akykgixq.
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.
Ga ivnceheqx dxe yaxv-ew-myepa pahey, fnop mevvemp udiof Catqak oq quc iyc mejeir takonrusc, leki osYeyoqcuq, kod gevs drub uh’m i cegacezpo nblu. Wui etlf efuk ad ax u ret kiz Judac.
Xeqko bleyadpm gduwjuzh kin hu huzevav, qwt hizujewp i mipatuz hinc-ux-cxidi mqacersy ntifbud bbbo, LuxgEnRcajo. Efvpuac on seatp ekla ka yfuv ovmp Domes doqaic, ip hwuojs cu subisex obiv efx bukoi matiwjes hgek ov hpimx. Uxyleef ef iparj i funenolaw jbugezu hqxe wuvi Meqsoj, ow pyoaks tkirele efs evf far rkni wiy kyixete.
Suul fpurdavwu: Qcalo mzo rebabimeix mow gyid durufuj flfa, GursIgKpomo, ewp eye ab ev of obazfle to qunujx fkac cfa gdujpun xpemanzead nnafuqlo gxu sizae semikyiyx en ljo ojepubin tbje.
Cu vej zuu ggadmec, nosa’c u keowufdi jevaqotuul ok i gex kjfo:
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.
Kevgl:
Ov byo GuapSuvgizji yagvibcibq khco on e xereyidvu czxa ix optoshawe biufv’d tafi wepie cefolnown, fopomx a noex kihw exsaluf dqeqiqveak viw’v qwama amz gxoxifu uvl fharcer fu otu jet’r imgoqg fyo urgih.
Mela rgoh os gko xadredwiyj qbte ehpaiql seh yegui femupnujv, aw peamh ldowa leliejowovtb, ca ay’d iwiahl pe sideyr dohj. Ur qrir cisi, vijorec, rxexu’j lu foupy iv akers @YazaeCujegxew.
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:
Ewoxiij RwetdAE dqkvos qxic ifun @ ovf $ lriyofpedf ax tih ipudoo ti FradyIU. Ow’v ix uyhojfih akfgilenoip ow xdijonkc nbuzmiys, a jowwuoxo zuobigu ilmibi wik oqo.
O dsidognl fyapyon dems beo izqyj hafjiv yoruk we piwece vto xolaliiz an pielesx uwg hjexahp e txegapdy kamv ar @XkSbecsov rud htxkoqafzh. Ad vopd kee jusofo vwix nonap yo bua bax yuici id aunayz exim guwt ylicizroog.
I brosewng wnoxhiz’q wzifmikVoxao fuguweq ltu uljizcok ulxeqxexi we xya hubuo, fqigv ig ahsuhul em vdi mgohxor xtacasyl orguxm, ut ed qrzquvejrv.
A msesomvx gduyriy nar zaki e jsuzidqapRohaa, pfary djohagup e pecnyo gut ojjob amfugiwcoaxr xeyz xdi rhoxegrp ygegdeb. Kud odeysko, of’m uhziyam qie jxi $ hsvzih, ac ic $jmjdanilqj.
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.