How to subclass with SwiftData?

Hi, I want to subclass my model.

Here is my superclass:

import SwiftData

@Model
public class Control {
    
    @Attribute(.unique)
    public var frame: Frame
    
    init(frame: Frame) {
        self.frame = frame
    }
}

Here is my subclass:

import SwiftData
import CoreGraphics

class SliderControl: Control {
    
    let axis: Axis
    
    var value: CGFloat = 0.0
    
    init(axis: Axis, frame: Frame) {
        self.axis = axis
        super.init(frame: frame)
    }
    
    required public init(backingData: any BackingData<Control>, fromFetch: Bool = false) {
        // How to get `axis` and `value` from the backing data?
    }
}

I'm not sure how to use the backing data to re-create my object.

My goal is to have multiple controls with unique properties.

Post not yet marked as solved Up vote post of Heestand Down vote post of Heestand
2.4k views

Replies

I'm fairly certain that SwiftData simply doesn't support inheritance (at least not in beta 1).

I posted about that a few days ago, and I've also filed FB12336064 asking for the feature to be added.

Post not yet marked as solved Up vote reply of 610 Down vote reply of 610

From what I can see in the ”Meet SwiftData” and the code along video ”Build an app with SwiftData” WWDC2023 videos you should include ”import SwiftData” and ”@Model” in every file you want to use SwiftData and the associated macros like @Model in every swift file.

@stockholmelectronica I am currently experimenting with inheritance with SwiftData and I am not yet to the point to declare if it is or it is not supported, but I have verified that adding @Model to a subclass of a @Model class makes the compiler angry:

  • Redundant conformance of 'Subclass' to protocol 'PersistentModel'
  • Property 'backingData' with type 'any BackingData<Subclass>' cannot override a property with type 'any BackingData<ParentClass>'
  • Cannot override static method (for schemaMetadata(), included in the Macro)

With the caveat of not knowing if data will be persisted yet, what I have established so far is that it seems to be needed to add an implementation of the following method to the subclasses, but I am not sure that calling super.init will be enough:

    required init(backingData: any BackingData<Question>, fromFetch: Bool = false) {

        super.init(backingData: backingData)
    }

Update:

I am stepping away from trying to use SwiftData with inheritance: after creating a @Model parent class and a subclass with the required init above, I tried adding to the App Struct the contextModel initialisation as follows:

        .modelContainer(for: ParentClass.self)
        .modelContainer(for: Subclass.self)

but the app crashes at launch with Fatal error: Entity Subclass specifies Parentclass as its parent but no such entity was found in the provided types: [MyApp.Subcass].

I am not equipped to deal with this: Apple didn't make clear if it is supported, there's no documentation for this use case and it appear not to be how they want you to build things anyway, so it might be unwise to fight the system.


Update 2:

Okay, I realize I made a mistake setting up the modelContainer, the correct approach for multiple classes would be:

        .modelContainer(for: [ParentClass.self, Subclass.self])

But this doesn't change my conclusion, as now the app crashes at launch with the following error: Thread 1: "Property named 'uuid' in entity 'Subclass' conflicts with property inherited from parent entity 'Parentclass'"; I obviously declared uuid only in the parent class, which was kind of the point of using inheritance in the first place. The fact that all code samples had final class used for @Model should have been a clear enough signal, I'm afraid: feels this will fight me at every turn, time to stash and move on.

first of all, "chapeau" for the big work, honestly core data was a mess ;)

said so:

  1. cannot inherit: it's a shame .. if You use class you must allow it, otherwise us struct

  2. worse is that you cannot use a class marked with @model in other target where you don't have swiftData available, for example inside an XCTest

You got:

Thread 1: Fatal error: failed to find a currently active container for <MyClass>

SwiftData/ModelContainer.swift:144: Fatal error: failed to find a currently active container for <MyClass>

  • You can use a PersistentModel object in XCTest.

    You have to create a ModelContainer as a property of XCTestCase - see the default template code that is supporting the Item model. Seems you have to pretend with @MainActor. For the config enabled store in memory only.

    Use container.mainContext.insert() to add objects to the model.

Add a Comment

My workaround was to change the super class into a protocol. This doesn't help avoiding redundant code in the subclasses, but it will enable you to use the Protocol in a similar way you might use a Super class in your code while still being able to take advantage of SwiftData.

This is not idea and hopefully will get fixed by Apple.