WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: Debugging Out of Memory Help

Forums > SwiftUI

I am getting an out of memory error on a view I created to allow users to review a bunch of images.

I am sure the issue is that the upper limit is unbounded. I am using a LazyVGrid with each card being loaded as defined in a custom SwiftUI View. As the user scrolls thru the Grid, it will eventual crash the app.

The data is stored as .externalStorage in a SwiftData store. I have run it in the profiler and it is giving me low memory warnings, and as above, it will eventually crash.

I am pretty sure that Views are the issue, but I am not sure how to tell Swift to unload them when they are not on the screen and reload them when they are brought back into scroll view.

Has anyone seen a good tutorial or set of guidance to help me work my way thru this? Thanks

Here's the detail messge from XCode when I was running it in debug mode:

The app “Greet Keeper” has been killed by the operating system because it is using too much memory.
Domain: IDEDebugSessionErrorDomain
Code: 11
Recovery Suggestion: Use a memory profiling tool to track the process memory usage.
User Info: {
    DVTErrorCreationDateKey = "2024-04-07 18:48:43 +0000";
    IDERunOperationFailingWorker = DBGLLDBLauncher;
}
--
The app “Greet Keeper” has been killed by the operating system because it is using too much memory.
Domain: IDEDebugSessionErrorDomain
Code: 11
Recovery Suggestion: Use a memory profiling tool to track the process memory usage.
User Info: {
    IDERunOperationFailingWorker = DBGLLDBLauncher;
}
--

Event Metadata: com.apple.dt.IDERunOperationWorkerFinished : {
    "device_isCoreDevice" = 1;
    "device_model" = "iPhone15,3";
    "device_osBuild" = "17.4.1 (21E236)";
    "device_platform" = "com.apple.platform.iphoneos";
    "dvt_coredevice_version" = "355.24";
    "dvt_mobiledevice_version" = "1643.100.58";
    "launchSession_schemeCommand" = Run;
    "launchSession_state" = 2;
    "launchSession_targetArch" = arm64;
    "operation_duration_ms" = 276870;
    "operation_errorCode" = 11;
    "operation_errorDomain" = IDEDebugSessionErrorDomain;
    "operation_errorWorker" = DBGLLDBLauncher;
    "operation_name" = IDERunOperationWorkerGroup;
    "param_debugger_attachToExtensions" = 0;
    "param_debugger_attachToXPC" = 1;
    "param_debugger_type" = 3;
    "param_destination_isProxy" = 0;
    "param_destination_platform" = "com.apple.platform.iphoneos";
    "param_diag_MainThreadChecker_stopOnIssue" = 0;
    "param_diag_MallocStackLogging_enableDuringAttach" = 0;
    "param_diag_MallocStackLogging_enableForXPC" = 1;
    "param_diag_allowLocationSimulation" = 1;
    "param_diag_checker_tpc_enable" = 1;
    "param_diag_gpu_frameCapture_enable" = 0;
    "param_diag_gpu_shaderValidation_enable" = 0;
    "param_diag_gpu_validation_enable" = 0;
    "param_diag_memoryGraphOnResourceException" = 0;
    "param_diag_queueDebugging_enable" = 1;
    "param_diag_runtimeProfile_generate" = 0;
    "param_diag_sanitizer_asan_enable" = 0;
    "param_diag_sanitizer_tsan_enable" = 0;
    "param_diag_sanitizer_tsan_stopOnIssue" = 0;
    "param_diag_sanitizer_ubsan_stopOnIssue" = 0;
    "param_diag_showNonLocalizedStrings" = 0;
    "param_diag_viewDebugging_enabled" = 1;
    "param_diag_viewDebugging_insertDylibOnLaunch" = 1;
    "param_install_style" = 0;
    "param_launcher_UID" = 2;
    "param_launcher_allowDeviceSensorReplayData" = 0;
    "param_launcher_kind" = 0;
    "param_launcher_style" = 99;
    "param_launcher_substyle" = 8192;
    "param_runnable_appExtensionHostRunMode" = 0;
    "param_runnable_productType" = "com.apple.product-type.application";
    "param_structuredConsoleMode" = 1;
    "param_testing_launchedForTesting" = 0;
    "param_testing_suppressSimulatorApp" = 0;
    "param_testing_usingCLI" = 0;
    "sdk_canonicalName" = "iphoneos17.4";
    "sdk_osVersion" = "17.4";
    "sdk_variant" = iphoneos;
}
--

