4

in my ios swift application I have a database using Core Data. It has many entities, all entities have an integer field called syncStatus. it can be 0, 1, or 2.

On startup, I want to loop through ALL the entities that have syncStatus = 1 and change it to 0

Is there a way to do it without fetching each type alone and changing it?

So what I want is:

  • fetch ALL entities with syncStatus = 1
  • Loop through them and set syncStatus = 0

Currently I'm doing them one by one:

  • fetch UserEntities with syncStatus = 1
  • Loop through them and set syncStatus = 0
  • fetch countryEntities with syncStatus = 1
  • Loop through them and set syncStatus = 0
  • Do the same for every entity one by one

code:

let allUsers = context?.fetch(FetchRequest<UserEntity>().filtered(with: "syncStatus", equalTo: "1"))
let allCountries = context?.fetch(FetchRequest<CountryEntity>().filtered(with: "syncStatus", equalTo: "1"))
.
.
.

I'm just trying to find a generic approach, in case later we add another entity/table we don't have to come back to this code and add it here also.

Rohit Suthar
  • 3,528
  • 1
  • 42
  • 48
Y2theZ
  • 10,162
  • 38
  • 131
  • 200

3 Answers3

4

First of all, fetching all entries and filter them is much more expensive than applying a predicate.

I recommend to use a protocol extension with static methods. The benefit is that you can call the methods directly on the type

protocol SyncStatusResettable
{
    associatedtype Entity: NSManagedObject = Self

    var syncStatus : String {get set}
    static var entityName : String { get }
    static func resetSyncStatus(in context: NSManagedObjectContext) throws
}

extension SyncStatusResettable where Entity == Self
{
    static var entityName : String {
        return NSStringFromClass(self).components(separatedBy: ".").last!
    }

    static func resetSyncStatus(in context: NSManagedObjectContext) throws
    {
        let request = NSFetchRequest<Entity>(entityName: entityName)
        request.predicate = NSPredicate(format: "syncStatus == 1")
        let items = try context.fetch(request)
        for var item in items { item.syncStatus = "0" }
        if context.hasChanges { try context.save() }
    }

}

To use it, adopt SyncStatusResettable for all NSManagedObject subclasses and call

do {
    try UserEntity.resetSyncStatus(in: managedObjectContext)
    try CountryEntity.resetSyncStatus(in: managedObjectContext)
} catch { print(error) }

managedObjectContext is the current NSManagedObjectContext instance

vadian
  • 274,689
  • 30
  • 353
  • 361
1

NSManagedObjectModel allows you to enumerate through the entities it contains, and NSEntityDescription can give you properties for each entity. Once you have a reference to the model:

let entitiesWithSync = model.entities.filter {
    $0.properties.contains(where: { $0.name == "syncStatus" })
}

Will give you all of the relevant entities. You can then use this list of entities to drive your updates - note that using NSBatchUpdateRequest is faster if you're doing this on startup. You can create batch update requests using the entity descriptions obtained in the loop above.

jrturton
  • 118,105
  • 32
  • 252
  • 268
0

In the past I have looped through all the entitiesByName from the object model:

lazy var managedObjectModel: NSManagedObjectModel = {
    let modelUrl = NSBundle.mainBundle().URLForResource("SomeProject", withExtension: "momd")!
    return NSManagedObjectModel(contentsOf: modelUrl)
}

func updateAllData() {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext

    context.performAndWait {
        let allEntities = self.managedObjectModel.entitiesByName
        for (entity, items) in allEntities {
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
            ...
        }
    }
}
davidethell
  • 11,708
  • 6
  • 43
  • 63