Years ago, we remarked that the “at sign” ( @ ) — along with square brackets and ridiculously-long method names — was a defining characteristic of Objective-C. Then came Swift, and with it an end to these curious little 🥨-shaped glyphs …or so we thought.

At first, the function of @ was limited to Objective-C interoperability: @IBAction , @NSCopying , @UIApplication Main , and so on. But in time, Swift has continued to incorporate an ever-increasing number of @ -prefixed attributes.

We got our first glimpse of Swift 5.1 at WWDC 2019 by way of the SwiftUI announcement. And with each “mind-blowing” slide came a hitherto unknown attribute: @State , @Binding , @Environment Object …

We saw the future of Swift, and it was full of @ s.

We’ll dive into SwiftUI once it’s had a bit longer to bake.

But this week, we wanted to take a closer look at a key language feature for SwiftUI — something that will have arguably the biggest impact on the «je ne sais quoi» of Swift in version 5.1 and beyond: property wrappers

About Property Delegates Wrappers

Property wrappers were first pitched to the Swift forums back in March of 2019 — months before the public announcement of SwiftUI.

In his original pitch, Swift Core Team member Douglas Gregor described the feature (then called “property delegates”) as a user-accessible generalization of functionality currently provided by language features like the lazy keyword.

Laziness is a virtue in programming, and this kind of broadly useful functionality is characteristic of the thoughtful design decisions that make Swift such a nice language to work with. When a property is declared as lazy , it defers initialization of its default value until first access. For example, you could implement equivalent functionality yourself using a private property whose access is wrapped by a computed property, but a single lazy keyword makes all of that unnecessary.

