Swift - @propertyWrapper with Publisher

Property wrappers are one of Swift’s major new features enabling us to attach some specific behavior each time a property gets accessed or mutated.

It comes in very handy when one needs to encapsulate some common logic such as the user defaults which are often read and written to throughout the app lifecycle.

If you’ve been playing with SwiftUI, you may have already encountered some of these wrappers: @EnvironmentObject , @ObservableObject, or @Published.

Declaring a property wrapper is pretty straightforward since it only requires us to implement a wrappedValue property.

Let’s build a property wrapper that encapsulates some reactive behavior in which any passed-in value is immediately sent to a subject which is bound to a publisher in order for a stream of these values to be observed.

  1. Implement the wrapped value
  2. Send the new value to a subject
  3. Type-erase the subject with a publisher

We create our property wrapper (marked with the @ attribute), implement the required wrappedValue property and provide with an init() using a generic T type.

@propertyWrapper
class PublisherConvertible<T> {
    var wrappedValue: T
    
    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }
}

First setup is done.

Then, create a CurrentValueSubject with the wrappedValue as initial value.
A subject enables us to bring imperative code into the Combine framework.
Every time a value is set, it is sent over to the subject (which is marked as private since it should not be accessible from outside the wrapper).

@propertyWrapper
struct PublisherConvertible<T> {
    var wrappedValue: T {
        willSet {
            subject.send(newValue)
        }
    }

    private lazy var subject = CurrentValueSubject<T, Error>(wrappedValue)
}

So far so good however there’s one piece missing which is the publisher from which we’ll be observing the stream of values.
We could use a regular computed property however a property wrapper also has a built-in projectedValue which is quite useful here.

@propertyWrapper
class PublisherConvertible<T> {
    var wrappedValue: T {
        willSet {
            subject.send(newValue)
        }
    }
    
    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }
    
    private lazy var subject = CurrentValueSubject<T, Error>(wrappedValue)
    
    var projectedValue: AnyPublisher<T, Error> {
        return subject.eraseToAnyPublisher()
    }
}

Finally, let’s declare a variable using our newly created property wrapper.

@PublisherConvertible var name = "John"

$name
    .sink(receiveCompletion: { _ in }, receiveValue: { print($0) })
    .store(in: &subscriptions)
        
name = "Jack"
name = "Joe"

// John
// Jack
// Joe

The $ syntax represents the projectedValue property we just created, there is another way to access it using _name.projectedValue.

The @Published attribute, part of the Foundation framework, is actually doing what we implemented through our PublisherConvertible wrapper but it is nice to see how things are done under the hood in order to fully explore the potential of property wrappers.