swiftswiftdata

What does the createBackingData() method do?


I noticed when I create a @Model, and go to Xcode's Refactor->Generate memberwise initializer, Xcode does this:

@Model
    class A {
        internal init(a: String = "", b: Date = Date(), _$backingData: any BackingData<SchemaV2.A> = A.createBackingData()) {
            self.a = a
            self.b = b
            self._$backingData = _$backingData
        }
        
        var a: String = ""
        var b: Date = Date()
    }

If I was doing it manually with my own initializer, I never used something like createBackingData(). What is its purpose?


Solution

  • I encourage you to see what the @Model macro actually expands to, by right clicking @Model and select "Expand Macro". There are more macros in its expansion - try expanding those too. Note that these underscore-prefixed things are all very subject to change in the future.

    One property that @Model generates is

    private var _$backingData: any SwiftData.BackingData<A> = A.createBackingData()
    

    This explains why "generate memberwise initializer" generated an extra parameter. That's just how it treats instance vars that are already initialised at its declaration. Try generating a memberwise initialiser for class Foo { private var x = 1 }, and see the same thing happen consistently.

    createBackingData is a static method declared in the PersistentModel protocol, which all @Models conform to. It is not documented, but we can roughly guess what it does from its name. It probably create something that actually stores the values of the persisted attributes of the model. BackingData is likely a wrapper around an NSManagedObject or something like that.

    Since _$backingData already has an initialiser expression, there is no need for your initialiser to initialise _$backingData at all.


    The values of the persisted attributes of the model don't actually live in the model class object. @Model first add a @_PersistedProperty macro to the stored properties you declare, then @_PersistedProperty turns them into computed properties with these accessors:

    @_PersistedProperty var name: String
    // expands to:
    var name: String
    {
        @storageRestrictions(accesses: _$backingData, initializes: _name)
        init(initialValue) {
            _$backingData.setValue(forKey: \.name, to: initialValue)
            _name = _SwiftDataNoType()
        }
        get {
            _$observationRegistrar.access(self, keyPath: \.name)
            return self.getValue(forKey: \.name)
        }
        set {
            _$observationRegistrar.withMutation(of: self, keyPath: \.name) {
                self.setValue(forKey: \.name, to: newValue)
            }
        }
    }
    
    @Transient
    private var _name: _SwiftDataNoType
    

    It only leaves an underscore-prefixed stored property that is basically just a marker to ensure that you have initialised everything in the initialiser. Without it, you don't have to initialise any of the model attributes in the initialiser, since they all turned into computed properties.