Expand to lazily evaluate this code expression. struct Structure { // Deferred property initialization with lazy keyword lazy var deferred = … // Equivalent behavior without lazy keyword private var _deferred : Type ? var deferred : Type { get { if let value = _deferred { return value } let initial Value = … _deferred = initial Value return initial Value } set { _deferred = new Value } } }

SE-0258: Property Wrappers is currently in its third review (scheduled to end yesterday, at the time of publication), and it promises to open up functionality like lazy so that library authors can implement similar functionality themselves.

The proposal does an excellent job outlining its design and implementation. So rather than attempt to improve on this explanation, we thought it’d be interesting to look at some new patterns that property wrappers make possible — and, in the process, get a better handle on how we might use this feature in our projects.

So, for your consideration, here are four potential use cases for the new @property Wrapper attribute:

Constraining Values

SE-0258 offers plenty of practical examples, including @Lazy , @Atomic , @Thread Specific , and @Box . But the one we were most excited about was that of the @Constrained property wrapper.

Swift’s standard library offer correct, performant, floating-point number types, and you can have it in any size you want — so long as it’s 32 or 64 (or 80) bits long (to paraphrase Henry Ford).

If you wanted to implement a custom floating-point number type that enforced a valid range of values, this has been possible since Swift 3. However, doing so would require conformance to a labyrinth of protocol requirements.

ExpressibleByIntegerLiteral Float ExpressibleByFloatLiteral BinaryFloatingPoint FloatingPoint Float80 Double SignedNumeric SignedInteger Int AdditiveArithmetic Numeric FixedWidthInteger BinaryInteger Comparable Equatable UnsignedInteger UInt Credit: Flight School Guide to Swift Numbers

Pulling this off is no small feat, and often far too much work to justify for most use cases.

Fortunately, property wrappers offer a way to parameterize standard number types with significantly less effort.

Implementing a value clamping property wrapper

Consider the following Clamping structure. As a property wrapper (denoted by the @property Wrapper attribute), it automatically “clamps” out-of-bound values within the prescribed range.

@property Wrapper struct Clamping < Value : Comparable > { var value : Value let range : Closed Range < Value > init ( initial Value value : Value , _ range : Closed Range < Value > ) { precondition ( range . contains ( value )) self . value = value self . range = range } var wrapped Value : Value { get { value } set { value = min ( max ( range . lower Bound , new Value ), range . upper Bound ) } } }

You could use @Clamping to guarantee that a property modeling acidity in a chemical solution within the conventional range of 0 – 14.

struct Solution { @Clamping ( 0 ... 14 ) var p H : Double = 7.0 } let carbonic Acid = Solution ( p H : 4.68 ) // at 1 m M under standard conditions

Attempting to set pH values outside that range results in the closest boundary value (minimum or maximum) to be used instead.

let super Duper Acid = Solution ( p H : - 1 ) super Duper Acid . p H // 0

You can use property wrappers in implementations of other property wrappers. For example, this Unit Interval property wrapper delegates to @Clamping for constraining values between 0 and 1, inclusive. @property Wrapper struct Unit Interval < Value : Floating Point > { @Clamping ( 0 ... 1 ) var wrapped Value : Value = . zero init ( initial Value value : Value ) { self . wrapped Value = value } } For example, you might use the @Unit Interval property wrapper to define an RGB type that expresses red, green, blue intensities as percentages. struct RGB { @Unit Interval var red : Double @Unit Interval var green : Double @Unit Interval var blue : Double } let cornflower Blue = RGB ( red : 0.392 , green : 0.584 , blue : 0.929 )

Related Ideas

A @Positive / @Non Negative property wrapper that provides the unsigned guarantees to signed integer types.

/ property wrapper that provides the unsigned guarantees to signed integer types. A @Non Zero property wrapper that ensures that a number value is either greater than or less than 0 .

property wrapper that ensures that a number value is either greater than or less than . @Validated or @Whitelisted / @Blacklisted property wrappers that restrict which values can be assigned.

Transforming Values on Property Assignment

Accepting text input from users is a perennial headache among app developers. There are just so many things to keep track of, from the innocent banalities of string encoding to malicious attempts to inject code through a text field. But among the most subtle and frustrating problems that developers face when accepting user-generated content is dealing with leading and trailing whitespace.

A single leading space can invalidate URLs, confound date parsers, and sow chaos by way of off-by-one errors:

import Foundation URL ( string : " https://nshipster.com" ) // nil (!) ISO8601Date Formatter () . date ( from : " 2019-06-24" ) // nil (!) let words = " Hello, world!" . components ( separated By : . whitespaces ) words . count // 3 (!)

When it comes to user input, clients most often plead ignorance and just send everything as-is to the server. ¯\_(ツ)_/¯ .

While I’m not advocating for client apps to take on more of this responsibility, the situation presents another compelling use case for Swift property wrappers.

Foundation bridges the trimming Characters(in:) method to Swift strings, which provides, among other things, a convenient way to lop off whitespace from both the front or back of a String value. Calling this method each time you want to ensure data sanity is, however, less convenient. And if you’ve ever had to do this yourself to any appreciable extent, you’ve certainly wondered if there might be a better approach.

In your search for a less ad-hoc approach, you may have sought redemption through the will Set property callback… only to be disappointed that you can’t use this to change events already in motion.

struct Post { var title : String { will Set { title = new Value . trimming Characters ( in : . whitespaces And Newlines ) /* ⚠️ Attempting to store to property 'title' within its own will Set, which is about to be overwritten by the new value */ } } }

From there, you may have realized the potential of did Set as an avenue for greatness… only to realize later that did Set isn’t called during initial property assignment.

struct Post { var title : String { // 😓 Not called during initialization did Set { self . title = title . trimming Characters ( in : . whitespaces And Newlines ) } } }

Setting a property in its own did Set callback thankfully doesn’t cause the callback to fire again, so you don’t have to worry about accidental infinite self-recursion.

Undeterred, you may have tried any number of other approaches… ultimately finding none to yield an acceptable combination of ergonomics and performance characteristics.

If any of this rings true to your personal experience, you can rejoice in the knowledge that your search is over: property wrappers are the solution you’ve long been waiting for.

Implementing a Property Wrapper that Trims Whitespace from String Values

Consider the following Trimmed struct that trims whitespaces and newlines from incoming string values.

import Foundation @property Wrapper struct Trimmed { private(set) var value : String = "" var wrapped Value : String { get { value } set { value = new Value . trimming Characters ( in : . whitespaces And Newlines ) } } init ( initial Value : String ) { self . wrapped Value = initial Value } }

By marking each String property in the Post structure below with the @Trimmed annotation, any string value assigned to title or body — whether during initialization or via property access afterward — automatically has its leading or trailing whitespace removed.

struct Post { @Trimmed var title : String @Trimmed var body : String } let quine = Post ( title : " Swift Property Wrappers " , body : " … " ) quine . title // "Swift Property Wrappers" (no leading or trailing spaces!) quine . title = " @property Wrapper " quine . title // "@property Wrapper" (still no leading or trailing spaces!)

Related Ideas

A @Transformed property wrapper that applies ICU transforms to incoming string values.

property wrapper that applies ICU transforms to incoming string values. A @Normalized property wrapper that allows a String property to customize its normalization form.

property wrapper that allows a property to customize its normalization form. A @Quantized / @Rounded / @Truncated property that quantizes values to a particular degree (e.g. “round up to nearest ½”), but internally tracks precise intermediate values to prevent cascading rounding errors.

Changing Synthesized Equality and Comparison Semantics

This behavior is contingent on an implementation detail of synthesized protocol conformance and may change before this feature is finalized (though we hope this continues to work as described below).

In Swift, two String values are considered equal if they are canonically equivalent . By adopting these equality semantics, Swift strings behave more or less as you’d expect in most circumstances: if two strings comprise the same characters, it doesn’t matter whether any individual character is composed or precomposed — that is, “é” ( U+00E9 LATIN SMALL LETTER E WITH ACUTE ) is equal to “e” ( U+0065 LATIN SMALL LETTER E ) + “◌́” ( U+0301 COMBINING ACUTE ACCENT ).

But what if your particular use case calls for different equality semantics? Say you wanted a case insensitive notion of string equality?

There are plenty of ways you might go about implementing this today using existing language features:

You could take the lowercased() result anytime you do == comparison, but as with any manual process, this approach is error-prone.

result anytime you do comparison, but as with any manual process, this approach is error-prone. You could create a custom Case Insensitive type that wraps a String value, but you’d have to do a lot of additional work to make it as ergonomic and functional as the standard String type.

type that wraps a value, but you’d have to do a lot of additional work to make it as ergonomic and functional as the standard type. You could define a custom comparator function to wrap that comparison — heck, you could even define your own custom operator for it — but nothing comes close to an unqualified == between two operands.

None of these options are especially compelling, but thanks to property wrappers in Swift 5.1, we’ll finally have a solution that gives us what we’re looking for.

As with numbers, Swift takes a protocol-oriented approach that delegates string responsibilities across a constellation of narrowly-defined types. For the curious, here's a diagram showing the relationship between all of the string types in the Swift standard library: RangeReplaceableCollection TextOutputStreamable TextOutputStream Collection Sequence BidirectionalCollection String StringProtocol CustomDebugStringConvertible LosslessStringConvertible CustomStringConvertible ExpressibleByExtendedGraphemeClusterLiteral ExpressibleByUnicodeScalarLiteral ExpressibleByStringInterpolation ExpressibleByStringLiteral Comparable Hashable Equatable Credit: Flight School Guide to Swift Strings While you could create your own String -equivalent type, the documentation carries a strong directive to the contrary: Do not declare new conformances to StringProtocol. Only the String and Substring types in the standard library are valid conforming types.

Implementing a case-insensitive property wrapper

The Case Insensitive type below implements a property wrapper around a String / Sub String value. The type conforms to Comparable (and by extension, Equatable ) by way of the bridged NSString API case Insensitive Compare(_:) :

import Foundation @property Wrapper struct Case Insensitive < Value : String Protocol > { var wrapped Value : Value } extension Case Insensitive : Comparable { private func compare ( _ other : Case Insensitive ) -> Comparison Result { wrapped Value . case Insensitive Compare ( other . wrapped Value ) } static func == ( lhs : Case Insensitive , rhs : Case Insensitive ) -> Bool { lhs . compare ( rhs ) == . ordered Same } static func < ( lhs : Case Insensitive , rhs : Case Insensitive ) -> Bool { lhs . compare ( rhs ) == . ordered Ascending } static func > ( lhs : Case Insensitive , rhs : Case Insensitive ) -> Bool { lhs . compare ( rhs ) == . ordered Descending } }

Although the greater-than operator ( > ) can be derived automatically, we implement it here as a performance optimization to avoid unnecessary calls to the underlying case Insensitive Compare method.

Construct two string values that differ only by case, and they’ll return false for a standard equality check, but true when wrapped in a Case Insensitive object.

let hello : String = "hello" let HELLO : String = "HELLO" hello == HELLO // false Case Insensitive ( wrapped Value : hello ) == Case Insensitive ( wrapped Value : HELLO ) // true

So far, this approach is indistinguishable from the custom “wrapper type” approach described above. And this is normally where we’d start the long slog of implementing conformance to Expressible By String Literal and all of the other protocols to make Case Insensitive start to feel enough like String to feel good about our approach.

Property wrappers allow us to forego all of this busywork entirely:

struct Account : Equatable { @Case Insensitive var name : String init ( name : String ) { $ name = Case Insensitive ( wrapped Value : name ) } } var johnny = Account ( name : "johnny" ) let JOHNNY = Account ( name : "JOHNNY" ) let Jane = Account ( name : "Jane" ) johnny == JOHNNY // true johnny == Jane // false johnny . name == JOHNNY . name // false johnny . name = "Johnny" johnny . name // "Johnny"

Here, Account objects are checked for equality by a case-insensitive comparison on their name property value. However, when we go to get or set the name property, it’s a bona fide String value.

That’s neat, but what’s actually going on here?

Since Swift 4, the compiler automatically synthesizes Equatable conformance to types that adopt it in their declaration and whose stored properties are all themselves Equatable . Because of how compiler synthesis is implemented (at least currently), wrapped properties are evaluated through their wrapper rather than their underlying value:

// Synthesized by Swift Compiler extension Account : Equatable { static func == ( lhs : Account , rhs : Account ) -> Bool { lhs . $ name == rhs . $ name } }

Related Ideas

Defining @Compatibility Equivalence such that wrapped String properties with the values "①" and "1" are considered equal.

such that wrapped properties with the values and are considered equal. A @Approximate property wrapper to refine equality semantics for floating-point types (See also SE-0259)

property wrapper to refine equality semantics for floating-point types (See also SE-0259) A @Ranked property wrapper that takes a function that defines strict ordering for, say, enumerated values; this could allow, for example, the playing card rank .ace to be treated either low or high in different contexts.

Auditing Property Access

Business requirements may stipulate certain controls for who can access which records when or prescribe some form of accounting for changes over time.

Once again, this isn’t a task typically performed by, say, an iOS app; most business logic is defined on the server, and most client developers would like to keep it that way. But this is yet another use case too compelling to ignore as we start to look at the world through property-wrapped glasses.

Implementing a Property Value Versioning

The following Versioned structure functions as a property wrapper that intercepts incoming values and creates a timestamped record when each value is set.

import Foundation @property Wrapper struct Versioned < Value > { private var value : Value private(set) var timestamped Values : [( Date , Value )] = [] var wrapped Value : Value { get { value } set { defer { timestamped Values . append (( Date (), value )) } value = new Value } } init ( initial Value value : Value ) { self . wrapped Value = value } }

A hypothetical Expense Report class could wrap its state property with the @Versioned annotation to keep a paper trail for each action during processing.

class Expense Report { enum State { case submitted , received , approved , denied } @Versioned var state : State = . submitted }

Related Ideas

An @Audited property wrapper that logs each time a property is read or written to.

property wrapper that logs each time a property is read or written to. A @Decaying property wrapper that divides a set number value each time the value is read.

However, this particular example highlights a major limitation in the current implementation of property wrappers that stems from a longstanding deficiency of Swift generally: Properties can’t be marked as throws .

Without the ability to participate in error handling, property wrappers don’t provide a reasonable way to enforce and communicate policies. For example, if we wanted to extend the @Versioned property wrapper from before to prevent state from being set to .approved after previously being .denied , our best option is fatal Error() , which isn’t really suitable for real applications:

class Expense Report { @Versioned var state : State = . submitted { will Set { if new Value == . approved , $ state . timestamped Values . map { $0 . 1 } . contains ( . denied ) { fatal Error ( "J'Accuse!" ) } } } } var trip Expenses = Expense Report () trip Expenses . state = . denied trip Expenses . state = . approved // Fatal error: "J'Accuse!"

This is just one of several limitations that we’ve encountered so far with property wrappers. In the interest of creating a balanced perspective on this new feature, we’ll use the remainder of this article to enumerate them.

Limitations

Some of the shortcomings described below may be more a limitation of my current understanding or imagination than that of the proposed language feature itself. Please reach out with any corrections or suggestions you might have for reconciling them!

Properties Can’t Participate in Error Handling

Properties, unlike functions, can’t be marked as throws .

As it were, this is one of the few remaining distinctions between these two varieties of type members. Because properties have both a getter and a setter, it’s not entirely clear what the right design would be if we were to add error handling — especially when you consider how to play nice with syntax for other concerns like access control, custom getters / setters, and callbacks.

As described in the previous section, property wrappers have but two methods of recourse to deal with invalid values:

Ignoring them (silently) Crashing with fatal Error()

Neither of these options is particularly great, so we’d be very interested by any proposal that addresses this issue.

Wrapped Properties Can’t Be Aliased

Another limitation of the current proposal is that you can’t use instances of property wrappers as property wrappers.

Our Unit Interval example from before, which constrains wrapped values between 0 and 1 (inclusive), could be succinctly expressed as:

typealias Unit Interval = Clamping ( 0 ... 1 ) // ❌

However, this isn’t possible. Nor can you use instances of property wrappers to wrap properties.

let Unit Interval = Clamping ( 0 ... 1 ) struct Solution { @Unit Interval var p H : Double } // ❌

All this actually means in practice is more code replication than would be ideal. But given that this problem arises out of a fundamental distinction between types and values in the language, we can forgive a little duplication if it means avoiding the wrong abstraction.

Property Wrappers Are Difficult To Compose

Composition of property wrappers is not a commutative operation; the order in which you declare them affects how they’ll behave.

Consider the interplay between an attribute that performs string inflection and other string transforms. For example, a composition of property wrappers to automatically normalize the URL “slug” in a blog post will yield different results if spaces are replaced with dashes before or after whitespace is trimmed.

struct Post { … @Dasherized @Trimmed var slug : String }

But getting that to work in the first place is easier said than done! Attempting to compose two property wrappers that act on String values fails, because the outermost wrapper is acting on a value of the innermost wrapper type.

@property Wrapper struct Dasherized { private(set) var value : String = "" var wrapped Value : String { get { value } set { value = new Value . replacing Occurrences ( of : " " , with : "-" ) } } init ( initial Value : String ) { self . wrapped Value = initial Value } } struct Post { … @Dasherized @Trimmed var slug : String // ⚠️ An internal error occurred. }

There’s a way to get this to work, but it’s not entirely obvious or pleasant. Whether this is something that can be fixed in the implementation or merely redressed by documentation remains to be seen.

Property Wrappers Aren’t First-Class Dependent Types

A dependent type is a type defined by its value. For instance, “a pair of integers in which the latter is greater than the former” and “an array with a prime number of elements” are both dependent types because their type definition is contingent on its value.

Swift’s lack of support for dependent types in its type system means that any such guarantees must be enforced at run time.

The good news is that property wrappers get closer than any other language feature proposed thus far in filling this gap. However, they still aren’t a complete replacement for true value-dependent types.

You can’t use property wrappers to, for example, define a new type with a constraint on which values are possible.

typealias p H = @Clamping ( 0 ... 14 ) Double // ❌ func acidity ( of : Chemical ) -> p H {}

Nor can you use property wrappers to annotate key or value types in collections.

enum HTTP { struct Request { var headers : [ @Case Insensitive String : String ] // ❌ } }

These shortcomings are by no means deal-breakers; property wrappers are extremely useful and fill an important gap in the language.

It’ll be interesting to see whether the addition of property wrappers will create a renewed interest in bringing dependent types to Swift, or if they’ll be seen as “good enough”, obviating the need to formalize the concept further.

Property Wrappers Are Difficult to Document

Pop Quiz: Which property wrappers are made available by the SwiftUI framework?

Go ahead and visit the official SwiftUI docs and try to answer.

😬

In fairness, this failure isn’t unique to property wrappers.

If you were tasked with determining which protocol was responsible for a particular API in the standard library or which operators were supported for a pair of types based only on what was documented on developer.apple.com , you’re likely to start considering a mid-career pivot away from computers.

This lack of comprehensibility is made all the more dire by Swift’s increasing complexity.

Property Wrappers Further Complicate Swift

Swift is a much, much more complex language than Objective-C. That’s been true since Swift 1.0 and has only become more so over time.

The profusion of @ -prefixed features in Swift — whether it’s

@dynamic Member Lookup and @dynamic Callable from Swift 4, or @differentiable and @memberwise from Swift for Tensorflow — makes it increasingly difficult to come away with a reasonable understanding of Swift APIs based on documentation alone. In this respect, the introduction of @property Wrapper will be a force multiplier.

How will we make sense of it all? (That’s a genuine question, not a rhetorical one.)

Alright, let’s try to wrap this thing up —

Swift property wrappers allow library authors access to the kind of higher-level behavior previously reserved for language features. Their potential for improving safety and reducing complexity of code is immense, and we’ve only begun to scratch the surface of what’s possible.

Yet, for all of their promise, property wrappers and its cohort of language features debuted alongside SwiftUI introduce tremendous upheaval to Swift.

Or, as Nataliya Patsovska put it in a tweet:

iOS API design, short history: Objective C - describe all semantics in the name, the types don’t mean much

Swift 1 to 5 - name focuses on clarity and basic structs, enums, classes and protocols hold semantics

Swift 5.1 - @wrapped $path @yolo @nataliya_bg

Perhaps we’ll only know looking back whether Swift 5.1 marked a tipping point or a turning point for our beloved language.