System Information

macOS Version 14.4.1 (Build 23E224)
Xcode 15.3 (22617) (Build 15E5202a)
Timestamp: 2024-04-07T14:48:43-04:00

   

See "Inspecting the debug memory graph" in: developer.apple.com/documentation/xcode/gathering-information-about-memory-use

Look for a retain cycle, which would be a circular connection in the Memory Graph.

   

Thank you @bobstern - So it appears to be that the individual card views are definitely showing a retain cycle (see picture) Now this is loading up all the images for a PDF print feature, so I would assume it needs to load them, I see the same retain cycle when I was loading the page to show them all.

Do I need to handle the memory myself for paging thru the LazyVGrid view? This is way outside of my comfortzone right now.

It can't be as easy as changing the image to a weak reference, or does that allow the LazyVGrid to release and reacquire?

   

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

It would be easier for us if you show us the code of your CardView and where it is called.

Perhaps adding a weak or unowned before the declaration of a specific variable could break the cycle.

https://www.hackingwithswift.com/articles/179/capture-lists-in-swift-whats-the-difference-between-weak-strong-and-unowned-references

As I'm not allowed to post a link to any other site and this software automatically transforms this to a link you need to type it yourself.

cocoacasts dot com/what-is-the-difference-between-weak-and-unowned-references-in-swift

   

There's a bit of layering here.. I have a FilteredList that lets you pick the type of LazyVGrid you'd like:

import SwiftData
import SwiftUI

struct FilteredList: View {
    @Environment(\.modelContext) private var modelContext

    @Query(sort: \EventType.eventName) private var eventTypes: [EventType]
    @Query(sort: [SortDescriptor(\Recipient.lastName), SortDescriptor(\Recipient.firstName)]) private var recipients: [Recipient]

    @Binding var navigationPath: NavigationPath

    private var listView: ListView

