Introduced in Swift 5.1, property wrappers quickly became one of the most heavily used features in all things Swift-related. All famous now SwiftUI framework could not have probably been the way it is now without its @State, @Binding, @StateObject and other black magic pieces you use every day. Or maybe you heard or even used one of the most popular object relational mapper frameworks (ORM) for a server side Swift’s Vapor called Fluent. They leave you with no choice to figure out how and when to use a dollar sign $ before a property neatly wrapped in yet another wrapper.
\ The truth is that property wrappers are not that hard to crack when you dive deep and try to use them on your own. In fact, you might probably have a bunch of places in your project where you can get things easier by bringing a wrapper to help. In this guide I’ll cover 5 basic use cases like that. But first, let’s refresh our memories on the subject itself.
What is a property wrapper?A property wrapper is a specially annotated type that encapsulates a piece of logic applied to any properties in general or properties constrained to some specific type you choose. It can be a struct, enum or class that declared with a @propertyWrapper attribute and has at least a wrappedValue as a mandatory property inside:
\
@propertyWrapper struct SomeWrapper\ Inside both a getter and a setter of the wrapped value property you can add any logic you need and intercept, transform or use values in basically any way you want. Then you can use the wrapper like this:
\
class SomeService { @SomeWrapper fancyNumber: Int init(number: Int) { self._number = SomeWrapper(wrappedValue: number) // use "_" to initialize a property wrapper } func doSmth() { print(fancyNumber) } }\ What’s nice about property wrappers is that you can add your own initializers, additional methods, properties, etc. that may have any functionality you need. One of this is an optional projectedValue property that can return even a wrapper itself or any other type. Now, remember: when you want to access something that belongs to the wrapper itself, but not to its value, you need to use a notorious dollar sign:
\
@propertyWrapper struct SomeWrapper\ Of course, all nonsensical examples above do not demonstrate true power of property wrappers enough, so let’s take a look at some of the most useful ways to make them shine.
1. StorageInspired by all the things Fluent can do, we can apply property wrappers to deal with UserDefaults instead of creating a separate service for it this way:
\
@propertyWrapper struct DefaultsStorage\ As you can see, the wrapper above can be used with anything Codable
\
@DefaultsStorage(key: "upload_date", defaultValue: Date()) var uploadDate: Date\ The implementation above can be tweaked and changed a lot depending on your needs. For example, you can make it truly versatile by accepting other types or allow to use suite-based UserDefaults instead of the from-the-box standard singleton.
\ Another thing is the data you save with it. If you want to store something securely, you may want to write a similar solution for Keychain.
By the way, Apple already has a similar wrapper called @AppStorage, but it was designed to be used with SwiftUI which you might not necessarily want.
2. LoggingIn cases where you need to monitor a specific value, the following property wrapper can be useful for you:
\
@propertyWrapper struct Logged\
@Logged(label: "Total Price") var price = Double(12.5)\ This property wrapper might become even more useful with additional analytics service injected. It can be something local or even remote. If using the latter, remember to filter at the call site of such service to avoid unnecessary calls.
3. ValidationWe often need to validate user’s input. There are so many possible input types, like email, a phone number, birth date and so on. All of these require specific logic piece applied to original data. You can solve this by using a property wrapper for every input:
\
@propertyWrapper struct ValidatedEmail\ Or you can even try one-size-fits-all solution like this:
\
@propertyWrapper struct Validated\ Similar to validating things, you can format dates with property wrappers by defining a set of prefixed formats you’d use inside the wrapper.
4. ClampedSometimes you need to limit a value in a certain range, for example having percentage spanning from 0 to 100 or a fraction value from 0 to 1 and so on. You can encapsulate this logic in a property wrapper, too:
\
@propertyWrapper struct Clamped\ You can use it like this:
\
struct ClampedExample { @Clamped(0...10) var number: Int init(number: Int) { self._number = Clamped(wrappedValue: number, 0...10) } func clampTest() { number = 20 print(example.number) // 10, because 20 is clamped to the upper bound 10 number = -5 print(example.number) // 0, because -5 is clamped to the lower bound 0 } }\
5. Base64EncodingAnother use case for property wrappers is to help us out with all things Codable. Custom encoding and decoding logic implementation can be safely put inside a wrapper if needed. You can also automatically encode a String into base64 with this simple solution:
\
@propertyWrapper struct Base64Encoding { private var value = "" var wrappedValue: String { get { Data(value.utf8).base64EncodedString() } set { value = newValue } } }\ Using the wrapper above, whenever you access the text transformed by it, it will always return a base64 encoded string.
\
Misuse of property wrappersHowever, when dealing with property wrappers, do not forget that they are same base Swift types we’ve known for years, but with added extra feature on top. One of many popular ideas on how to use a property wrapper back in the past was to try to thread-safe properties with it. You can find multiple implementations of @Atomic online, but it turned out that most of them are quite problematic. Even though fixes also can be found for it, I don’t recommend using @Atomic anymore, especially with actors now available to serve this purpose.
ConclusionI hope that you learned a lot or at least refreshed your knowledge of property wrappers in Swift. With this refresher take a look at your current projects: maybe you have some pieces that can leverage the power of this feature!
\
All Rights Reserved. Copyright , Central Coast Communications, Inc.