Bobby Encoded
PostsAbout
PostsAbout

© 2026 Bobby Jose

← Back to Blog

From Core Data to SwiftData: A Backend Developer's Perspective

January 25, 2026 · 5 min read

Swift, iOS, SwiftData, Core Data, Mobile Development

I'm primarily a C# and Azure developer. When I started building my nutrition tracking iOS app as a side project, the persistence layer was my biggest struggle. Coming from Entity Framework and SQL Server, Core Data felt alien. Last weekend, I migrated everything to SwiftData, and it finally feels like home.

My Core Data Struggles

Let me be real - I never fully understood Core Data. I got it working, but I always felt like I was cargo-culting code from Stack Overflow. The terminology alone was confusing: managed object contexts, persistent store coordinators, fetch requests with string-based predicates. None of it mapped to anything I knew from the .NET world.

In C#, I'd write:

var meals = await _context.Meals
    .Where(m => m.UserId == userId && !m.IsDeleted)
    .OrderByDescending(m => m.ConsumedAt)
    .ToListAsync();

In Core Data, I was writing:

let request: NSFetchRequest<MealEntity> = MealEntity.fetchRequest()
request.predicate = NSPredicate(
    format: "userId == %@ AND isMarkedDeleted == NO",
    userId as CVarArg
)
request.sortDescriptors = [NSSortDescriptor(key: "consumedAt", ascending: false)]
return try viewContext.fetch(request)

That string-based predicate gave me anxiety. One typo and it crashes at runtime. No IntelliSense, no compiler help. It felt like writing SQL in strings - something I'd never do in C#.

Why SwiftData Clicked

When I saw SwiftData's #Predicate macro, something clicked. Look at this:

let predicate = #Predicate<Meal> { meal in
    meal.userId == userId && meal.isMarkedDeleted == false
}

That's a lambda! That's basically the same mental model as LINQ. The compiler checks it. I get autocomplete. If I typo a property name, it won't even build.

The @Model macro reminded me of Entity Framework's entity classes:

@Model
final class Meal {
    var id: UUID
    var userId: UUID
    var name: String
    var consumedAt: Date

    @Relationship(deleteRule: .cascade)
    var ingredients: [MealIngredient]?
}

Compare that to EF Core:

public class Meal
{
    public Guid Id { get; set; }
    public Guid UserId { get; set; }
    public string Name { get; set; }
    public DateTime ConsumedAt { get; set; }
    public List<MealIngredient> Ingredients { get; set; }
}

Almost the same thing! The mental overhead dropped significantly.

The Migration Weekend

I had 21 entities in Core Data. Meals, ingredients, glucose readings, recipes, health tracking - the works. I was dreading this migration for months.

Here's the thing: it took a weekend, but most of that time was me being overly cautious. Once I understood the pattern, each entity took maybe 15 minutes to convert.

The hardest part wasn't SwiftData - it was letting go of Core Data patterns I'd memorized but never understood. I kept looking for the equivalent of NSManagedObjectContext. Turns out, SwiftData's ModelContext is simpler and does what you'd expect.

What Surprised Me

No schema file. Core Data has this .xcdatamodeld file that's basically XML you edit through a GUI. SwiftData just uses your Swift code as the source of truth. Coming from EF Core's code-first approach, this made way more sense.

Relationships are just arrays. In Core Data, to-many relationships are NSSet and you need generated accessor methods. In SwiftData, it's just a Swift array. Append, remove, iterate - normal stuff.

The context is injected. SwiftUI automatically provides the model context through the environment. It's like dependency injection, which I'm very used to from .NET.

Things I Had to Work Around

Not everything maps perfectly:

No case-insensitive contains in predicates. I had to fetch and filter in memory for search. Annoying, but manageable.

Arrays of strings aren't native. I store tags as JSON strings with computed properties. Works fine, just not as elegant as I'd like.

Everything's on MainActor by default. SwiftData really wants you on the main thread. Coming from async/await in C#, I had to adjust my thinking about concurrency.

The Result

My data layer went from "code I'm afraid to touch" to "code I actually understand." That's huge for a side project where I might not look at this code for weeks.

The patterns finally feel familiar:

  • Models are just classes with decorators
  • Queries use lambdas with type safety
  • The context is injected where needed
  • Relationships are native collections

If you're a backend developer dipping into iOS, SwiftData is the persistence layer you've been waiting for. It won't feel exactly like Entity Framework, but it's close enough that your existing mental models transfer.

My Advice

  1. Learn SwiftData first if you're new to iOS. Don't bother with Core Data unless you're maintaining legacy code. SwiftData is the modern path.

  2. Think of @Model like an EF entity. The decorator approach is familiar if you've used data annotations.

  3. #Predicate is basically LINQ. Write it like a Where clause and you'll be fine.

  4. Trust the automatic saves. SwiftData handles persistence. You don't need to call save after every change.

Now I have 9 more repositories to migrate, but I'm actually looking forward to it. That's not something I ever said about Core Data.

← Previous

The Agentic AI Landscape: A C#/Azure Developer's Field Guide

Next →

Understanding MCP: The Protocol Revolutionizing AI Tool Integration