IOS developer based in barcelona.

Property Wrappers: Cache and Validation

Property wrappers is a great feature introduced in Swift 5.1 (aka Property Delegates). The main idea behind property wrappers is a generic data structure which encapsulates some kind of “logic” that can be reused in other properties.

We will see two examples; the first one will be based on a property wrapper which can cache a value X seconds, and the second one will be a validation. If you’re interested in seeing more in depth you can take a look at the following link


Cache a value X seconds

Imagine that one of your use cases is in charge of retrieving some information from your backend and you have one scenario where you don’t want to repeat again the same request because the current information is fresh in memory. In this case, you can create something like the following property wrapper in which you can cache some data for a specific period of time.

import Foundation

@propertyWrapper
struct Cache < Value> {
    private var seconds: TimeInterval
    private var date: TimeInterval
    
    var value: Value?
    
    var wrappedValue: Value? {
        get {
            if (Date().timeIntervalSince1970 - self.date) > seconds {
                return nil
            } else {
                return value
            }
        }
        set {
            self.date = Date().timeIntervalSince1970
            value = newValue
        }
    }
    
    init(initialValue value: Value?, seconds: TimeInterval) {
        self.value = value
        self.date = Date().timeIntervalSince1970
        self.seconds = seconds
    }
}

Cache structure is a property wrapper where you can store a value only X seconds. This means, if you get this property before X seconds you will retrieve a value and if you access after X seconds you will receive nil. Easy right?

Let’s try our new property wrapper!

First of all, you can create a new structure with a property of type String to store the user name for X seconds. You will use the next annotation @Cache(seconds: 2)

struct CachedUser {
    @Cache(seconds: 2)
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

If you want to test it, you can do the following using your ContentView (yes, using SwiftUI)

struct ContentView: View {
    var cachedUser = CachedUser(name: "Alberto")
    
    var body: some View {
        VStack {
            Text("Property Wrapper Example")
            Button(action: {
                self.printCachedValue()
                self.printCachedValueAfter3Seconds()
            }) {
                Text("Tap Me Now")
            }
        }
    }
    
    func printCachedValue() {
        print("Name: \(String(describing: self.cachedUser.name))")
    }
    
    func printCachedValueAfter3Seconds() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            print("Name after 3 seconds: \(String(describing: self.cachedUser.name))")
        }
    }
}

You can tap the button that appears in the center of the screen and then you will see the following output in your console:

Name: Optional("Alberto")
Name after 3 seconds: nil

It works 🎉🎉🎉

Note: In fact, it's a simple example because the value isn’t deallocated. It would be great to assign nil automatically when the expiration takes place. This is just to show some useful implementations

Let’s take a look to another example


Validate

Imagine you need to validate some basic fields. For example: you need to guarantee that a String is longer than 4 characters. For this purpose you can create a property wrapper with an array of validations.

@propertyWrapper
struct Validate < Value> {
    var value: Value?
    var validations: [(Value?) -> Bool] = []
    
    var wrappedValue: Value? {
        get {
            let checkValidations = validations.reduce(true) { (result, function) in
                return result && function(value)
            }
            guard checkValidations else {
                return nil
            }
            return value
        }
        set {
            value = newValue
        }
    }
    
    init(initialValue value: Value?, validations: [(Value?) -> Bool]) {
        self.value = value
        self.validations = validations
    }
}

If the code passes the validation you will receive the value. Otherwise you will receive nil. To check the validation, you must pass and array of functions where the input is of type Value and the output a Bool. I’ve created two simple validations to inject in our new property wrapper:

  • UserNameIsAllowedValidator

  • UserNameLengthValidator

struct UserNameIsAllowedValidator {
    func execute(name: String?) -> Bool {
        guard let username = name,
            username != "Albert" else { return false }
        return true
    }
}
struct UserNameLengthValidator {
    func execute(name: String?) -> Bool {
        guard let username = name,
            username.count > 4 else { return false }
        return true
    }
}

It’s time to use these validators. That’s why I’ve created a structure called UserForm

struct User {
    let name: String
}

struct UserForm {
    enum UserError: Error, Equatable {
        case noValidUser
    }
    
    @Validate(initialValue: nil, validations: [UserNameLenghtValidator().execute,
                                               UserNameIsAllowedValidator().execute])
    private var nameField: String?
        
    var validate: Result {
        guard let name = nameField else {
            return .failure(.noValidUser)
        }
        return .success(.init(name: name))
    }
    
    init(name: String) {
        self.nameField = name
    }
}

You can see that I’ve added the @Validate with an initial value and all the validations that you would like to check (these are the two validations previously created). Also, you will have noticed that there is a validate property to return a valid user in case the validation is successful. In the case of failure you would receive an error (we use Result from the Swift Standard Library)

Let’s try it! We will do the same as the previous example. When a user taps a button the validation will be executed

struct ContentView: View {
    var userForm = UserForm(name: "Alberto")
    
    var body: some View {
        VStack {
            Text(“Validation")
            Button(action: 
                self.printValidation()
            ) {
                Text("Tap Me Now")
            }
        }
    }
    
    func printValidation() {
        switch userForm.validate {
        case .success:
            print("Success")
        case .failure(let error):
            print("Error \(error)")
        }
    }
}

The output is:

Success

Conclusion

This article was a small introduction to @propertyWrappers and all the magic you can do with them. If you are creating your own property wrappers I would like to know about what you are doing! You can find all the code in my repo https://github.com/MoralAlberto/PropertyWrappers


Thank you! 😃

Share Keychain Between App and Extension