Property Wrappers

Property wrappers are a Swift 5.1 language feature that gives you the ability to defined a wrapped property. I’ll examine some common property wrappers used in SwiftUI/Combine and also describe how to create your own property wrappers. Some common property wrapers include

  • @Published
  • @State
  • @Binding
  • @ObservedObject
  • @EnvironmentObject

@Published

This struct wraps a value and provides access to a publisher that is backed by an internal Subject. The definition is

/// Adds a `Publisher` to a property.
///
/// Properties annotated with `@Published` contain both the stored value and a publisher which sends any new values after the property value has been sent. New subscribers will receive the current value of the property first.
/// Note that the `@Published` property is class-constrained. Use it with properties of classes, not with non-class types like structures.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper public struct Published<Value> {

    /// Initialize the storage of the Published property as well as the corresponding `Publisher`.
    public init(initialValue: Value)

    /// A publisher for properties marked with the `@Published` attribute.
    public struct Publisher : Publisher {

        /// The kind of values published by this publisher.
        public typealias Output = Value

        /// The kind of errors this publisher might publish.
        ///
        /// Use `Never` if this `Publisher` does not publish errors.
        public typealias Failure = Never

        /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
        ///
        /// - SeeAlso: `subscribe(_:)`
        /// - Parameters:
        ///     - subscriber: The subscriber to attach to this `Publisher`.
        ///                   once attached it can begin to receive values.
        public func receive<S>(subscriber: S) where Value == S.Input, S : Subscriber, S.Failure == Published<Value>.Publisher.Failure
    }

    /// The property that can be accessed with the `$` syntax and allows access to the `Publisher`
    public var projectedValue: Published<Value>.Publisher { mutating get }
}

The struct contains an internal structure which is of type Publisher and it also contains a projectedValue property that returns the internal struct Published<Value>.Publisher

To define an @Published property just prefix the property defintion with @Published

@Published var players: [Player] = []

To get/set the value just reference the propery normally

let x: [Players] = players
players = [ ... ]

To obtain the internal Subject as a Publisher, use the $ syntax (which accesses the projectedValue).

let playersPublisher: Published<[Player]>.Publisher = self.viewModel.$players
        
        playersPublisher
            .print("self.frc.objectsPublisher")
            .sink{ players in
                players.forEach{ print("----> \($0)")}
            }
        .store(in: &cancellableSet)

I use the $ syntax to obtain the internal struct Publisher which I can then subscribe to via sink

We could imagine that the @Published property wrapper replaces this

final class ContentViewModel: ObservableObject {
// Would be replaced by @Published var testText: String = ""
    private let testTextPropertySubject = PassthroughSubject<String, Never>()
    
    var testText : String = "" {
        willSet {
            self.objectWillChange.send()
        }
        
        didSet {
            self.testTextPropertySubject.send(oldValue)
        }
    }
    
    var testTextPublisher: AnyPublisher<String, Never> {
        get { self.testTextPropertySubject.eraseToAnyPublisher() }
    }
}

I would think that SwiftUI would inspect all @ObservedObject wrappers and subscribe to their objectWillChange publishers. If any events are fired, evaluate all views that depend on any @Published properties of the observed object and refresh the views if necessary.

You may find that objectWillChange fires multiple times but no views are refreshed. This is normal SwiftUI behavior.

To access different parts of the property wrapper

@Published var test: Int = 0

let y: Int = self.test
let x: Published<Int> = self._test
let z: Published<Int>.Publisher = self.$test

The $ accesses the projectedValue of the property wrapper while _ retrieves the instance of the property wrapper itself.

@State

This property wrapper is intended to be used within SwiftUI views to track view state. It is defined as

/// A linked View property that instantiates a persistent state
/// value of type `Value`, allowing the view to read and update its
/// value.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper public struct State<Value> : DynamicProperty {

    /// Initialize with the provided initial value.
    public init(wrappedValue value: Value)

    /// Initialize with the provided initial value.
    public init(initialValue value: Value)

    /// The current state value.
    public var wrappedValue: Value { get nonmutating set }

    /// Produces the binding referencing this state value
    public var projectedValue: Binding<Value> { get }
}

To define a @State property just prefix the property defintion with @State

@State private var showPopover: Bool = false

It is suggested that @State properties are marked private, only the current view should have access to its @State properties

To get/set the value just reference the propery normally

.blur(isBlurred: self.showPopover, radius: 2.0) // conditionally blur
self.showPopover = true

To obtain the binding for the state Binding<Value>, use the $ syntax (which accesses the projectedValue).

let viewModel = PopoverViewModel<Player>(showOverlay: self.$showPopover)
@State private var type = 0

let x: Int = type
let y: Binding<Int> = $type
let z: State<Int> = _type

@Binding

