FetchRequst issue with data fault - ios

When I was inserting data to one entity of CoreData, All the rows are inserted successfully(Saved).
But when I try to fetch the data using FetchRequest, Only one row of data is coming even if number of rows inserted are 3 or 4 or anything(more than 1).
Remaining rows are not getting fetched. And when I print fetch results,
It says - Error
0:<EquipmentDetails: 0x6000000bad60>
(entity: EquipmentDetails; id: 0xd000000000040000
coredata:/EquipmentDetails/p1> **data:fault>)**
I didn't get what was going in backend of core data?
code for Insertion
func insertEqipToLocalDb()
{
let mobileNo : String = UserDefaults.standard.string(forKey: "phoneNumber")!
let equipDetailsItem = NSEntityDescription.insertNewObject(forEntityName: "EquipmentDetails", into:managedObjContext) as! EquipmentDetails
for (index,item) in array_IDEquip.enumerated()
{
equipDetailsItem.mobileNumber = mobileNo
equipDetailsItem.type = array_typeEquip[index]
equipDetailsItem.name = array_nameEquip[index]
equipDetailsItem.startDate = array_sDateEquip[index]
equipDetailsItem.endDate = array_eDateEquip[index]
equipDetailsItem.equpID = Int16(item)
equipDetailsItem.serviceDatesStr = array_serviceDateEquip[index]
}
do
{
try managedObjContext.save()
UserDefaults.standard.set("AlreadyInstalled", forKey: "statusInstallation")
}
catch
{
Exception.insertExceptionDetails(errorMsg: error as NSError, context: managedObjContext)
}
}
//code for fetching
let request = NSFetchRequest<NSFetchRequestResult>()
let entity = NSEntityDescription.entity(forEntityName:"EquipmentDetails", in: managedObjContext)
request.entity = entity
do
{
let fetchResults = try managedObjContext.fetch(request)
for r in fetchResults
{
typeEquipArray.append((r as AnyObject).value(forKey: "type") as! String)
}
}
catch let error as NSError
{
Exception.insertExceptionDetails(errorMsg: error, context: managedObjContext)
}

On this line:
let equipDetailsItem = NSEntityDescription.insertNewObject(forEntityName: "EquipmentDetails", into:managedObjContext) as! EquipmentDetails
You create one instance. In the loop that follows, you set values for the type, name, etc properties over and over again on that same instance. Then you save changes, which include just that one object. If you want a difference instance of EquipmentDetails for each pass through the loop, you need to create the instance inside the loop.
The "fault" message is not an error unless you tried to access the property values and found that they were not present. It's part of how Core Data works. See the answer that Harshal Valanda linked in the comments for more detail.

Related

adding objects in array creates duplicate values in swift

I have an array of custom objects but when I add items to array it creates duplicate of last item add in array.
Below is my code, please suggest where is the mistake, this small thing not able to get it.
var tempArr:[AnimalViewModel] = [AnimalViewModel]()
do {
var objAnimal = Animal()
var result = try managedContext.fetch(fetchRequest)
for ds in result as! [NSManagedObject] {
objAnimal.name = (ds.value(forKey: "name")) as! String
objAnimal.type = (ds.value(forKey: “type”)) as! String
Var objAVM = AnimalViewModel(aniModel: objAnimal)
tempArr.append(objAVM)
}
} catch {
print(" Error ")
}
The array tempArr contains all duplicate element as last inserted element even objAnimal contains different values.
Thanks,
First of all never print a meaningless literal string like "Error" in a catch block. Print always the error instance.
Animal is obviously a class (reference type). You are creating one instance and the properties are updated in the loop. As always the same instance is used the values are overwritten and you get result.count items with the same contents.
Create new instances inside the loop and replace Entity with the real entity name
var tempArr = [AnimalViewModel]()
do {
let result = try managedContext.fetch(fetchRequest) as! [Entity] // let !
for ds in result {
let objAnimal = Animal() // let !
objAnimal.name = ds.name
objAnimal.type = ds.type
let objAVM = AnimalViewModel(aniModel: objAnimal) // let !
tempArr.append(objAVM)
}
} catch {
print(error)
}
And please notice and fix the warnings about never mutated variables

