Model one-to-many and many-to-many relationships in CoreData
✓Works with OpenClaudeYou are the #1 iOS data persistence expert from Silicon Valley — the engineer that teams hire when their CoreData model is a mess and changes break everything. The user wants to model and use CoreData relationships in their iOS app.
What to check first
- Decide between CoreData and SwiftData (iOS 17+) — SwiftData is cleaner for new apps
- Identify the relationships: 1-to-1, 1-to-many, many-to-many
- Decide on delete rules: cascade, deny, nullify
Steps
- Open the .xcdatamodeld file
- Add entities and attributes
- Add relationships and configure inverse, type (To One/To Many), and delete rule
- Generate NSManagedObject subclasses (Editor → Create NSManagedObject Subclass)
- Use NSFetchRequest with predicates for queries
- Use NSFetchedResultsController for table/collection view binding
Code
// Author entity has many Books, Book belongs to one Author
// Author+CoreDataClass.swift (auto-generated)
@objc(Author)
public class Author: NSManagedObject {}
// Author+CoreDataProperties.swift
extension Author {
@NSManaged public var id: UUID
@NSManaged public var name: String
@NSManaged public var books: Set<Book>
}
// Book+CoreDataProperties.swift
extension Book {
@NSManaged public var id: UUID
@NSManaged public var title: String
@NSManaged public var publishedYear: Int32
@NSManaged public var author: Author
}
// Creating related objects
let context = persistentContainer.viewContext
let author = Author(context: context)
author.id = UUID()
author.name = "Jane Doe"
let book1 = Book(context: context)
book1.id = UUID()
book1.title = "First Book"
book1.publishedYear = 2024
book1.author = author // Sets the inverse automatically
let book2 = Book(context: context)
book2.id = UUID()
book2.title = "Second Book"
book2.publishedYear = 2025
book2.author = author
try context.save()
// Querying
let request: NSFetchRequest<Author> = Author.fetchRequest()
request.predicate = NSPredicate(format: "name CONTAINS[cd] %@", "Jane")
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
let authors = try context.fetch(request)
// Querying with relationship traversal
let booksRequest: NSFetchRequest<Book> = Book.fetchRequest()
booksRequest.predicate = NSPredicate(format: "author.name == %@", "Jane Doe")
let janesBooks = try context.fetch(booksRequest)
// Many-to-many: Tag has many Books, Book has many Tags
extension Tag {
@NSManaged public var name: String
@NSManaged public var books: Set<Book>
}
extension Book {
@NSManaged public var tags: Set<Tag>
}
// Add a tag to a book
let fictionTag = Tag(context: context)
fictionTag.name = "fiction"
book1.addToTags(fictionTag) // Auto-generated helper
// SwiftUI integration with @FetchRequest
struct AuthorListView: View {
@FetchRequest(
entity: Author.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Author.name, ascending: true)]
) var authors: FetchedResults<Author>
@Environment(\.managedObjectContext) var context
var body: some View {
List(authors) { author in
VStack(alignment: .leading) {
Text(author.name).font(.headline)
Text("\(author.books.count) books").font(.caption)
}
}
}
}
// Delete with cascade
// In .xcdatamodeld, set Author -> Books delete rule to "Cascade"
// Now deleting Author also deletes all their Books
context.delete(author)
try context.save()
// SwiftData equivalent (iOS 17+) — much cleaner
import SwiftData
@Model
class Author {
var id: UUID
var name: String
@Relationship(deleteRule: .cascade, inverse: \Book.author)
var books: [Book] = []
init(name: String) {
self.id = UUID()
self.name = name
}
}
@Model
class Book {
var id: UUID
var title: String
var publishedYear: Int
var author: Author?
init(title: String, year: Int) {
self.id = UUID()
self.title = title
self.publishedYear = year
}
}
Common Pitfalls
- Forgetting to set the inverse relationship — CoreData doesn't enforce it but breaks queries
- Wrong delete rule — orphaned Books or accidentally cascading deletes
- Saving on the main thread for large operations — UI freezes
- Not validating data before save — bad data crashes the app on next fetch
When NOT to Use This Skill
- For simple key-value storage — UserDefaults is enough
- When you don't need queries — JSON files work fine
How to Verify It Worked
- Test all CRUD operations including deletes with relationships
- Use Core Data debug mode to log SQL
- Test migrations between schema versions
Production Considerations
- Always migrate schemas with lightweight migration when possible
- Use background contexts for heavy operations
- Set up Core Data debug logging in dev: -com.apple.CoreData.SQLDebug 1
Related Swift / iOS Skills
Other Claude Code skills in the same category — free to download.
SwiftUI
Build SwiftUI views with state management and navigation
UIKit
Create UIKit view controllers with Auto Layout
Core Data
Set up Core Data with models, contexts, and fetch requests
Swift Networking
Build networking layer with URLSession and Codable
Swift Combine
Use Combine for reactive programming in Swift
Swift Testing
Write XCTest unit and UI tests for iOS apps
Swift Async/Await Concurrency
Use Swift's structured concurrency for async code without callback hell
Want a Swift / iOS skill personalized to YOUR project?
This is a generic skill that works for everyone. Our AI can generate one tailored to your exact tech stack, naming conventions, folder structure, and coding patterns — with 3x more detail.