Due to the enhancement in my project, I have done the heavyweight migration, now after heavy weight migration, CoreData is unable to store data it always return empty.
I have a class Function
class func insert(object: [String: Any], entityName: String) -> NSManagedObject? {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: entityName, in: context)!
let managedObject = NSManagedObject(entity: entity, insertInto: context)
let keys = Array(entity.attributesByName.keys)
print("Keys:\(keys)\nobject:\(object)")
for (key, element) in object {
if keys.contains(key) {
managedObject.setValue(element, forKey: key)
}
}
do {
print(managedObject.isUpdated)
try context.save()
print(managedObject.isUpdated)
print("Success insert")
} catch {
print("Error in Insert: \(error)")
}
return managedObject
}
so I pass my Object with the key value pair in form of Dictionary, I pass the entityName to this function so it never return nil in managedObject While it always show false on print(managedObject.isUpdated) on both lines. Here is my console logs for saving data.
Keys:["contactIdentifier", "timeAndDate", "receiverId", "createdAt", "updatedAt", "i-----------g", "se---d", "duration", "senderId", "number", "name", "isFromiOS", "opponentsIds", "m-------d"]
object:["sessionID": "E1ECAB27-D312", "message": "--------", "contactIdentifier": "------- ", "I----p": "1", "contactNumbers": "------------,-----------", "V-----l": "1", "co------pe": "2", "createdAt": 2022-10-27 11:58:18 +0000, "opponentsIds": "---, ----"]
false
false
Success insert
updateCallInformation:<CallHistory2: 0x2835e99f0> (entity: CallHistory2; id: 0xa46b7d533bd5d14b <x-coredata://BAF5AFD6-8567-4485-8938-E13D3BD7B314/CallHistory2/p1>; data: {
contactIdentifier = "MDanishPK ";
createdAt = "2022-10-27 11:58:18 +0000";
duration = nil;
incomingOutgoing = nil;
isFromiOS = 0;
missedReceived = nil;
name = nil;
number = nil;
opponentsIds = nil;
receiverId = nil;
senderId = nil;
sessionID = "E1ECAB27-D312-4402-91D3-87AEBAC1A709";
timeAndDate = nil;
updatedAt = nil;
})
In console ---- I added intentially here for the privacy. There are some proper values and keys here.
If I revert my migration and goto the older branch using git than its working fine and save data properly. But I need heavy weight migration which I achieved using this article Kindly help me out and guide me what I have to do To save managedObject? and what I am doing wrong in saving managedObject? I changed all the table name but still its not saving data. On every launch I also lost the data.
Update Question
After Insert data I am using this fetch method, It always return me 0 against the array of [NSManagedObject]?
class func fetchList(entityName: String, predicate: NSPredicate? = nil, sortBy: [(key: String, ascending: Bool)]? = nil) ->
[NSManagedObject]? {
let context = CoreDataStack.context
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: entityName)
fetchRequest.predicate = predicate
if let sortedBy = sortBy {
var sortDescriptors = [NSSortDescriptor]()
for sortElement in sortedBy {
let sortDescriptor = NSSortDescriptor(key: sortElement.key, ascending: sortElement.ascending)
sortDescriptors.append(sortDescriptor)
}
fetchRequest.sortDescriptors = sortDescriptors
}
do {
let results = try context.fetch(fetchRequest)
return results
} catch {
print("Could not fetch: \(error)")
}
return nil
}
Related
I am relearning the swift core data basics using the swift playground.
I am writing out core data long-hand to write a simple playground app where
One Company has many Employees
I am constantly getting an error:
error: Execution was interrupted, reason: signal SIGABRT.
When it comes to saving a relationship between a company and a single employee, but I'm not sure why its being raised.
My code now follows:
// Swift playground code
import CoreData
class NotificationListener: NSObject {
#objc func handleDidSaveNotification(_ notification:Notification) {
print("did save notification received: \(notification)")
}
}
let listener = NotificationListener()
NotificationCenter.default.addObserver(listener, selector: #selector(NotificationListener.handleDidSaveNotification(_:)), name: NSNotification.Name.NSManagedObjectContextDidSave, object: nil)
// Define managed object
let model = NSManagedObjectModel()
//: [Entities]
let companyEntity = NSEntityDescription()
companyEntity.name = "Company"
let employeeEntity = NSEntityDescription()
employeeEntity.name = "Employee"
employeeEntity.indexes = []
//: [Attributes]
let companyNameAttribute = NSAttributeDescription()
companyNameAttribute.name = "name"
companyNameAttribute.attributeType = NSAttributeType.stringAttributeType
companyNameAttribute.isOptional = false
let countryAttribute = NSAttributeDescription()
countryAttribute.name = "country"
countryAttribute.attributeType = NSAttributeType.stringAttributeType
countryAttribute.isOptional = false
let employeeNameAttribute = NSAttributeDescription()
employeeNameAttribute.name = "name"
employeeNameAttribute.attributeType = NSAttributeType.stringAttributeType
employeeNameAttribute.isOptional = false
let ageAttribute = NSAttributeDescription()
ageAttribute.name = "age"
ageAttribute.attributeType = NSAttributeType.integer16AttributeType
ageAttribute.isOptional = false
// Relationships
let companyRelationship = NSRelationshipDescription()
let employeeRelationship = NSRelationshipDescription()
companyRelationship.name = "company"
companyRelationship.destinationEntity = companyEntity
companyRelationship.minCount = 0
companyRelationship.maxCount = 0
companyRelationship.deleteRule = NSDeleteRule.cascadeDeleteRule
companyRelationship.inverseRelationship = employeeRelationship
employeeRelationship.name = "employees"
employeeRelationship.destinationEntity = employeeEntity
employeeRelationship.minCount = 0
employeeRelationship.maxCount = 1
employeeRelationship.deleteRule = NSDeleteRule.nullifyDeleteRule
employeeRelationship.inverseRelationship = companyRelationship
companyEntity.properties = [companyNameAttribute, countryAttribute, employeeRelationship]
employeeEntity.properties = [employeeNameAttribute, ageAttribute, companyRelationship]
model.entities = [companyEntity, employeeEntity]
// Create persistent store coordinator
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel:model)
do {
try persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
} catch {
print("error creating persistentStoreCoordinator: \(error)")
}
let managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator
// Companies
let companyABC = NSEntityDescription.insertNewObject(forEntityName: "Company", into: managedObjectContext)
companyABC.setValue("ABC Ltd", forKeyPath: "name")
companyABC.setValue("United States", forKeyPath: "country")
let companyDelta = NSEntityDescription.insertNewObject(forEntityName: "Company", into: managedObjectContext)
companyDelta.setValue("Delta", forKeyPath: "name")
companyDelta.setValue("Canada", forKeyPath: "country")
let tom = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: managedObjectContext)
tom.setValue("Tom", forKey: "name")
tom.setValue(22, forKey: "age")
tom.setValue(companyABC, forKey: "company") // <<-- Throws error
let sarah = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: managedObjectContext)
sarah.setValue("Sarah", forKey: "name")
sarah.setValue(41, forKey: "age")
sarah.setValue(companyDelta, forKey: "company") // <<-- Throws error
func save(context: NSManagedObjectContext) {
// Save context
do {
try context.save()
} catch {
print("error saving context: \(error)")
}
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Company")
var results: [NSManagedObject] = []
do {
results = try managedObjectContext.fetch(fetchRequest)
print ("\n#\(results.count) records found\n")
} catch {
print("error executing fetch request: \(error)")
}
print("results: \(results)")
}
save(context: managedObjectContext)
The issue comes when it attempts to save one employee:
let tom = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: managedObjectContext)
tom.setValue("Tom", forKey: "name")
tom.setValue(22, forKey: "age")
tom.setValue(companyABC, forKey: "company")
The error is raised when attempting to set the companyABC as the relationship for the tom object.
The objective is to make Tom and employee of companyABC
I believe the relationship has been created properly.
But I am unsure of what is causing the error.
Thus, my query is: How can I resolve this error?
With thanks
...
tom.setValue(Set([companyABC]), forKey: "company")
...
sarah.setValue(Set([companyDelta]), forKey: "company")
...
Because in this case, if you'd generate a class model with XCode from a CoreData Graph, it would have generate the objects where the property company is a (NS)Set. I think it should be written somewhere in the CoreData documentation, but set is unfortunately a too common word. Edit, found it.
From the doc:
The Destination pop-up menu defines what object (or objects) is returned when the relationship is accessed in code. If the relationship is defined as to-one, a single object (or nil if the relationship can be optional) is returned. If the relationship is defined as to-many, a set is returned (or again, nil if the relationship can be optional).
I'm having trouble creating with CloudKit References. Data is being saved into CloudKit but its not referencing its parent (list). Don't know what i'm doing wrong, any help would be much appreciated!
Saving Method
var list: CKRecord?
var item: CKRecord?
#objc func save() {
let name = nameTextField.text! as NSString
//Fetch Private Database
let privateDatabase = CKContainer.default().privateCloudDatabase
if item == nil {
//Create Record
item = CKRecord(recordType: RecordTypeItems)
//Initialization Reference
guard let recordID = list?.recordID else { return }
let listReference = CKRecord.Reference(recordID: recordID, action: .deleteSelf)
item?.setObject(listReference, forKey: "list")
}
item?.setObject(name, forKey: "name")
//Save Record
privateDatabase.save(item!) { (record, error) in
DispatchQueue.main.sync {
self.processResponse(record: record, error: error)
}
}
}
Fetch Method
var list: CKRecord!
var items = [CKRecord]()
private func fetchItems() {
//Fetch Private Database
let privateDatabase = CKContainer.default().privateCloudDatabase
//Initialize Query
guard let recordID = list?.recordID else { return }
let reference = CKRecord.Reference(recordID: recordID, action: .deleteSelf)
let query = CKQuery(recordType: RecordTypeItems, predicate: NSPredicate(format: "list == %#", [reference]))
//Configure Query
query.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
//Peform Query
privateDatabase.perform(query, inZoneWith: nil) { (records, error) in
DispatchQueue.main.sync {
self.processResponseForQuery(records: records, error: error)
}
}
}
Where you are creating your query to retrieve items referencing the list, should the list reference in the predicate format string be inside an array? If you create the item's reference like item?.setObject(listReference, forKey: "list"), CloudKit will infer the list field to be a single CKRecord.Reference, so the query would be:
let query = CKQuery(recordType: RecordTypeItems, predicate: NSPredicate(format: "list == %#", reference))
I want to fetch all items from Core Data, but sorted from lastest to oldest. My Entity "Dice" has attribute "timestamp" of type Date.
This is how I save and fetch:
class func saveResults(_ withResults: Int) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "Dice", in: context)
let manageObject = NSManagedObject(entity: entity!, insertInto: context)
let date = Date()
manageObject.setValue(withResults, forKey: "result")
manageObject.setValue(date, forKey: "timestamp")
do {
try context.save()
return true
} catch {
return false
}
}
class func fetchObject() -> [Dice]? {
let context = getContext()
var dices:[Dice]? = nil
do {
dices = try context.fetch(Dice.fetchRequest())
return dices
} catch {
return dices
}
}
Does anyone know how to sort it. All solutions are confusing and showing the sort from old to new.
You need to specify an appropriate sort descriptor.
I recommend to make the function throw and hand over the error.
class func fetchObject() throws -> [Dice] {
let context = getContext()
let fetchRequest : NSFetchRequest<Dice> = Dice.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
return try context.fetch(fetchRequest)
}
Note: The fetch operation returns [Dice]. A type cast is not needed.
You can do like this
class func fetchObject() -> [Dice]? {
var dices:[Dice]? = nil
let context = getContext()
let fetchDice: NSFetchRequest<Dice> = Dice.fetchRequest()
let sortDescriptor = [NSSortDescriptor.init(key: "timestamp", ascending: false)]
fetchDice.sortDescriptors = sortDescriptor
do
{
dices = try context.fetch(fetchDice)
return dices
}catch(let error) {
print(error.localizedDescription)
return dices
}
}
I have an array with a SQLite with about 2000 records and all are listed on a tableview. When one of the records are selected, it goes to the "speciesDetailViewController" were it displays details of that item, including the common name of that species.
Currently, all displayed fields are not editable.
I am now adding the ability for the user to to change one of the fields, their common name and the ability to add notes per species.
The minor change is saved in CoreData as I have no experience with SQLite (hired someone).
I am fairly certain the data is being stored as I have print commands showing so.
My issue seems to be retrieving the data.
Note that as editing this field is optional, not every species will have a record in coreData, only the species that the user updated their common name.
class SpeciesDetailData: NSManagedObject
{
#NSManaged var speciesName: String
#NSManaged var commonName: String
#NSManaged var commonForeignName: String
#NSManaged var speciesNote: String
}
.
var speciesDetailData : SpeciesDetailData?
var speciesDataObject: [NSManagedObject] = []
var speciesNameToSave = String()
#IBAction func ckSaveCommonNameButton(_ sender: Any) {
speciesNameToSave = speciesLabel.text!
self.saveSpeciesName(speciesName: speciesNameToSave)
let commonNameToSave = ckCommonNameTextField.text
self.saveCommonName(commonName: commonNameToSave!)
}
func saveCommonName (commonName: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "SpeciesDetailData", in: managedContext)!
let saveEntity = NSManagedObject(entity: entity, insertInto: managedContext)
saveEntity.setValue(commonName, forKey: "commonName")
saveSpeciesName(speciesName: speciesNameToSave)
do {
try managedContext.save()
speciesDataObject.append(saveEntity)
print(commonName)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
func saveSpeciesName (speciesName: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "SpeciesDetailData", in: managedContext)!
let saveEntity = NSManagedObject(entity: entity, insertInto: managedContext)
saveEntity.setValue(speciesName, forKey: "speciesName")
do {
try managedContext.save()
speciesDataObject.append(saveEntity)
print(speciesName)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
Here is the retrieving function
func retrieveCoreDataSpecies () {
let context = (UIApplication.shared.delegate
as! AppDelegate).persistentContainer.viewContext
let entity = NSEntityDescription.entity(
forEntityName: "SpeciesDetailData", in: context)
let request: NSFetchRequest<SpeciesDetailData> = SpeciesDetailData.fetchRequest()
request.entity = entity
let pred = NSPredicate(format: "speciesName = %#", specieDetail.specie)
request.predicate = pred
do {
let results = try context.fetch(request as!
NSFetchRequest<NSFetchRequestResult>)
if (results.count > 0) {
let match = results[0] as! NSManagedObject
if speciesDetailData?.commonName != nil {
ckCommonNameTextField.text = match.value(forKey: "commonName") as? String
} else {
}
if ckNotesTextView.text == "" || ckNotesTextView.text == nil {
} else {
ckNotesTextView.text = match.value(forKey: "speciesNote") as! String
}
}
} catch let error {
print("Count not fetch \(error), \(error.localizedDescription)")
}
}
When it gets to the
if speciesDetailData?.commonName != nil
it thinks the record is empty and skips over the needed lines.
Any help is appreciated
You are creating separate objects in the two save functions. In each case you are setting only one of the properties, so after you call saveSpeciesName you will have created an object with species name set, and after you call saveCommonName you will have created a different object with the common name set. You need to set both the species name and the common name on the same object.
I have the following entities and relationship
I want to be able to set an exercise to have a nil result for its routine name relationship, if that makes sense? so that it can later be set as a routine name when the routine entity is formed.
My question is, how do you set this sort of attribute up? I am trying the following code but it causes a fatal crash:
userExercise.usersroutine?.name = nil
My logic being that i take the exercise and follow the relationship to the name property and set it to nil?
Thanks for any correction and clarification on my logic
EDIT: Added my existing exercise and routine save functions
func createExercise() {
guard let managedObjectContext = managedObjectContext else { return }
if let userExercise = userExercise {
userExercise.name = userExerciseName.text
userExercise.sets = Int64(userSetsCount)
userExercise.reps = Int64(userRepsCount)
userExercise.weight = Double(self.userExerciseWeight.text!)!
userExercise.id = UUID().uuidString
userExercise.routine = nil
}
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
Routine Creation:
func createRoutine() {
guard let managedObjectContext = managedObjectContext else { return }
let userRoutine = UserRoutine(context: managedObjectContext)
userRoutine.name = workoutNameTextfield.text
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
Current Fetch Request:
fileprivate lazy var fetchedResultsController: NSFetchedResultsController<UserExercise> = {
let fetchRequest: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "id", ascending: true)]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
return fetchedResultsController
Please check the implementation below I'have created some exercises and routines. Also read comments in code, this will help you figure out how to go about it.
Function to add a new exercise
func createExercise(weight: Int16, respetitions: Int16, name: String, routine: Routine?)->Exercise? {
let context = getMainContext()
let exercise = NSEntityDescription.insertNewObject(forEntityName: "Exercise", into: context) as! Exercise
exercise.setValue(weight, forKey: "weight")
exercise.setValue(name, forKey: "name")
exercise.setValue(respetitions, forKey: "rep")
do {
try context.save()
return exercise
}
catch
{
fatalError("unable to Ssavve")
}
}
Function to add a new routine
func createRoutine(name: String, exercises:[Exercise]) {
let context = getMainContext()
let routine = NSEntityDescription.insertNewObject(forEntityName: "Routine", into: context) as! Routine
routine.name = name
//Iterate over Exercise objects & check if routine is nil.
//Here if routine is not nil it menas your exercise is already assigned to a routine.
//If routine is nil assign routine.addToRelationship(<#T##value: Exercise##Exercise#>) and Also assign routine to the execise.
do {
try context.save()
}
catch
{
fatalError("unable to Ssavve")
}
}
Function to get main NSManagedObjectContext on which we can perform core-data actions
func getMainContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
Below, First I create few exercises without any relationship to routines
"The routine doesnt exist when the exercises are created, it is created afterward and its name is set"
and then create routines by passing some exercises (You can refer to other answer on how to fetch exercises with routine as nil values)
func initializer() {
//I'm adding exercises first without routines
let ex1 = self.createExercise(weight: 10, respetitions: 4, name: "Exercise1", routine: nil)
let ex2 = self.createExercise(weight: 5, respetitions: 10, name: "Exercise2", routine: nil)
let ex3 = self.createExercise(weight: 20, respetitions: 2, name: "Exercise3", routine: nil)
let ex4 = self.createExercise(weight: 5, respetitions: 10, name: "Exercise2", routine: nil)
self.createRoutine(name: "Routine 1", exercises: [ex1!, ex2!]) //You can pass all the exercises or use fetch request to query exercises with routine as nil
self.createRoutine(name: "Routine 2", exercises: [ex3!, ex4!])
self.createRoutine(name: "Routine 3", exercises: [ex1!, ex2!]) //This routine shall not be adding any execises as they are already added to othe routines
}
Updating create routine Function to query results of UserExercise which has usersroutine as nil
func createRoutine() {
guard let managedObjectContext = managedObjectContext else { return }
let userRoutine = UserRoutine(context: managedObjectContext)
userRoutine.name = workoutNameTextfield.text
//Getting nil value User Exercises
let request: NSFetchRequest<UserExercise> = UserExercise.fetchRequest()
request.predicate = NSPredicate(format: "usersroutine == nil")
do {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let queryResults = try context.fetch(request)
//I like to check the size of the returned results!
print ("num of results = \(queryResults.count)")
//You need to convert to NSManagedObject to use 'for' loops
for exercise in queryResults as [NSManagedObject] {
//get the Key Value pairs (although there may be a better way to do that...
print("Exercise NAME: \(exercise.value(forKey: "name"))")
}
} catch {
print("Error with request: \(error)")
}
do {
try managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
It doesn't seem that you should need to use the name attribute at all. This attribute should be used for storing the actual name of the UserRoutine and not for anything relationship based.
The relationships between entities in Core Data don't rely on a particular attribute of an entity, but between the entities themselves.
"I want the routine builder to look at the exercises and import all the exercises with nil in the relationship into it"
So...
Create a fetch request to fetch all the entities of UserExercise that don't have a related UserRoutine (i.e. where userroutine is nil).
let orphanedExerciseFetchRequest = NSFetchRequest(entityName: "UserExercises")
orphanedExerciseFetchRequest.predicate = NSPredicate(format: "userroutine == nil)
Execute this fetch request to get an array of UserExercises (with no related routine)
let orphanedExercises = managedObjectContext.executeFetchRequest(orphanedExerciseFetchRequest())
"creating a routine with attributed exercises"
Set the fetched UserExercise entitiy's property userRoutine to your routine (and don't forget to save the changes in your managed object context).
myRoutine.userexercises = orphanedExercises
Later, if you want to get the exercises for a particular routine:
let fetchRequest = NSFetchRequest(entityName: "UserExercises")
fetchRequest.predicate = NSPredicate(format: "userroutine == %#", someUserRoutine)