    init(searchText: String, listView: ListView, navigationPath: Binding<NavigationPath>) {
        self.listView = listView

        _eventTypes = Query(filter: #Predicate {
            if searchText.isEmpty {
                return true
            } else {
                return $0.eventName.localizedStandardContains(searchText)
            }
        }, sort: \EventType.eventName)
        _recipients = Query(filter: #Predicate {
            if searchText.isEmpty {
                return true
            } else {
                return $0.lastName.localizedStandardContains(searchText) || $0.firstName.localizedStandardContains(searchText)
            }
        }, sort: [SortDescriptor(\Recipient.lastName), SortDescriptor(\Recipient.firstName)])

        self._navigationPath = navigationPath
    }

    var body: some View {
        List {
            switch listView {
            case .events:
                ForEach(eventTypes, id: \.self) { eventType in
                    NavigationLink(destination:
                        ViewCardsView(eventType: eventType, navigationPath: $navigationPath)
                    ) {
                        Text("\(eventType.eventName)")
                            .foregroundColor(.green)
                    }
                }
                .onDelete(perform: deleteEvent)
            case .recipients:
                ForEach(recipients, id: \.self) { recipient in
                    NavigationLink(destination:
                        ViewEventsView(recipient: recipient, navigationPath: $navigationPath)
                    ) {
                        Text("\(recipient.fullName)")
                            .foregroundColor(.green)
                    }
                }
                .onDelete(perform: deleteRecipient)
            case .greetingCard:
                ForEach(eventTypes, id: \.self) { eventType in
                    NavigationLink(destination:
                        ViewGreetingCardsView(eventType: eventType, navigationPath: $navigationPath)
                    ) {
                        Text("\(eventType.eventName)")
                            .foregroundColor(.green)
                    }
                }
            }
        }
    }

    func deleteRecipient(offsets: IndexSet) {  
        for index in offsets {
            let recipient = recipients[index]
            modelContext.delete(recipient)
        }
        do {
            try modelContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }

    func deleteEvent(offsets: IndexSet) {  
        for index in offsets {
            let event = eventTypes[index]
            modelContext.delete(event)
        }
        do {
            try modelContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }
}

If we look at one of these (ViewCardsView) we see the following (this is including the PDF Print function, which is causing things to load twice when I load the view. I can't take it off of @MainActor for the renderer, but I am not sure how I can make that only happen when the user choose the shareSheet):

import os
import SwiftData
import SwiftUI

struct ViewCardsView: View {
    @Environment(\.modelContext) var modelContext
    @Environment(\.presentationMode) var presentationMode
    @Query(sort: [SortDescriptor(\Card.cardDate, order: .reverse)]) private var cards: [Card]

    private let blankCardFront = UIImage(named: "frontImage")
    private var gridLayout: [GridItem]
    private var iPhone = false
    private var eventType: EventType

    @Binding var navigationPath: NavigationPath

    // MARK: PDF Properties
    @State var PDFUrl: URL?
    @State var showShareSheet: Bool = false

    init(eventType: EventType, navigationPath: Binding<NavigationPath>) {
        self.eventType = eventType
        let eventTypeID = eventType.persistentModelID // Note this is required to help in Macro Expansion
        _cards = Query(
            filter: #Predicate {$0.eventType?.persistentModelID == eventTypeID },
            sort: [
                SortDescriptor(\Card.cardDate, order: .reverse),
            ]
        )
        if UIDevice.current.userInterfaceIdiom == .pad {
            self.gridLayout = [
                GridItem(.adaptive(minimum: 320), spacing: 20, alignment: .center)
            ]
        } else {
            iPhone = true
            self.gridLayout = [
                GridItem(.adaptive(minimum: 160), spacing: 10, alignment: .center)
            ]
        }
        self._navigationPath = navigationPath
    }

    var body: some View {
        VStack {
            ScrollView {
                LazyVGrid(columns: gridLayout, alignment: .center, spacing: 5) {
                    ForEach(cards) { card in
                        ScreenView(card: card, greetingCard: nil, isEventType: .events, navigationPath: $navigationPath)
                    }
                    .padding()
                }
            }
            .navigationTitle("Cards Sent")
            .navigationBarTitleDisplayMode(.inline)
            .navigationBarItems(trailing:
                                    HStack {
                ShareLink("Export PDF", item: render(viewsPerPage: 16))
            }
            )
        }
    }

    @MainActor func render(viewsPerPage: Int) -> URL {
        let cardsArray: [Card] = cards.map { $0 }
        let url = URL.documentsDirectory.appending(path: "\(eventType.eventName)-cards.pdf")
        var pageSize = CGRect(x: 0, y: 0, width: 612, height: 792)

        guard let pdfOutput = CGContext(url as CFURL, mediaBox: &pageSize, nil) else {
            return url
        }

        let numberOfPages = Int((cards.count + viewsPerPage - 1) / viewsPerPage)   // Round to number of pages
        let viewsPerRow = 4
        let rowsPerPage = 4
        let spacing = 10.0

        for pageIndex in 0..<numberOfPages {
            let startIndex = pageIndex * viewsPerPage
            let endIndex = min(startIndex + viewsPerPage, cardsArray.count)

            var currentX : Double = 0
            var currentY : Double = 0

            pdfOutput.beginPDFPage(nil)

            // Printer header - top 160 points of the page
            let renderTop = ImageRenderer(content: EventTypeView(eventType: eventType, isCards: false))
            renderTop.render { size, renderTop in
                // Go to Bottom Left of Page and then translate up to 160 points from the top
                pdfOutput.move(to: CGPoint(x: 0.0, y: 0.0))
                pdfOutput.translateBy(x: 0.0, y: 692)
                currentY += 692
                renderTop(pdfOutput)
            }
            pdfOutput.translateBy(x: spacing / 2, y: -160)

            for row in 0..<rowsPerPage {
                for col in 0..<viewsPerRow {
                    let index = startIndex + row * viewsPerRow + col
                    if index < endIndex, let event = cardsArray[safe: index] {
                        let renderBody = ImageRenderer(content: PrintView(card: event, greetingCard: nil, isEventType: .events))
                        renderBody.render { size, renderBody in
                            renderBody(pdfOutput)
                            pdfOutput.translateBy(x: size.width + 10, y: 0)
                            currentX += size.width + 10
                        }
                        //                        print("Body - currentX = \(currentX), currentY = \(currentY)")
                    }
                }
                pdfOutput.translateBy(x: -pageSize.width + 5, y: -144)
                currentY -= 153
                currentX = 0
            }

            // Print Footer - from bottom of page, up 40 points
            let renderBottom = ImageRenderer(content: FooterView(page: pageIndex + 1, pages: numberOfPages))
            pdfOutput.move(to: CGPoint(x: 0, y: 0))
            pdfOutput.translateBy(x: 0, y: 40)
            renderBottom.render { size, renderBottom in
                renderBottom(pdfOutput)
            }
            pdfOutput.endPDFPage()
        }
        pdfOutput.closePDF()
        return url
    }
}

ScreenView and PrintView are different layouts for the same Cards:

import SwiftData
import SwiftUI

struct ScreenView: View {
    private let blankCardFront = UIImage(named: "frontImage")
    private var iPhone = false
    private var card: Card?
    private var greetingCard: GreetingCard?
    var isEventType: ListView = .recipients
    @Binding var navigationPath: NavigationPath

    init(card: Card?, greetingCard: GreetingCard?, isEventType: ListView, navigationPath: Binding<NavigationPath>) {
        self.card = card
        self.greetingCard = greetingCard
        self.isEventType = isEventType
        self._navigationPath = navigationPath

        if UIDevice.current.userInterfaceIdiom == .pad {
            iPhone = false
        } else {
            iPhone = true
        }
    }

    var body: some View {
        HStack {
            VStack {
                if isEventType != .greetingCard {
                    AsyncImageView(imageData: card!.cardFront?.cardFront)
                } else {
                    AsyncImageView(imageData: greetingCard!.cardFront)
                }
                HStack {
                    VStack {
                        switch isEventType {
                        case .events:
                            Text("\(card?.recipient?.fullName ?? "Unknown")")
                                .foregroundColor(.green)
                            HStack {
                                Text("\(card?.cardDate ?? Date(), formatter: cardDateFormatter)")
                                    .fixedSize()
                                    .foregroundColor(.green)
                                MenuOverlayView(card: card!, greetingCard: greetingCard, isEventType: .events, navigationPath: $navigationPath)
                            }
                        case .recipients:
                            Text("\(card?.eventType?.eventName ?? "Unknown")")
                                .foregroundColor(.green)
                            HStack {
                                Text("\(card?.cardDate ?? Date(), formatter: cardDateFormatter)")
                                    .fixedSize()
                                    .foregroundColor(.green)
                                MenuOverlayView(card: card!, greetingCard: greetingCard, isEventType: .recipients, navigationPath: $navigationPath)
                            }
                        case .greetingCard:
                            HStack {
                                Text("\(greetingCard?.cardName ?? "") - Sent: \(greetingCard?.cardsCount() ?? 0)")
                                    .fixedSize(horizontal: false, vertical: true)
                                    .foregroundColor(.green)
                                MenuOverlayView(card: nil, greetingCard: greetingCard, isEventType: .greetingCard, navigationPath: $navigationPath)
                            }
                        }

                    }
                    .padding(iPhone ? 1 : 5)
                    .font(iPhone ? .caption : .title3)
                    .foregroundColor(.primary)
                }
            }
        }
        .padding()
        .frame(minWidth: iPhone ? 160 : 320, maxWidth: .infinity,
               minHeight: iPhone ? 160 : 320, maxHeight: .infinity)
        .background(Color(UIColor.systemGroupedBackground))
        .mask(RoundedRectangle(cornerRadius: 20))
        .shadow(radius: 5)
        .padding(iPhone ? 5: 10)
    }
}

Which ultimately loads an individual card via MenuOverlayView:

struct MenuOverlayView: View {
    @Environment(\.modelContext) var modelContext
    @Environment(\.presentationMode) var presentationMode

    @State var areYouSure: Bool = false
    @State var isEditActive: Bool = false
    @State var isCardActive: Bool = false
    @Binding var navigationPath: NavigationPath

    private let blankCardFront = UIImage(named: "frontImage")
    private var iPhone = false
    private var card: Card?
    private var greetingCard: GreetingCard?
    private var isEventType: ListView = .recipients

    init(card: Card?, greetingCard: GreetingCard?, isEventType: ListView, navigationPath: Binding<NavigationPath>) {
        if UIDevice.current.userInterfaceIdiom == .phone {
            iPhone = true
        }
        self.card = card
        self.greetingCard = greetingCard
        self.isEventType = isEventType
        self._navigationPath = navigationPath
    }

    var body: some View {
        HStack {
            Spacer()
            NavigationLink {
                if isEventType != .greetingCard {
                    EditCardView(card: Bindable(card!), navigationPath: $navigationPath)
                } else {
                    if isEventType == .greetingCard {
                        EditGreetingCardView(greetingCard: greetingCard)
                    }
                }
            } label: {
                Image(systemName: "square.and.pencil")
                    .foregroundColor(.green)
                    .font(iPhone ? .caption : .title3)
            }
            NavigationLink {
                if isEventType != .greetingCard {
                    CardView(cardImage: card!.cardUIImage(), cardTitle: "\(card!.cardDate.formatted(date: .abbreviated, time: .omitted))")
                } else {
                    CardView(cardImage: greetingCard!.cardUIImage(), cardTitle: "\(greetingCard?.cardName ?? "Missing EventType")")
                }
            } label: {
                Image(systemName: "doc.richtext")
                    .foregroundColor(.green)
                    .font(iPhone ? .caption : .title3)
            }
            Button(action: {
                areYouSure.toggle()
            }, label: {
                Image(systemName: "trash")
                    .foregroundColor(.red)
                    .font(iPhone ? .caption : .title3)
            })
            .confirmationDialog("Are you sure", isPresented: $areYouSure, titleVisibility: .visible) {
                Button("Yes", role:.destructive) {
                    withAnimation {
                        if isEventType != .greetingCard {
                            deleteCard(card: card!)
                        } else {
                            deleteGreetingCard(greetingCard: greetingCard!)
                        }
                    }
                }
                Button("No") {
                    withAnimation {
                        if isEventType != .greetingCard {
                            print("Cancelled delete of \(String(describing: card?.eventType)) \(String(describing: card?.cardDate))")
                        } else {
                            print("Cancelled delete of \(String(describing: greetingCard?.eventType)) \(String(describing: greetingCard?.cardName))")
                        }
                    }
                }
                .keyboardShortcut(.defaultAction)
            }
        }
    }

    private func deleteCard(card: Card) {
        let logger=Logger(subsystem: "com.theapapp.cardTracker", category: "MenuOverlayView.deleteCard")
        let taskContext = modelContext

        taskContext.delete(card)
        do {
            try taskContext.save()
        } catch {
            let nsError = error as NSError
            logger.log("Unresolved error \(nsError), \(nsError.userInfo)")
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }

    private func deleteGreetingCard(greetingCard: GreetingCard) {
        let logger=Logger(subsystem: "com.theapapp.cardTracker", category: "MenuOverlayView.deleteGreetingCard")
        let taskContext = modelContext

        taskContext.delete(greetingCard)
        do {
            try taskContext.save()
        } catch {
            let nsError = error as NSError
            logger.log("Unresolved error \(nsError), \(nsError.userInfo)")
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }
}

I hope this helps...

   

You haven’t revealed your definitions of the Card and GreetingCard classes, but I wonder whether that is the problem rather than the views. I hope GreetingCard is not a subclass of Card, because that is not supported in SwiftData models. That could be the significance of the Card object in the memory graph having a connection to itself.

Did you really write all this code before testing it? You may have to create a new Xcode project with only the lowest level view, test that, and then add more of your complexity a little at a time, testing after each addition.

In addition to the Apple article referenced above, see the tutorial on Instruments (not updated since 2018, unfortunately): help.apple.com/instruments/mac/10.0/#/dev022f987b

p.s. for @Hatsushira-san: I discovered that this site allows you to paste an entire URL if you merely delete the https prefix.

   

@bobstern Thanks, I will give it a try.

   

Thanks @bobstern - I have made my repo available publically on github - https://github.com/TheApApp/CardTrackerSwiftData - as this is more of a personal project.

A Card is an instance of a GreetingCard sent to a specific Recipient

import Foundation
import SwiftData
import SwiftUI
import UIKit

@Model
final class Card: CustomDebugStringConvertible {
    var debugDescription: String {
        "\(cardDate), \(eventType?.eventName ?? "Unknown event"), \(cardFront?.cardName ?? "No Card Name"), \(recipient?.fullName ?? "No Name")"
    }

    var cardDate: Date = Date()
    var eventType: EventType? 
    var cardFront: GreetingCard?
    var recipient: Recipient?

    // MARK: - Initializer
    init(cardDate: Date, eventType: EventType, cardFront: GreetingCard, recipient: Recipient) {
        self.cardDate = cardDate
        self.eventType = eventType
        self.cardFront = cardFront
        self.recipient = recipient
    }

    // MARK: - Intents
    /// A helper value that exposes the card as an Image either a blank image or the value of the image from the realted GreetingCard
    func cardUIImage() -> UIImage {
        if let greetingCard = cardFront {
            if let cardFront = greetingCard.cardFront {
                return UIImage(data: cardFront)!
            } else {
                return UIImage(named: "frontImage")!
            }
        } else {
            return UIImage(named: "frontImage")!
        }
    }

}

While a Greeting Card is a card

import Foundation
import SwiftData
import UIKit

@Model
final class GreetingCard: CustomDebugStringConvertible {
    var cardName: String = ""
    @Attribute(.externalStorage) var cardFront: Data?
    var eventType: EventType?
    var cardManufacturer: String = ""
    var cardURL: String = ""
    @Relationship(deleteRule: .cascade, inverse: \Card.cardFront) var cards: [Card]? = [Card]()

    var debugDescription: String {
        "\(cardName ), \(eventType?.eventName ?? "No Event Type"), \(cardManufacturer ), \(cardURL), Used - \(cardsCount()) "
    }

    init(cardName: String, cardFront: Data?, eventType: EventType? = nil, cardManufacturer: String, cardURL: String) {
        /// When creating a new Greeting Card, you have to have an image, all other properties are optional.
        self.cardName = cardName
        self.cardFront = cardFront ?? UIImage(named: "frontImage")!.pngData()!
        self.eventType = eventType
        self.cardManufacturer = cardManufacturer
        self.cardURL = cardURL
    }

    func cardUIImage() -> UIImage {
        let defaultImage: UIImage = UIImage(data: (cardFront)!) ?? UIImage(named: "frontImage")!
        return defaultImage
    }

    func cardsCount() -> Int {
        cards?.count ?? 0
    }
}

Think about it this way, you may buy a bunch of "thank you" greeting cards, and then you send 1 card to someone.

The project worked fine for years until I really started loading up the number of cards in the application. I first found an issue with my compression not working. But the print function is something I very recently added. I think another issue is I should build out my unit tests.

(sorry to take so long to respond - as this is just a nights/weekends project). I really do appreciate any pointers and will go back thru the instruments class from 2018.

   

So I ran instruments and had it crash out of the app, but Leaks can't be causing it now.. as this is what I am seeing Instruments View 112 bytes is not much of leak. But my heap allocation is going thru the roof, which I am sure is the issue with loading up all the images in the Greeting Card Picker.

Is it possible that SwiftUI and SwiftData are not releasing memory when I am switching between tabs? Allocations

   

When debugging "Out of Memory" errors, start by identifying potential memory leaks or inefficient memory usage in your code. Utilize tools like memory profilers and heap analyzers to pinpoint memory-hungry areas. Consider optimizing data structures, freeing unused memory, and minimizing memory allocations. Additionally, ensure proper resource management, handle exceptions gracefully, and consider scaling resources if necessary.

   

In the GreetingCard class, I suspect SwiftData may be misinterpreting the relationship with the Card class because the related property cards should not be optional and should not be initialized in its declaration. Try this:

@Relationship(deleteRule: .cascade, inverse: \Card.cardFront) var cards: [Card]

Beyond that, I’m out of my depth. I suggest you repost your question with SwiftData in the title so that SwiftData experts will see it.

To facilitate help from others, I suggest you rename the GreetingCard model as CardTemplate. You also might consider renaming the Card model as PersonalizedCard. (Assuming I understand your intention.)

   

Another idea: The heap, where you have exploding memory, contains the results of all your queries. If you don’t get more focussed advice from a SwiftData expert, you might try a trial-and-error approach of commenting-out all of your queries and assiging each of their vars to a constant value, then one-by-one reinstaing the queries until the heap size explodes again.

1      

Thanks @Bobstern, It has been a crasy week (I only get to work on this in my spare time), so I will take your advice to heart. The original version of this app I wrote in CoreData, but was much more limited, so I took a lot of liberties when I rewrote it for SwiftData, and I am just learning to understand SwiftData.

   

I expect it would be easier for you to stick with Core Data because the model editor defines relationships clearly and unambiguously, as opposed to wondering what SwiftData will infer from your model definition.

I'd love to learn from someone who understands both Core Data and SwiftData what the advantage would be of switching to SwiftData in your situation.

   

One of the values of SwiftData is automatic support of CloudKit, it is also why I have to make cards an Optional. I think I am going to close this thread and try and open up a new one related to SwiftData. I really want to make that work, as I want to learn SwiftData. I did pickup Paul's book, but it didn't really cover this level of details yet.

   

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.