Building a Fully Dynamic, Type-Safe Core Data + CloudKit Stack in Swift
generated by AI

Building a Fully Dynamic, Type-Safe Core Data + CloudKit Stack in Swift

As iOS developers, we all know the pain of managing Core Data. Every new project starts with repetitive boilerplate:

  • Creating .xcdatamodeld files
  • Writing CRUD code for each entity
  • Handling CloudKit sync manually
  • Using setValue(forKey:) and value(forKey:), which feels fragile

I kept asking myself: there has to be a better way.

Recently, I built a reusable solution that I want to share.

The Idea

I wanted to:

  1. Define entities entirely in Swift structs.
  2. Auto-generate Core Data entities dynamically at runtime.
  3. Wrap them in Swift-friendly, type-safe accessors.
  4. Make CloudKit sync nearly automatic.

Goal: A reusable, SOLID-compliant data layer for any entity, without repetitive boilerplate.

Flow Diagram

Swift Structs → Dynamic Core Data Stack → Type-Safe Wrapper → CloudKit Sync → App Data
        

Step 1: Define Entities in Swift

struct AttributeDefinition {
    let name: String
    let type: NSAttributeType
    let isOptional: Bool
}

struct EntityDefinition {
    let name: String
    let attributes: [AttributeDefinition]
}
        

Now I can create any entity dynamically without touching .xcdatamodeld.

Step 2: Build a Dynamic Core Data Stack

Here’s the heart of the solution:

final class DynamicCloudDataStack {
    private let container: NSPersistentCloudKitContainer
    
    init(entities: [EntityDefinition], cloudContainerId: String) {
        let model = NSManagedObjectModel()
        model.entities = entities.map { entityDef in
            let entity = NSEntityDescription()
            entity.name = entityDef.name
            entity.managedObjectClassName = NSManagedObject.self.description()
            
            entity.properties = entityDef.attributes.map { attr in
                let attribute = NSAttributeDescription()
                attribute.name = attr.name
                attribute.attributeType = attr.type
                attribute.isOptional = attr.isOptional
                return attribute
            }
            
            return entity
        }
        
        container = NSPersistentCloudKitContainer(name: "DynamicStack", managedObjectModel: model)
        container.persistentStoreDescriptions.first?.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: cloudContainerId)
        
        container.loadPersistentStores { storeDescription, error in
            if let error = error {
                fatalError("Failed to load store: \(error)")
            }
        }
        
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    }
    
    func create(entityName: String) -> NSManagedObject? {
        guard let entity = container.managedObjectModel.entitiesByName[entityName] else { return nil }
        return NSManagedObject(entity: entity, insertInto: container.viewContext)
    }
    
    func fetch(entityName: String, predicate: NSPredicate? = nil) throws -> [NSManagedObject] {
        let request = NSFetchRequest<NSManagedObject>(entityName: entityName)
        request.predicate = predicate
        return try container.viewContext.fetch(request)
    }
    
    func save() throws {
        if container.viewContext.hasChanges {
            try container.viewContext.save()
        }
    }
}
        

Diagram of Core Data Stack

[Dynamic Entity Struct] → [NSManagedObject] → [NSPersistentCloudKitContainer] → [CloudKit Sync]
        

Step 3: Type-Safe Swift Wrapper

No more setValue(forKey:):

@propertyWrapper
struct CoreDataField<Value> {
    let key: String
    let object: NSManagedObject
    
    var wrappedValue: Value {
        get { object.value(forKey: key) as! Value }
        set { object.setValue(newValue, forKey: key) }
    }
}
        

Usage:

let stack = DynamicCloudDataStack(
    entities: [
        EntityDefinition(name: "User",
                         attributes: [
                            AttributeDefinition(name: "username", type: .stringAttributeType, isOptional: false),
                            AttributeDefinition(name: "age", type: .integer16AttributeType, isOptional: false)
                         ])
    ],
    cloudContainerId: "iCloud.com.yourapp.container"
)

if let user = stack.create(entityName: "User") {
    user.setValue("Alice", forKey: "username")
    user.setValue(25, forKey: "age")
    try? stack.save()
}
        

Step 4: CloudKit Sync

With NSPersistentCloudKitContainer:

  • Automatic sync across devices
  • Persistent history tracking
  • Merge policies for smooth updates

Basically, once your stack is configured, CloudKit just works.

Step 5: Reusable & SOLID

You can reuse this stack for any entity, anywhere:

struct ProductAttributes {
    static let name = AttributeDefinition(name: "name", type: .stringAttributeType, isOptional: false)
    static let price = AttributeDefinition(name: "price", type: .doubleAttributeType, isOptional: false)
}

let productStack = DynamicCloudDataStack(
    entities: [EntityDefinition(name: "Product", attributes: [ProductAttributes.name, ProductAttributes.price])],
    cloudContainerId: "iCloud.com.yourapp.container"
)
        

✅ Fully dynamic ✅ Type-safe ✅ CloudKit-ready ✅ SOLID-compliant


Takeaways

  • Writing a dynamic Core Data stack eliminates boilerplate
  • Swift property wrappers make it safe and intuitive
  • CloudKit integration becomes almost effortless
  • Following SOLID keeps your code clean, reusable, maintainable

This approach lets me iterate quickly, test new entities on the fly, and focus on features—not boilerplate.


To view or add a comment, sign in

More articles by Md Jubel Hossain

  • Function Composition-Swift

    Once you have a collection of small, re-usable functions in your code, you can create new functions combining small…

  • Sorting complex data

    When you have an array that contains a custom data type, e.g.

  • Variadic functions — Swift

    Variadic function is a function which accepts a variable number of arguments. The function arguments are represented by…

  • Contiguous Array

    Swift provides two types of arrays, but almost always only one is used. You should be aware that the following two…

Explore content categories