Core Data - Fetch all entities using the same field - ios

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.

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

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.

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)
...
}
}
}

Related

How can we decide to choose `NSBatchUpdateRequest` or `NSManagedObject`, when updating 1 row?

I know that when updating multiple rows of data, NSBatchUpdateRequest is a recommended way, as it is faster and consumed less memory.
However, what if we are only updating 1 row? Should we choose to update using NSBatchUpdateRequest or NSManagedObject? Is there any rule-of-thumb to decide the choice?
Using NSManagedObject
func updateName(_ objectID: NSManagedObjectID, _ name: String) {
let coreDataStack = CoreDataStack.INSTANCE
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
do {
let nsTabInfo = try backgroundContext.existingObject(with: objectID) as! NSTabInfo
nsTabInfo.name = name
RepositoryUtils.saveContextIfPossible(backgroundContext)
} catch {
backgroundContext.rollback()
error_log(error)
}
}
}
Using NSBatchUpdateRequest
func updateName(_ objectID: NSManagedObjectID, _ name: String) {
let coreDataStack = CoreDataStack.INSTANCE
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
do {
let batchUpdateRequest = NSBatchUpdateRequest(entityName: "NSTabInfo")
batchUpdateRequest.predicate = NSPredicate(format: "self == %#", objectID)
batchUpdateRequest.propertiesToUpdate = ["name": name]
batchUpdateRequest.resultType = .updatedObjectIDsResultType
let batchUpdateResult = try backgroundContext.execute(batchUpdateRequest) as? NSBatchUpdateResult
guard let batchUpdateResult = batchUpdateResult else { return }
guard let managedObjectIDs = batchUpdateResult.result else { return }
let changes = [NSUpdatedObjectsKey : managedObjectIDs]
coreDataStack.mergeChanges(changes)
} catch {
backgroundContext.rollback()
error_log(error)
}
}
}
May I know how can we decide to choose NSBatchUpdateRequest or NSManagedObject, when updating 1 row?
For one/few objects (that can be easily pulled into memory without issues), it is usually easier/recommended to -
fetch NSManagedObject into NSManagedObjectContext.
Perform your updates.
Save the context.
This saves you from having to merge the same set of changes to all other contexts in the app (which may have reference to the object being updated).
This works because NSManagedObjectContext fires notifications on save() calls that can be automatically observed by other contexts (if needed).

CoreData crashes when fetching data in loop

I'm working with old xcdatamodel, it was created in xcode 7.3 (that's a crucial since I don't have the following issue on modern models). At the same time, this issue is not cured by simple changing Tool Version to Xcode 9.0 for my xcdatamodel.
I'm fetching data in for loop, in the thread of context I use for fetching data. When I try to fetch the entity that has already been fetched once, coreData crashes with EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP). Zombie tracking says [CFString copy]: message sent to deallocated instance 0x608000676b40.
This is the concept of what I do:
LegacyDatabaser.context.perform {
do {
for _ in 0..<10 {
let entity = try self.legacyDatabase.getEntity(forId:1)
print(entity.some_string_property) // <- crash here
}
} catch {
// ...
}
}
Here is the context initializer:
class LegacyDatabaser {
static var context: NSManagedObjectContext = LegacyDatabaseUtility.context
// ...
}
And
class LegacyDatabaseUtility {
fileprivate class var context: NSManagedObjectContext {
//let context = NSManagedObjectContext(concurrencyType:.privateQueueConcurrencyType)
//context.persistentStoreCoordinator = storeContainer.persistentStoreCoordinator
//return context // This didn't help also
return storeContainer.newBackgroundContext()
}
private static var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name:"MyDBName")
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
return container
}()
}
Here is the data fetcher:
func getEntity(forId id: NSNumber) throws -> MyEntity? {
// Create predicate
let predicate = NSPredicate(format:"id_local == %#", id)
// Find items in db
let results = try LegacyDatabaseUtility.find(predicate:predicate, sortDescriptors:nil, in:LegacyDatabaser.context)
// Check it
if results.count == 1 {
if let result = results.first as? MyEntity {
return result
} else {
return nil
}
} else {
return nil
}
}
And:
static func find(predicate:NSPredicate?, sortDescriptors:[NSSortDescriptor]?, in context: NSManagedObjectContext) throws -> [NSManagedObject] {
// Create a request
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName:"MyEntity")
// Apply predicate
if let predicate = predicate {
fetchRequest.predicate = predicate
}
// Apply sorting
if let sortDescriptors = sortDescriptors {
fetchRequest.sortDescriptors = sortDescriptors
}
// Run the fetchRequest
return try context.fetch(fetchRequest)
}
I don't address the context somewhere in a parallel, I'm sure I use the correct thread and context (I tested the main context also, the same result). What I'm doing wrong, why re-fetching the same entity fails?
If anyone catches any quirk crashes like one I described above, check the name of properties in your NSManagedObject. In my case, crashes were on call new_id property which is, I guess, kinda reserved name. Once I renamed this property, the crashes stopped.