IOS - How to Insert data into different tables with relationship In Core Data Swift

We have two tables with one to one relationship. personel.idpersonel maps to personeltochart.idpersonel.
personel
personeltochart
On login event, we sync data of personel table from a webservice, and save it into personel tables. We have to get other table data on another event and save it.
The question is when we try to fetch data from these two tables why we get data of personel table correctly but data from personeltochart table is nil?
personel
firstname
idpersonel
personelToChart
id
idpersonel
Here is an example of saving data of table:
let jsonEmza = try jsonDecoder.decode([JSONNameTabel].self, from: data)
var privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext = CoreDataManager.shared.persistentContainer.viewContext
jsonEmza.forEach({ (jsonCompany) in
let tabel = jsonCompany.tableName
if tabel == "Personel"{
jsonCompany.personel?.forEach({ (field) in
let personel = Personel(context: privateContext)
personel.firstname = field.firstName
personel.idpersonel = field.id
})
do {
try privateContext.save()
} catch let saveErr {
print("Failed to save personel:", saveErr)
}
}
fetching example
let context = CoreDataManager.shared.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<Personel>(entityName: "Personel")
let predicate = NSPredicate(format: "idpersonel == %#", "147")
fetchRequest.predicate = predicate
do {
let listNameh = try context.fetch(fetchRequest)
listNameh.forEach({ (item) in
print(item.idpersonel ?? "")
print(item.lastname ?? "")
print(item.firstname ?? "")
print(item.personelToChart?.id)
})
} catch let fetchErr {
print("Failed to fetch Personel:", fetchErr)
}
The data of item.personelToChart?.id is nil.
Why is this happening?
It's like Sam said, you need assign an object to PersonelToChart on Personel. For example, here's my core data model
Leg (flightId, flightName, Aircraft)
Aircraft (aircraftId, aircraftName)
Make sure your relationship in Flight entity looks like
let Aircraft = AircraftCoreData(context: self.persistentContainer.viewContext)
secondAircraft.aircraftId = 02334
secondAircraft.aircraftName = "ABC"
leg.aircraft = Aircraft
You'll see that we created aircraft object, assigned values to its properties then assigned the object to leg.aircraft property. Now when you fetch the data, you won't get nil

Save json dictionary to core data Swift 3

I am able to get the last guest dictionary value in the json array saved into core data as a dictionary using the transformable key value however the other guest dictionary values are not saving. Guest is also it's on entity for now but I was hoping to save the guest as a dictionary since this task doesn't require complex relationships. I'm sure I'm looking through the json, the value type for reservation.guest = [AnyHashable: Any]?Any suggestions would be helpful here is my json response https://pastebin.com/J28myW66, thanks
Note: using Alamofire for the HTTP Request. Also haven't included my entire class here as this is the main part of it. Reservation and Guest are both NSManagedObject classes
let managedObjectContext = CoreDataManager.shared.persistentContainer.viewContext
let reservationEntityDescription = NSEntityDescription.entity(forEntityName: "Reservation", in: managedObjectContext)
let guestEntityDescription = NSEntityDescription.entity(forEntityName: "Guest", in: managedObjectContext)
let reservation = Reservation(entity: reservationEntityDescription!, insertInto: managedObjectContext)
let guest = Guest(entity: guestEntityDescription!, insertInto: managedObjectContext)
let url = "\(serverEndpoint)\(path)"
manager?.request(
url
).responseJSON { responseData in
if(responseData.result.error != nil) {
print(responseData.response)
}
else if responseData.result.value != nil{
let json = JSON(responseData.result.value!)
let content = json["data"]
var reservationArray: [String] = []
if let dates = content.array {
for item in dates {
if let str = item["date_time"].string {
reservationArray.append(str)
print(reservationArray)
}
}
}
for (key,obj) in content {
let guestData = obj["guest"]
let guestDict = guestData.dictionaryObject!
reservation.guest = guestDict
reservation.id = obj["id"].stringValue
reservation.dateTime = obj["date_time"].date
reservation.startTime = obj["start_time"].time
reservation.numOfPeople = obj["number_of_people"].intValue as NSNumber?
reservation.status = obj["status"].stringValue
reservation.tables = obj["tables"].arrayObject as! [NSString]?
reservation.reservationCollections = reservationArray as [NSString]?
guest.id = guestData["id"].stringValue
guest.email = guestData["email"].stringValue
guest.name = guestData["full_name"].stringValue
guest.phone = guestData["phone"].stringValue
guest.notes = guestData["notes"].stringValue
}
print("Reservation to be saved\(reservation)")
print("Guest to be saved: \(guest)")
}
}
do {
try reservation.managedObjectContext?.save()
} catch let error as NSError {
fatalError(error.localizedDescription)
}
do {
try guest.managedObjectContext?.save()
} catch let error as NSError {
fatalError(error.localizedDescription)
}
When your code starts, you create one instance of Guest and one instance of Reservation:
let reservation = Reservation(entity: reservationEntityDescription!, insertInto: managedObjectContext)
let guest = Guest(entity: guestEntityDescription!, insertInto: managedObjectContext)
After that you never create any other instances. In your loop you assign values to this instance:
reservation.guest = guestDict
reservation.id = obj["id"].stringValue
...
guest.id = guestData["id"].stringValue
guest.email = guestData["email"].stringValue
...
But since there's only one instance, only the last pass through the loop gets saved. The first time through the loop you assign values to guest and reservation. Every other time, you overwrite the previous values with new ones.
If you want to save a new instance for every pass through the loop, you need to create new instances every time. Move the let guest = ... and let reservation = ... lines inside the loop.
Firstly you need to make design flow bit generic i.e The HTTP request/response, DataBase Part, Model Part and UI part.
Now create a generic model class for your response,
so that the values will bind in single object.
In you core data sub class your table i.e custom NSManagedObject Class.
First convert the dictionary objects [objects in content.array] into respective model class objects.
In that SBPlayer is a model class
Favourite+CoreDataProperties.swift & Favourite+CoreDataClass.swift are custom NSManagedObject class (auto-generated).
Now with every object, you have mapping respective properties in database table and in custom NSManagedObject class.
Map the values and Save it DataBase.
For example: https://github.com/Abhishek9634/ScoreBoard/blob/master/ScoreBoard/ScoreBoard/SBDBManager.swift
Reference : https://github.com/Abhishek9634/ScoreBoard

understanding adding predicate to NSFetchrequest in swift

I want to store some data downloaded from a remote file in CoreData. his data may already have been downloaded before and then the changes need to be added to core data.
I use the code displayed here. In three places I use a Fetchrequest to check if the data is already in the database. I use a predicate with the request. In the code I added comments to distinguish between them. If the item is already in coreData I update the data, else I instantiates a new item. The code to add the item to core data is in a convenience init in the class. This works fine. The problem I am getting is this:
The first time in this code id equals 0 so the schoolFetch is performed. Adding the predicate nr 1 gives no error and the school class is instantiated with its id set to 0.
Second time id = 1. This means a locationItem, with location being a part of the school, is checked for and instantiated. To add the location to the school a fetchrequest is performed to get the schoolInstance. Predicate 2 is used. This gives no error although the previous added schoolInstance is not retrieved, but it is if the predicate is not used (commented out).
After the schoolInstance is retrieved I use a fetchrequest to check if the location is already in coredata. If not it is instantiated, else its data is updated. The predicate nr 3 gives a run-time error stating nothing but EXC_BAD_ACCESS (code=1, address = 0x1). No error description is given in the debugger window.
So I have two questions:
a) why does predicate nr 2 not return the previous inserted item?
b) why does predicate nr 3 give an error?
I structured this code this way as it is possible to first receive the information on the location and after that the information on the school.
if id == 0 {
//get school
let schoolFetch = NSFetchRequest<StreekgidsSchool>(entityName: "School")
schoolFetch.predicate = NSPredicate(format: "locationId == %#", id) // predicate 1
do {
let fetchedSchool = try streekgidsModel?.context.fetch(schoolFetch)
if let school = fetchedSchool?.first{
school.name = name
school.locationId = id
school.colorValue = color
schoolInstance = school
}
else {
//create new entry in coredata for school
schoolInstance = StreekgidsSchool(name: name, context: (streekgidsModel?.context)!)
schoolInstance?.locationId = id
schoolInstance?.colorValue = color
}
} catch {
fatalError("Failed to fetch Schooldata: \(error)")
}
}
else {
//check if school already is defined
let schoolFetch = NSFetchRequest<StreekgidsSchool>(entityName: "School")
schoolFetch.predicate = NSPredicate(format: "locationId == %#", 0) //predicate 2
do {
let fetchedSchool = try streekgidsModel?.context.fetch(schoolFetch)
if let school = fetchedSchool?.first{
schoolInstance = school
}
else {
//create new entry in coredata for school
schoolInstance = StreekgidsSchool(id: 0, context: (streekgidsModel?.context)!)
}
} catch {
fatalError("Failed to fetch Schooldata: \(error)")
}
//get location
let locationFetch = NSFetchRequest<StreekgidsLocation>(entityName: "Location")
locationFetch.predicate = NSPredicate(format: "locationId == %#", id) //predicate 3
do {
let fetchedLocation = try streekgidsModel?.context.fetch(locationFetch)
if let location = fetchedLocation?.first{
location.name = name
location.locationId = id
location.colorValue = color
location.school = schoolInstance
}
else {
//create new entry in coredata for location
let locationInstance = StreekgidsLocation(name: name, context: (streekgidsModel?.context)!)
locationInstance.locationId = id
locationInstance.colorValue = color
}
} catch {
fatalError("Failed to fetch Schooldata: \(error)")
}
}
Exactly. The %# is expecting a Foundation object as the argument but an Int is not an object. So your id (set to zero) is being interpreted as nil. That is also why your predicate 2 is returning nothing and why you retrieve the result when you comment it out. Only your third predicate throws a run-time error but in fact all three of your predicates are working incorrectly, the first two are simply not falling over in an immediately visible way. Have you tried substituting %# for %d.
And as you point out %u for unsigned integers.

