DEV Community

Anis Ali Khan
Anis Ali Khan

Posted on

Tutorial 33: Advanced Core Data - Relationships, Fetch Requests, and Performance πŸš€

Featuring: The Fart Button Universe Expanded πŸŒŒπŸ’¨


Welcome back, brave developer! πŸ§™β€β™‚οΈβœ¨ If you thought saving farts was magical, get ready β€” today, we're supercharging the Fart Button App with relationships, advanced fetch requests, and performance tips.

We're building not just a fart database β€” but a whole fart multiverse. 🌌πŸ’₯


πŸ“š What You'll Learn

  • How to model relationships in Core Data
  • How to write advanced FetchRequests
  • Performance tips to keep your app speedy πŸ’¨πŸ’¨

πŸ›  New Plan: Fart Categories

Our new app idea:

  • Different types of farts belong to Categories.
  • A Category (e.g., "Epic", "Squeaky", "Stealth Mode") has many Farts.
  • Each Fart belongs to one Category.

In Core Data lingo: One-to-Many Relationship.


πŸ’Ύ Updating the Data Model

Open Model.xcdatamodeld:

  1. Add a new Entity ➑️ Name it Category.
  2. Add attributes to Category:
    • name: String
  3. Update Fart:
    • Add a Relationship:
      • Name: category
      • Destination: Category
      • Type: To One
  4. In Category, add a Relationship:
    • Name: farts
    • Destination: Fart
    • Type: To Many
  5. Set inverse relationships:
    • category β¬…οΈβž‘οΈ farts

Congrats! You just taught Core Data that farts are social beings. πŸŽ“


πŸ–₯ Updated Fart Button UI

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        entity: Category.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \Category.name, ascending: true)]
    ) private var categories: FetchedResults<Category>

    var body: some View {
        NavigationView {
            List {
                ForEach(categories) { category in
                    Section(header: Text(category.name ?? "Unknown Category")) {
                        ForEach(category.fartsArray) { fart in
                            Text(fart.soundName ?? "Mystery Fart")
                        }
                    }
                }
            }
            .navigationTitle("πŸ’¨ Fart Archive πŸ’¨")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button(action: addSampleData) {
                        Label("Add Sample", systemImage: "plus")
                    }
                }
            }
        }
    }

    private func addSampleData() {
        let newCategory = Category(context: viewContext)
        newCategory.name = "Epic Farts"

        let fart1 = Fart(context: viewContext)
        fart1.soundName = "MegaBlast.mp3"
        fart1.timestamp = Date()
        fart1.category = newCategory

        let fart2 = Fart(context: viewContext)
        fart2.soundName = "AtomicFart.mp3"
        fart2.timestamp = Date()
        fart2.category = newCategory

        do {
            try viewContext.save()
        } catch {
            print("😱 Failed to save sample data: \(error.localizedDescription)")
        }
    }
}

extension Category {
    var fartsArray: [Fart] {
        let set = farts as? Set<Fart> ?? []
        return set.sorted { $0.timestamp ?? Date() < $1.timestamp ?? Date() }
    }
}
Enter fullscreen mode Exit fullscreen mode

Boom. You now have categorized farts. Elite developer status unlocked. πŸ†


🎯 Advanced Fetching: Only Epic Farts, Please

Want to get only farts from "Epic Farts" category? πŸ”₯

func fetchEpicFarts() -> [Fart] {
    let request: NSFetchRequest<Fart> = Fart.fetchRequest()
    request.predicate = NSPredicate(format: "category.name == %@", "Epic Farts")
    request.sortDescriptors = [NSSortDescriptor(keyPath: \Fart.timestamp, ascending: true)]

    do {
        return try viewContext.fetch(request)
    } catch {
        print("😱 Error fetching epic farts: \(error.localizedDescription)")
        return []
    }
}
Enter fullscreen mode Exit fullscreen mode

With this, your app filters the legendary explosions from the mundane ones. 🎺πŸ’₯


⚑️ Core Data Performance Tips

  • Batch Size: Large datasets? Use fetchBatchSize to avoid loading too much into memory.
request.fetchBatchSize = 20
Enter fullscreen mode Exit fullscreen mode
  • Faulting: Core Data lazy-loads objects β€” don’t worry if you see a "fault" β€” it's good for performance!

  • Background Contexts: For heavy operations (e.g., importing thousands of farts), use a background context so your UI doesn't freeze. 🧊

  • Indexes: Set Indexes on frequently queried fields like timestamp or soundName!


🧹 Cleaning Up: Deleting a Category

Want to delete a Category (and maybe all its farts)?

func deleteCategory(_ category: Category) {
    viewContext.delete(category)

    do {
        try viewContext.save()
    } catch {
        print("😱 Failed to delete category: \(error.localizedDescription)")
    }
}
Enter fullscreen mode Exit fullscreen mode

Easy peasy. (And surprisingly sad if you're attached to your farts.) 😒


πŸŽ‡ Final Challenge: Stretch Goals

  • Create Custom Category Icons (πŸ’₯ vs πŸ₯·)
  • Allow renaming Categories
  • Sort Categories based on number of farts (MOST FARTS WINS! πŸ†)
  • Add audio playback when tapping a fart 🎧

Congratulations, Core Data Wizard! πŸ§™β€β™€οΈπŸ’¨

You just leveled up your Fart Button App into a relational database masterpiece. Your ancestors would be proud.

Until next time β€” stay classy, and keep coding! πŸ’¨πŸ‘‘

Top comments (0)