This property wrapper is used to provide bi-directional binding to @State properties. If view A contains an @State property that it wants to provide read/write access to view B, view A would pass the @State property to view B as a Bindinb<Value> type. This type is accessed by applying the $ syntax to the @State property. The defintion is

/// A value and a means to mutate it.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper @dynamicMemberLookup public struct Binding<Value> {

    /// The transaction used for any changes to the binding's value.
    public var transaction: Transaction

    /// Initializes from functions to read and write the value.
    public init(get: @escaping () -> Value, set: @escaping (Value) -> Void)

    /// Initializes from functions to read and write the value.
    public init(get: @escaping () -> Value, set: @escaping (Value, Transaction) -> Void)

    /// Creates a binding with an immutable `value`.
    public static func constant(_ value: Value) -> Binding<Value>

    /// The value referenced by the binding. Assignments to the value
    /// will be immediately visible on reading (assuming the binding
    /// represents a mutable location), but the view changes they cause
    /// may be processed asynchronously to the assignment.
    public var wrappedValue: Value { get nonmutating set }

    /// The binding value, as "unwrapped" by accessing `$foo` on a `@Binding` property.
    public var projectedValue: Binding<Value> { get }

    /// Creates a new `Binding` focused on `Subject` using a key path.
    public subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> Binding<Subject> { get }
}

To define a @Binding property just prefix the property defintion with @Binding

@Binding var showOverlay: Bool

To get/set the value just reference the propery normally

.onTapGesture { self.viewModel.showOverlay = false }

setting the property will update the @State property that is associated with the binding (updating the @Binding property on view B will update the @State property on view A).

To access the projectedValue use the $ syntax

let x: Binding<Bool> = self.viewModel.$showOverlay
@Binding var addViewDisplayed: Bool

let y: Bool = self.viewModel.addViewDisplayed
let t: Binding<Bool> = self.viewModel.$addViewDisplayed

@ObservedObject

This wrapper is used to define properties of objects that adopt the ObservableObject protocol. It is mainly used to invalidate the view when the property changes. This is key to getting views to refresh from updates to @Published properties on objects wrapped by @ObservedObject. The definition is

/// A dynamic view property that subscribes to a `ObservableObject` automatically invalidating the view
/// when it changes.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper public struct ObservedObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject {

    /// A wrapper of the underlying `ObservableObject` that can create
    /// `Binding`s to its properties using dynamic member lookup.
    @dynamicMemberLookup public struct Wrapper {

        /// Creates a `Binding` to a value semantic property of a
        /// reference type.
        ///
        /// If `Value` is not value semantic, the updating behavior for
        /// any views that make use of the resulting `Binding` is
        /// unspecified.
        public subscript<Subject>(dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject>) -> Binding<Subject> { get }
    }

    public init(initialValue: ObjectType)

    public init(wrappedValue: ObjectType)

    public var wrappedValue: ObjectType

    public var projectedValue: ObservedObject<ObjectType>.Wrapper { get }
}

To define an @ObservedObject property just prefix the property definition with @ObservedObject

@ObservedObject private var viewModel: ContentViewModel

If the ContentViewModel adopts ObservableObject AND contains any @Published properties, whenever any @Published property changes, any views that reference the @Published properties will refresh themselves. The ObservableObject protocol is defined as

/// A type of object with a publisher that emits before the object has changed.
///
/// By default an `ObservableObject` will synthesize an `objectWillChange`
/// publisher that emits before any of its `@Published` properties changes:
///
///     class Contact: ObservableObject {
///         @Published var name: String
///         @Published var age: Int
///
///         init(name: String, age: Int) {
///             self.name = name
///             self.age = age
///         }
///
///         func haveBirthday() -> Int {
///             age += 1
///             return age
///         }
///     }
///
///     let john = Contact(name: "John Appleseed", age: 24)
///     john.objectWillChange.sink { _ in print("\(john.age) will change") }
///     print(john.haveBirthday())
///     // Prints "24 will change"
///     // Prints "25"
///
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ObservableObject : AnyObject {

    /// The type of publisher that emits before the object has changed.
    associatedtype ObjectWillChangePublisher : Publisher = ObservableObjectPublisher where Self.ObjectWillChangePublisher.Failure == Never

    /// A publisher that emits before the object has changed.
    var objectWillChange: Self.ObjectWillChangePublisher { get }
}

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ObservableObject where Self.ObjectWillChangePublisher == ObservableObjectPublisher {

    /// A publisher that emits before the object has changed.
    public var objectWillChange: ObservableObjectPublisher { get }
}

It uses an ObservableObjectPublisher

/// The default publisher of an `ObservableObject`.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
final public class ObservableObjectPublisher : Publisher {

    /// The kind of values published by this publisher.
    public typealias Output = Void

    /// The kind of errors this publisher might publish.
    ///
    /// Use `Never` if this `Publisher` does not publish errors.
    public typealias Failure = Never

    public init()