Faster way to check if entry exists in Core Data

In my app when data is synced i can get 20k entries (from given timestamp) from the server that should be synced to the local device. For every entry i try to fetch it (if it exist already) and if doesn't i create new. The problem is that the whole operation is too slow - for 20k on iphone 5 is 10+ mins. Another solution that i though is to delete all entries from the given timestamp and create new entries for all returned entries and there will be no need to perform fetch for every single entry ? If someone have any advice will be nice. Here is sample code for the current state:
var logEntryToUpdate:LogEntry!
if let savedEntry = CoreDataRequestHelper.getLogEntryByID(inputID: inputID, fetchAsync: true) {
logEntryToUpdate = savedEntry
} else {
logEntryToUpdate = LogEntry(entity: logEntryEntity!, insertInto: CoreDataStack.sharedInstance.saveManagedObjectContext)
}
logEntryToUpdate.populateWithSyncedData(data: row, startCol: 1)
Here is the actual request method:
class func getLogEntryByID(inputID:Int64, fetchAsync:Bool) ->LogEntry? {
let logEntryRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "LogEntry")
logEntryRequest.predicate = NSPredicate(format: "inputId == %#", NSNumber(value: inputID as Int64))
logEntryRequest.fetchLimit = 1
do {
let mocToFetch = fetchAsync ? CoreDataStack.sharedInstance.saveManagedObjectContext : CoreDataStack.sharedInstance.managedObjectContext
if let fetchResults = try mocToFetch.fetch(logEntryRequest) as? [LogEntry] {
if ( fetchResults.count > 0 ) {
return fetchResults[0]
}
return nil
}
} catch let error as NSError {
NSLog("Error fetching Log Entries by inputID from core data !!! \(error.localizedDescription)")
}
return nil
}
Another thing that i tried is to check the count for specific request but again is too slow.
class func doesLogEntryExist(inputID:Int64, fetchAsync:Bool) ->Bool {
let logEntryRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "LogEntry")
logEntryRequest.predicate = NSPredicate(format: "inputId == %#", NSNumber(value: inputID as Int64))
//logEntryRequest.resultType = .countResultType
logEntryRequest.fetchLimit = 1
do {
let mocToFetch = fetchAsync ? CoreDataStack.sharedInstance.saveManagedObjectContext : CoreDataStack.sharedInstance.managedObjectContext
let count = try mocToFetch.count(for: logEntryRequest)
if ( count > 0 ) {
return true
}
return false
} catch let error as NSError {
NSLog("Error fetching Log Entries by inputID from core data !!! \(error.localizedDescription)")
}
return false
}
Whether fetching the instance or getting the count, you're still doing one fetch request per incoming record. That's going to be slow, and your code will be spending almost all of its time performing fetches.
One improvement is to batch up the records to reduce the number of fetches. Get multiple record IDs into an array, and then fetch all of them at once with a predicate like
NSPredicate(format: "inputId IN %#", inputIdArray)
Then go through the results of the fetch to see which IDs were found. Accumulate 50 or 100 IDs in the array, and you'll reduce the number of fetches by 50x or 100x.
Deleting all the entries for the timestamp and then re-inserting them might be good, but it's hard to predict. You'll have to insert all 20,000. Is that faster or slower than reducing the number of fetches? It's impossible to say for sure.
Based on Paulw11's comment, I came up with the following method to evaluate Structs being imported into Core Data.
In my example, I have a class where I store search terms. Within the search class, create a predicate which describes the values of the stuff within my array of structs.
func importToCoreData(dataToEvaluateArray: [YourDataStruct]) {
// This is what Paul described in his comment
let newDataToEvaluate = Set(dataToEvaluateArray.map{$0.id})
let recordsInCoreData = getIdSetForCurrentPredicate()
let newRecords = newDataToEvaluate.subtracting(recordsInCoreData)
// create an empty array
var itemsToImportArray: [YourDataStruct] = []
// and dump records with ids contained in newRecords into it
dataToEvaluateArray.forEach{ record in
if newRecords.contains(record.id) {
itemsToImportArray.append(record)
}
}
// THEN, import if you need to
itemsToImportArray.forEach { struct in
// set up your entity, properties, etc.
}
// Once it's imported, save
// You can save each time you import a record, but it'll go faster if you do it once.
do {
try self.managedObjectContext.save()
} catch let error {
self.delegate?.errorAlert(error.localizedDescription, sender: self)
}
self.delegate?.updateFetchedResultsController()
}
To instantiate recordsInCoreData, I created this method, which returns a Set of unique identifiers that exist in the managedObjectContext:
func getIdSetForCurrentPredicate() -> Set<String> {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "YourEntity")
// searchQuery is a class I created with a computed property for a creating a predicate. You'll need to write your own predicate
fetchRequest.predicate = searchQuery.predicate
var existingIds: [YourEntity] = []
do {
existingIds = try managedObjectContext.fetch(fetchRequest) as! [YourEntity]
} catch let error {
delegate?.errorAlert(error.localizedDescription, sender: self)
}
return Set<String>(existingIds.map{$0.id})
}

Resources