MVC - NSFetchRequest in model file without UIKit

In my ViewController, I have a function for fetching all the 'headingNumbers' (an attribute of the entity 'Vocabulary'). I understood that this is not good MVC practice.
So I created a new swift file.
I did not import UIKit. Youtube Stanford - Developing iOS 9 Apps with Swift - 2, 22:53: "Never import UI KIT in a model file because the model is UI independent"
(https://www.youtube.com/watch?v=j50mPzDMWVQ)
However, now I get these messages like
"Use of unresolved identifier 'UIApplication'".
Which makes sense, as I did not import the UIKit.
The question is: how do I now execute a fetch request in my new swift file.
(As you probably now by now, I am a beginner)
import Foundation
import CoreData
class QueryData {
private var selectedHeadingNumber:String = "-123456789"
private var setOfHeadingNumbers:[String] = [String]()
func getHeadingNumbers2() -> [String] {
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
// Create Fetch Request
let fetchRequest = NSFetchRequest(entityName: "Vocabulary")
// Add Sort Descriptor
let sortDescriptor = NSSortDescriptor(key: "headingNumber", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Execute Fetch Request
do {
let result = try managedObjectContext.executeFetchRequest(fetchRequest)
for managedObject in result {
if let foundHeadingNumber = managedObject.valueForKey("headingNumber") {
if let result_number = foundHeadingNumber as? NSNumber
{
let result_string = "\(result_number)"
if !setOfHeadingNumbers.contains(result_string) {
print("Headingnumber: \(foundHeadingNumber) ")
setOfHeadingNumbers.append(result_string)
print("updated selectedHeadingNumber: ", selectedHeadingNumber)
selectedHeadingNumber = result_string
// set the default lessonnumber to the first lesson
if selectedHeadingNumber == "-123456789" {
selectedHeadingNumber = result_string
print("updated selectedHeadingNumber: ", selectedHeadingNumber)
}
}
}
//setOfHeadingNumbers.append(first)
}
}
} catch {
let fetchError = error as NSError
print(fetchError)
}
} // end of if statement
return setOfHeadingNumbers
} // end of func
} // end of class
What you actually need is NSManagedObjectContex. Pass it as an argument to method:
func getHeadingNumbers2(inContext context: NSManagedObjectContext) -> [String]
{
...
}
Or inject it as dependency in initializer
class QueryData
{
let context: NSManagedObjectContext
init(context: NSManagedObjectContext)
{
self.context = context
}
func getHeadingNumbers2() -> [String]
{
let fetchRequest = NSFetchRequest(entityName: "Vocabulary")
let result = try self.context.executeFetchRequest(fetchRequest)
...
}
}

core data get related entities

I have entities Application and Process, one application can have many processes, but one process can only have one application. I get one specific application entity:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let appfetchRequest = NSFetchRequest(entityName: "Application")
//appfetchRequest.returnsObjectsAsFaults = false
do{
let results = try managedContext.executeFetchRequest(appfetchRequest)
applications.addObjectsFromArray(results as! [NSMutableArray])
}catch let error as NSError{
print("Jakis blad z applications entity: \(error)")
}
let predicate:NSPredicate = NSPredicate(format: "name == %#", appTitle)
let application:NSArray = applications.filteredArrayUsingPredicate(predicate)
and in this I have relationship (named applicationprocesses).
I try to get an array with these entities in many ways, but no one work.
Actually, I have:
let processes = application.valueForKey("applicationprocesses").allObjects.first
print(processes?.valueForKey("applicationprocesses"))
And this give me:
Optional({(
<NSManagedObject: 0x7f945871b7a0> (entity: Application; id: 0xd000000000080002 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Application/p2> ; data: {
appcolor = "#3F3F3F";
appicon = bed;
applabel = Proces;
applabelplural = Procesy;
applicationprocesses = (
"0xd000000000140004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p5>",
"0xd000000000100004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p4>",
"0xd000000000180004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p6>",
"0xd0000000001c0004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p7>",
"0xd0000000000c0004 <x-coredata://00C2FE4A-143B-436E-B39B-A0A32C300B68/Process/p3>"
);
companyid = 392;
id = 1261;
name = "aplikacja 1";
processescount = 5;
})
)})
I need to display these processes in a UITablewView, so I need an array.
I will be grateful for any help.
The problem you are having is not the result of a bad line of code somewhere. It is actually working as it is supposed to. But you can make it a lot easier to work with NSManagedObject
Any fetch from your database results in [AnyObject]. If you leave it like it is, you are forced to use key-value coding which is a pain and very easy to mess up.
However it is very simple to create Classes from CD Entities and downcast the fetch result. This is an awesome feature of CoreData that unfortunately is not stressed enough.
link to gist
Your related entities might look like this:
Go to Menu -> Editor -> Create....
Select the entities you want to create a subclass for.
New files will show up in your project :
Now you can use code like this :
Insert objects
Notice the .... as? Company this is the downcast.
It allows you to access the attributes from the CD Entity like you would access any attributes from a Struct or Class.
func createACompany() {
// no appDel here. appDel and managedContext are best declared in the class scope. See gist for entire ViewController
guard let moc = managedContext else { return }
guard let company = NSEntityDescription.insertNewObjectForEntityForName("Company", inManagedObjectContext: moc) as? Company else {
return // guard is handy to "abort"
}
company.name = "Apple"
guard let bob = NSEntityDescription.insertNewObjectForEntityForName("Employee", inManagedObjectContext: moc) as? Employee else {
return
}
bob.name = "Bob"
bob.company = company
do {
try moc.save()
} catch {
print("core data save error : \(error)")
}
moc.refreshAllObjects()
}
Fetch objects
func fetchCompanies() -> [Company] {
guard let moc = managedContext else { return [] }
let request = NSFetchRequest(entityName: "Company")
request.returnsObjectsAsFaults = true
do {
let results = try moc.executeFetchRequest(request)
guard let companies = results as? [Company] else {
return []
}
return companies
}catch let error as NSError{
print("core data fetch error: \(error)")
return []
}
}
Get related objects
Look closely at the guard statement.
let employeeSet = company.employees -> unwrap of optional NSSet
let employees = employeeSet.allObjects -> get all objects in the NSSet
as? [Employee] -> downcast the result of allObjects to an Array
of Employee
func getEmployees(forCompany company : Company) -> [Employee] {
guard let employeeSet = company.employees, let employees = employeeSet.allObjects as? [Employee] else {
return []
}
return employees
}
Side note:
If you change your mind about the naming of the class and you change it everywhere. Don't forget the update it here too :
The class field has to be updated.
Similar to the InterfaceBuilder. If you change the class name it will not find it anymore.
I believe that you want to get all the managed objects (process) relate to the application object you already got. We usually use the "entity" word for same things as table in database.
I'm sorry I didn't have swift version for this line of code. I'm not sure about my swift skill
NSArray *processes = [[application fristObject] objectIDsForRelationshipNamed:#"applicationprocesses"]
Then use API to convert objectID to ManagedObject
(__kindof NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID;
The only problem is this API, (NSArray *)objectIDsForRelationshipNamed:(NSString *)key , only supported from version 8.3

Update CoreData Object

I'd like to update a CoreData Object.
Backgrund: I made an app which includes a UITableView. In the textLabel of the UITableViewCell is a name. In the detailTextLabel of this cell is a date which can be changed/updated. Now I'd like to change this date.
I wrote the following code:
var people = [NSManagedObject]()
func saveDate(date: NSDate) {
//1
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext:managedContext)
let person = people[dateIndexPath.row]
//3
person.setValue(date, forKey: "datum")
//4
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
//5
people.append(person)
tableView.reloadData()
}
Now, if I run this code:
The date is successfully updated but the cell in which the date has been updated is displayed 2 times. For example if I added 3 cells and changed the date in the 3rd cell, I now get 4 cells displayed and 2 of them have the same content/are duplicated.
Does someone knows how to solve this problem?
You're adding an additional object to your array each time. Your updated Person is already in the array and will display the new information when you reload your table data. To fix this, just take out this line:
people.append(person)
You're going to want to associate some kind of unique identifier attribute to your Person class. This allows to retrieve that same object later using it identifier. I would suggest using a UUID string value, called personID, identifier, or something similar.
You can override the awakeFromInsert method on your Person class like so:
// This is called when a new Person is inserted into a context
override func awakeFromInsert()
{
super.awakeFromInsert()
// Automatically assign a randomly-generated UUID
self.identifier = NSUUID().UUIDString
}
When you want to edit an existing Person, you want to retrieve it by the UUID. I suggest a class function like so (in the Person class):
class func personWithIdentifier(identifier: String, inContext context: NSManagedObjectContext) -> Person?
{
let fetchRequest = NSFetchRequest(entityName: "Person")
fetchRequest.predicate = NSPredicate(format: "identifier ==[c] %#", identifier)
fetchRequest.fetchLimit = 1 // Only want the first result
var error : NSError?
let results = context.executeFetchRequest(fetchRequest, error: &error) as [Person]
// Handle error here
return results.first?
}
This way you can use the following function:
let identifier = ...
let context = ...
var person = Person.personWithIdentifier(identifier, inContext: context)
if let person = person
{
// Edit the person
person.value = // change the values as you need
}
else
{
// Person does not exist!
person = // possibly create a person?
}

Resources