My steps:
Create entity via .init(context: NSManagedObjectContext)
Save context if it has changes. Save is succeded.
Use this entity to set some values. Example: let newString = "test == \(entity.id.intValue)"
The problem that after step 2 the id value of the entity time to time become 0.
I did debug and there are two types of output:
<Entity: 0x60000023def0> (entity: Entity; id: 0x600002777d40 <x-coredata:///UserEntity/tC5203E3F-0F4A-4937-B44A-45CD578382AA10>; data: <fault>)
<Entity: 0x6000003c9f40> (entity: Entity; id: 0xab5e4a90b6dee4ba <x-coredata://3EC29A9F-A81A-49EC-B4BE-23004ECF10FF/UserEntity/p88>; data: { ...has data }
Also in the first situation isFault is true, is second it is false.
I can't understand why saving of the context can make entities fault?
I assume that the bug is probably somewhere in your code which you are NOT showing in your question. That is why I am just going to give you the function to fetch the value in the entity. If the code I post wont help then perhaps its the timing issue.
static let persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "YOUR_APPs_NAME")
container.loadPersistentStores { (_, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
static var context: NSManagedObjectContext { return persistentContainer.viewContext }
func loadUserIsVerified() -> Id {
var id : Id!
let context = CoreDataStack.persistentContainer.viewContext
let fetchRequest: NSFetchRequest< Id > = Id.fetchRequest()
do {
let ids = try context.fetch(fetchRequest)
if(sessions.count > 0) {
id = ids.first
}
} catch {
print("Something happened while trying to retrieve tasks...")
}
return id
}
Related
Currently, the following is my implementation regarding CoreData.
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "xxx")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// This is a serious fatal error. We will just simply terminate the app, rather than using error_log.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
private(set) lazy var backgroundContext: NSManagedObjectContext = {
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return backgroundContext
}()
}
class NSAttachmentRepository {
static let INSTANCE = NSAttachmentRepository()
private init() {
}
func isExist(_ name: String) -> Bool {
let coreDataStack = CoreDataStack.INSTANCE
let viewContext = coreDataStack.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
fetchRequest.fetchLimit = 1
fetchRequest.predicate = NSPredicate(format: "name == %#", name)
do {
let count = try viewContext.count(for: fetchRequest)
if count > 0 {
return true
}
} catch {
error_log(error)
}
return false
}
}
My strategy dealing with core data are
To perform non-blocking write call from main thread (UI thread), I will use CoreDataStack.INSTANCE.backgroundContext
To perform blocking read call from main thread (UI thread), I will use CoreDataStack.INSTANCE.persistentContainer.viewContext
This work fine all the time, until I need to perform the following operation
To perform blocking read call from background thread (non UI thread)
We need to run the code in PHPickerViewControllerDelegate's loadFileRepresentation callback. If we check using Thread.isMainThread (returns false) inside loadFileRepresentation callback, it is executed in a background thread.
When I perform call NSAttachmentRepository.INSTANCE.isExist(name) in function where Thread.isMainThread is false, I will get the following crash
CoreData`+[NSManagedObjectContext
Multithreading_Violation_AllThatIsLeftToUsIsHonor]:
I attempt to "fix" the problem by modifying the code from using coreDataStack.persistentContainer.viewContext to coreDataStack.backgroundContext
func isExist(_ name: String) -> Bool {
let coreDataStack = CoreDataStack.INSTANCE
////let viewContext = coreDataStack.persistentContainer.viewContext
let backgroundContext = coreDataStack.backgroundContext
let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
fetchRequest.fetchLimit = 1
fetchRequest.predicate = NSPredicate(format: "name == %#", name)
do {
////let count = try viewContext.count(for: fetchRequest)
let count = try backgroundContext.count(for: fetchRequest)
if count > 0 {
return true
}
} catch {
error_log(error)
}
return false
}
However, I am still getting the same crash error.
Do you have any idea how I can perform CoreData context.count from a background thread?
It's not enough to just use a background context. You need to use that context on its own queue. You checked that you're not running on the main queue, but you could be on any queue, and the background context only works on one of them. The error message you see is what Core Data says when you're using it on the wrong queue.
Any time you use backgroundContext, you need to wrap the code in a call to perform or performAndWait, to ensure that your code runs on the background context's queue. Since your isExist function is synchronous, it needs to use performAndWait so that it can get a result before returning.
Thanks for pointer from #Tom Harrington
Here's the code snippet on how to tackle such threading issue.
func isExist(_ name: String) -> Bool {
let coreDataStack = CoreDataStack.INSTANCE
let viewContext = coreDataStack.persistentContainer.viewContext
var result = false
viewContext.performAndWait {
let fetchRequest = NSFetchRequest<NSAttachment>(entityName: "NSAttachment")
fetchRequest.fetchLimit = 1
fetchRequest.predicate = NSPredicate(format: "name == %#", name)
do {
let count = try viewContext.count(for: fetchRequest)
if count > 0 {
result = true
} else {
result = false
}
} catch {
error_log(error)
}
}
return result
}
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).
I have save values to entity method which save new data and updates existing data.
func saveSteps(_ serverJson: [[String: Any]]){
let stepService = StepService(context: context);
if(serverJson.count > 0){
for step in serverJson {
let stepTitle = step["stepTitle"] as? String ?? ""
let stepDescription = step["stepDescription"] as? String ?? ""
let stepId = step["_id"] as? String ?? ""
let predicate: NSPredicate = NSPredicate(format: "stepId=%#", stepId)
let stepList = stepService.get(withPredicate: predicate);
if(stepList.count == 0){
stepService.create(stepId: stepId, stepTitle: stepTitle, stepDescription: stepDescription);
}else{
if let updatableStep = stepList.first{
updatableStep.stepDescription = stepDescription //EXC_BAD_ACCESS Error Here
updatableStep.stepName = stepName
updatableStep.stepTitle = stepTitle
stepService.update(updatedStep: updatableStep)
}else{
stepService.create(stepId: stepId, stepTitle: stepTitle, stepDescription: stepDescription);
}
}
saveContext()
}
My Create update and get methods are in stepService
func create(stepId:String, stepDescription: String, stepTitle:String){
let newItem = NSEntityDescription.insertNewObject(forEntityName: "Steps", into: context) as! Steps //EXC_BAD_ACCESS Error Here
newItem.stepId = stepId
newItem.stepTitle = stepTitle
newItem.stepDescription = stepDescription
}
func update(updatedStep: Steps){
if let step = getById(id: updatedStep.objectID){
step.stepId = updatedStep.stepId
step.stepTitle = updatedStep.stepTitle
step.stepDescription = updatedStep.stepDescription
}
func get(withPredicate queryPredicate: NSPredicate) -> [Steps]{
let fetchRequest: NSFetchRequest<Steps> = Steps.fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = queryPredicate
do {
let response = try context.fetch(fetchRequest)
return response
} catch let error as NSError {
// failure
print(error)
return [Steps]()
}
}
}
Mysave context method is
// Creating private queue to save the data to disk
lazy var savingModelcontext:NSManagedObjectContext = {
var managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = self.coordinator
return managedObjectContext
}()
// Creating Context to save in block main queue this will be temporary save
lazy var context:NSManagedObjectContext = {
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.parent = self.savingModelcontext
return managedObjectContext
}()
func saveContext () {
guard savingModelcontext.hasChanges || context.hasChanges else {
return
}
context.performAndWait {
do {
try self.context.save()
} catch let error as NSError {
print("Could not save in Context: \(error.localizedDescription)")
}
}
savingModelcontext.perform {
do {
try self.savingModelcontext.save()
} catch let error as NSError {
print("Could not save savingModelContext: \(error.localizedDescription)")
}
}
}
There are two places that core data crashes with same error message one is when i access the data to update the method and other is when i am trying to create a new item using NSEntityDescription.insertNewObject with entity name.
while saing i have tried Dispach queue with qos of userInitiated and default. I didn't use background as user might open some thing that might use this.
The problem is the crash is not consistanct and has never crashed when doing a debug which leads me to belive it is concurrency issue but the data is never deleted or read when being updated.
PS: I have read the questions with same and similar issues but i could not get a working answer here the question.
Kidly point out if i have made any mistakes
Thanks any help is appreciated
The Reason fo the crash was i was trying to change was that i was using and using private queue context in main thread and vise versa. to fix this using Group Dispach queue with desired QOS for private queue context and main thread for using main queue objects.
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.
I need to fetch some information from the server.
and then I need to save them using CoreData.
The problem is that the same record with same attributes is saved several times.
I want to save just new records to the CoreData and update the existing ones.
func saveChanges() throws {
var error: ErrorType?
mainQueueContext.performBlockAndWait () {
if self.mainQueueContext.hasChanges {
do {
try self.mainQueueContext.save()
} catch let saveError {
error = saveError
}
}
}
if let error = error {
throw error
}
}
func fetchRecentPosts(completion completion: (PostResult) -> Void){
.
.
.
do{
try self.coreDataStack.saveChanges()
print("now data is saved in this device")
}
catch let error {
result = .Failure(error)
}
.
.
.
}
My idea is to check if that id was saved. If it was saved before I should not add another record. Then how can I update the existing one?
And is it a good way to solve this problem?
First check if your record exists in DB
static func getMyRecord(with id : String, context : NSManagedObjectContext) -> MyRecord? {
let fetchRequest: NSFetchRequest< MyRecord> = MyRecord.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id = %#", id)
let foundRecordsArray = try! context.fetch(fetchRequest)
if foundRecordsArray.count > 0 {
return foundRecordsArray[0]
}
return nil
}
While inserting if available update it else create new record.
static func insertRecord(_ record : MyRecord, context : NSManagaedObjectContext) {
let foundRecord = DataInserterClass. getMyRecord(with: yourID, context: context)
if foundRecord == nil {
//create new record
}
else {
//update foundRecord
}
try! context.save()
}