After upgrading to XCode 16 app stopped working

Building an app, which worked fine until I updated to XCode 16.

The app parses data and saves it to SwiftData, and later that store is used for filtering in data.

If I create the store on iOS 18, the store created has issues with relationships, so filtering is not working (I hope that will be fixed in iOS 18.1), so I need to create the store on iOS 17.5.

But before saving the parsed objects to the SwiftData (store), I do the checking for dupes. And after Xcode 16 update that is not working anymore on iOS 17.5, (but this part is working fine under iOS 18):

Here is my object:

import Foundation
import SwiftData

@Model
class CaliberData: Identifiable {
    var id: UUID = UUID()
    var sizeHeights: [SizeObject]
    var featuresABCDIds: [Int]

    init(
        id: UUID,
        sizeHeights: [SizeObject],
        featuresABCDIds: [Int],
    ) {
        self.id = id
        self.sizeHeights = sizeHeights
        self.featuresABCDIds = featuresABCDIds
    }

extension CaliberData: Equatable {
    static func == (lhs: CaliberData, rhs: CaliberData) -> Bool {
        lhs.featuresABCDIds == rhs.featuresABCDIds
        && lhs.sizeHeights == rhs.sizeHeights
    }
}

SizeObject if needed:

@Model
class SizeObject: Identifiable, Equatable {
    @Attribute(.unique)
    var id: Float

    @Relationship(inverse: \CaliberData.sizeHeights)
    private(set) var caliberDataSizesX: [CaliberData]?

    init(_ size: Float) {
        self.id = size
    }
}

When I am doing the caliberData1 == CaliberData2 comparison to remove dupes before inserting to the SwifData modelContext I am getting exception, and Xcode shows it inside getter part of this expanded section under sizeHeights definition:

    @storageRestrictions(accesses: _$backingData, initializes: _sizeHeights)
    init(initialValue) {
        _$backingData.setValue(forKey: \.sizeHeights, to: initialValue)
        _sizeHeights = _SwiftDataNoType()
    }
    get {
        _$observationRegistrar.access(self, keyPath: \.sizeHeights)
        return self.getValue(forKey: \.sizeHeights)
    }
    set {
        _$observationRegistrar.withMutation(of: self, keyPath: \.sizeHeights) {
            self.setValue(forKey: \.sizeHeights, to: newValue)
        }
    }
}

Following is the stacktrace:

#1	0x00000001cc168928 in ___lldb_unnamed_symbol4385 ()
#2	0x00000001cc163f34 in ___lldb_unnamed_symbol4354 ()
#3	0x00000001cc165fc4 in ___lldb_unnamed_symbol4369 ()
#4	0x00000001cc169dd4 in ___lldb_unnamed_symbol4403 ()
#5	0x00000001cc123854 in SwiftData.PersistentModel.getValue<τ_0_0, τ_0_1 where τ_1_0: SwiftData.RelationshipCollection, τ_1_1 == τ_1_0.PersistentElement>(forKey: Swift.KeyPath<τ_0_0, τ_1_0>) -> τ_1_0 ()
#6	0x0000000105eda3b0 in CaliberData.sizeHeights.getter at /var/folders/5y/vv3v98ms7jj3z4kj0f3d6f9h0000gq/T/swift-generated-sources/@__swiftmacro_11CaliberDataC11sizeHeights18_PersistedPropertyfMa_.swift:9
#7	0x0000000105ef1a84 in static CaliberData.== infix(_:_:) at ...

My assumption is that "it" thinks that object is already inside the modelContext, when it's actually not yet inserted there and should act like regular class (by regular, I mean - like not marked with @Model macro).

How can I do objects comparison on iOS 17.5 with Xcode 16 (before they are inserted in modelContext)? Any other options?

As a workaround until better solution is found this works:

import Foundation
import SwiftData

@Model
class CaliberData: Identifiable {
    var id: UUID = UUID()
    var sizeHeights: [SizeObject]
    @Transient var sizeHeightsCopy: [SizeObject] // <-here
    var featuresABCDIds: [Int]

    init(
        id: UUID,
        sizeHeights: [SizeObject],
        featuresABCDIds: [Int],
    ) {
        self.id = id
        self.sizeHeights = sizeHeights
        self.featuresABCDIds = featuresABCDIds

        sizeHeightsCopy = sizeHeights // <-here
    }

extension CaliberData: Equatable {
    static func == (lhs: CaliberData, rhs: CaliberData) -> Bool {
        lhs.featuresABCDIds == rhs.featuresABCDIds
        && lhs.sizeHeightsCopy == rhs.sizeHeightsCopy // <-here
    }
}

Your model looks a bit strange to me. Why do you have a many to many relationship between the two models and why do you have an id property of type Float? Is it correct to assume that the float is the actual value (size) here and if so why is then the value also the identifier and why does each value need to be unique?

User should be able to filter CaliberData by specifying size range.

                #Predicate {
                    caliberData in
                    caliberData.sizeHeights.contains {
                        sizeObject in
                        sizeObject.id >= minSizeHeight && sizeObject.id <= maxSizeHeight
                    }
                }

You are right, that Float is actual size value, but had to wrap it into a custom class as a workaround due to some SwiftData limitation regarding using filtering predicates on value (or relationships (or both)).

So using the actual value as 'id' to make it conform to needed Identifiable protocol made sense to save some space.

After upgrading to XCode 16 app stopped working
 
 
Q