    /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
    ///
    /// - SeeAlso: `subscribe(_:)`
    /// - Parameters:
    ///     - subscriber: The subscriber to attach to this `Publisher`.
    ///                   once attached it can begin to receive values.
    final public func receive<S>(subscriber: S) where S : Subscriber, S.Failure == ObservableObjectPublisher.Failure, S.Input == ObservableObjectPublisher.Output

    final public func send()
}

Adopters of this protocol will raise an event BEFORE any of the @Published objects will change. This is possible because behind the scenes we have something like this:

class Observable<T>: ObservableObject, Identifiable {
    let id = UUID()
    let publisher = PassthroughSubject<T, Never>()
    var value: T {
        willSet { objectWillChange.send() } // provided automatically by adopting ObservableObject
        didSet { publisher.send(value) }
    }

    init(_ initValue: T) { self.value = initValue }
}

Internally the @Published property wrapper contains a subject. When the proerty is set the willSet will notify the class it is contained in via a call to objectWillChange.send(). It also updates its own internal subject in the didSet call.

final class ContentViewModel: ObservableObject {
  // list of players
    @Published var players: [Player] = []
}

The ContentViewModel has an objectWillChange property public var objectWillChange: ObservableObjectPublisher { get }. When the @Published var players willSet is called, the ContentViewModel.objectWillChange.send() function is called. This function will notifiy all dependent views to invalidate and refresh themselves. Internally the didSet property observer will update the value & subject within the @Published var players property.

Manually pushing updates

The property wrappers are a nice way to remove bolierplate code from you application. Given what we know, we could do all the updates by doing the following

final class ContentViewModel: ObservableObject {
    // Would be replaced by @Published var testText: String = ""
    private let testTextPropertySubject = PassthroughSubject<String, Never>()
    
    var testText : String = "" {
        willSet {
            self.objectWillChange.send()
        }
        
        didSet {
            self.testTextPropertySubject.send(oldValue)
        }
    }
    
    var testTextPublisher: AnyPublisher<String, Never> {
        get { self.testTextPropertySubject.eraseToAnyPublisher() }
    }
    
    init() {
        self.testText = "Phil"
    }
    
    func addPlayer(name: String) {
        
        player.name = name
        let msg = "Added \(player.name)"
        print(msg)
        
        self.testText = msg
    }
}

Simple view model with an exposed String property, a publisher and an addPlayer() function.

struct ContentView: View {
  @ObservedObject private var viewModel: ContentViewModel
  
  init() {
        self.viewModel.objectWillChange
            .print("PAK: objectWillChange")
            .sink{ _ in
                print("PAK: objectWillChange event")
            }
            .store(in: &cancellableSet)
        
        self.viewModel.testTextPublisher
            .print("PAK: testTextPublisher")
            .sink{
                print("PAK: testText event: \($0)")
            }
            .store(in: &cancellableSet)
  }

  var body: some View {
    Text("Test Text: \(self.viewModel.testText)")
  }
}

Using the objectWillChange publisher on the view model we can get notifications when the view model changes (i.e. any @Published properties) in response to changing the testText

@FetchRequest

Used to fetch data from CoreData.

/// Property wrapper to help Core Data clients drive views from the results of
/// a fetch request. The managed object context used by the fetch request and
/// its results is provided by @Environment(\.managedObjectContext).
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper public struct FetchRequest<Result> : DynamicProperty where Result : NSFetchRequestResult {

    /// The current collection of fetched results.
    public var wrappedValue: FetchedResults<Result> { get }

    /// Creates an instance by defining a fetch request based on the parameters.
    /// - Parameters:
    ///   - entity: The kind of modeled object to fetch.
    ///   - sortDescriptors: An array of sort descriptors defines the sort
    ///     order of the fetched results.
    ///   - predicate: An NSPredicate defines a filter for the fetched results.
    ///   - animation: The animation used for any changes to the fetched
    ///     results.
    public init(entity: NSEntityDescription, sortDescriptors: [NSSortDescriptor], predicate: NSPredicate? = nil, animation: Animation? = nil)

    /// Creates an instance from a fetch request.
    /// - Parameters:
    ///   - fetchRequest: The request used to produce the fetched results.
    ///   - animation: The animation used for any changes to the fetched
    ///     results.
    public init(fetchRequest: NSFetchRequest<Result>, animation: Animation? = nil)

    /// Creates an instance from a fetch request.
    /// - Parameters:
    ///   - fetchRequest: The request used to produce the fetched results.
    ///   - transaction: The transaction used for any changes to the fetched
    ///     results.
    public init(fetchRequest: NSFetchRequest<Result>, transaction: Transaction)

    /// Called immediately before the view's body() function is
    /// executed, after updating the values of any dynamic properties
    /// stored in `self`.
    public mutating func update()
}

https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-a-core-data-fetch-request-using-fetchrequest