func save(handler: #escaping (NSManagedObjectContext) -> Void, completion: ((Error?)-> Void)? = nil) {
if let rootContext = rootContext {
let localContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
localContext.parent = rootContext
localContext.obtainPermanentIdsBeforeSaving()
handler(localContext)
guard localContext.hasChanges else {
DispatchQueue.main.async {
completion?(nil)
}
return
}
do {
try localContext.save()
try localContext.parent?.save() //here, line 65 in CoreDataManager
DispatchQueue.main.async {
completion?(nil)
}
} catch let error as NSError {
print("❌ Core Data Save Error \(error), \(error.userInfo)")
DispatchQueue.main.async {
completion?(error)
}
}
}
}
This is report from Firebase:
Please note that I am not an expert on CoreData, however to me, it looks like a threading issue.
A simple solution that you could try would be to wrap your parent context's save call in the performAndWait(_:) method, like so:
func save(handler: #escaping (NSManagedObjectContext) -> Void, completion: ((Error?)-> Void)? = nil) {
if let rootContext = rootContext {
let localContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
localContext.parent = rootContext
localContext.obtainPermanentIdsBeforeSaving()
handler(localContext)
guard localContext.hasChanges else {
DispatchQueue.main.async {
completion?(nil)
}
return
}
do {
try localContext.save()
localContext.parent?.performAndWait {
try localContext.parent?.save()
}
DispatchQueue.main.async {
completion?(nil)
}
} catch let error as NSError {
print("❌ Core Data Save Error \(error), \(error.userInfo)")
DispatchQueue.main.async {
completion?(error)
}
}
}
}
Related
I am new in Swift and I want to delete all data from core data. I have seen several examples but in all of them persistentContainer is in AppDelegate and in my case persistentContainer is not in AppDelegate. It is in a different class as shown below:
class CoreDataStack: NSObject {
static let sharedInstance = CoreDataStack()
private override init() {}
func applicationDocumentsDirectory() {
if let url = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).last {
print(url.absoluteString)
}
}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Database")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
print(storeDescription)
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
In the AppDelegate method I am just calling it as
CoreDataStack.sharedInstance.applicationDocumentsDirectory()
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
CoreDataStack.sharedInstance.saveContext()
}
I tried this code but it does not work for me.
One of the solutions can be to use NSBatchDeleteRequest for all entities in the persistentContainer. You can add these methods to your CoreDataStack:
func deleteAllEntities() {
let entities = persistentContainer.managedObjectModel.entities
for entity in entities {
delete(entityName: entity.name!)
}
}
func delete(entityName: String) {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try persistentContainer.viewContext.execute(deleteRequest)
} catch let error as NSError {
debugPrint(error)
}
}
The whole code can be found here
Need to use NSBatchDeleteRequest for particular entity from persistentContainer
func deleteEntityData(entity : String) {
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do {
try CoreDataStack.sharedStack.mainContext.execute(deleteRequest)
CoreDataStack.sharedStack.saveMainContext()
} catch {
print ("There was an error")
}
}
I have detailed an answer in below link.
enter link description here
A more radical approach is to remove/recreate your persistent stores.
extension NSPersistentContainer {
func destroyPersistentStores() throws {
for store in persistentStoreCoordinator.persistentStores {
let type = NSPersistentStore.StoreType(rawValue: store.type)
try persistentStoreCoordinator.destroyPersistentStore(at: store.url!, type: type)
}
loadPersistentStores()
}
func loadPersistentStores() {
loadPersistentStores { _, error in
if let error { fatalError(error.localizedDescription) }
}
}
}
Usage: try container.destroyPersistentStores()
Source
I am saving different activities to Core Data. While the app is working i can easily retrieve this data. But after the app I closed the data is gone even if even after I explicitly save it using context.save(). Any ideas why this happens?
private func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
func retrieveTestEntity() -> TestEntity? {
let managedContext = getContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "TestEntity")
do {
let result = try managedContext.fetch(fetchRequest) as! [TestEntity]
if result.count > 0 {
// Assuming there will only ever be one Entity in the app.
return result[0]
} else {
return nil
}
} catch let error as NSError {
print("Retrieving user failed. \(error): \(error.userInfo)")
return nil
}
}
func saveActivity(_ activity: Activity) {
let managedContext = getContext()
guard let testEntity = retrieveTestEntity() else { return }
testEntity.activity.append(activity)
do {
print("Saving session...")
try managedContext.save()
} catch let error as NSError {
print("Failed to save session data! \(error): \(error.userInfo)")
}
}
I have method inserting some object into Core Data storage
func insert(_ e: Entity, completion: #escaping (Result<Entity, Error>) -> Void) {
storage.persistentContainer.performBackgroundTask { [weak self] context in
guard let self = self else { return }
}
}
I have also such test case and only last changeExpectation fails as not receiving this notification.
func test_whenMovieSaved_shouldBeRetrivable() {
// given
let dao = MoviesDao(storage: storage)
let movie = Movie(id: 1, title: "Test Title", posterPath: nil, overview: "Test Details", releaseDate: nil)
let insertExpectation = self.expectation(description: "Should insert entity.")
let loadExpectation = self.expectation(description: "Should load entity.")
let changeExpectation = self.expectation(forNotification: .NSManagedObjectContextDidSave, object: storage.persistentContainer.viewContext, handler: nil)
// when
dao.insert(movie) { (result) in
guard let e = try? result.get() else {
XCTFail("Should get valid entity back.")
return
}
XCTAssertEqual(movie, e)
insertExpectation.fulfill()
dao.load(id: "\(movie.id)") { result in
guard let e = try? result.get() else {
XCTFail("Should load valid entity back.")
return
}
XCTAssertEqual(movie, e)
loadExpectation.fulfill()
}
}
// then
wait(for: [insertExpectation, loadExpectation], timeout: 0.2)
wait(for: [changeExpectation], timeout: 2)
}
I have even tried to add something like this but it didn't force notification to send
self.storage.saveContext(context)
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: [NSInsertedObjectsKey: insertedObjectIds], into: [self.storage.persistentContainer.viewContext])
completion(.success(e))
I also have something like this while creating persistentCoordinator
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
container.viewContext.undoManager = nil
container.viewContext.shouldDeleteInaccessibleFaults = true
container.viewContext.automaticallyMergesChangesFromParent = true
I think I have finally found the solution to this problem. It was in stack.saveContext(context) method as I am using performBackgroundTask { context in
it requires to not only background context.save() to be called but also viewContext.save() should be called.
In my CoreDataStack now I have such methods
func saveContextAsync(_ context: NSManagedObjectContext) {
guard context != mainContext else {
saveContext()
return
}
context.perform {
do {
try context.save()
} catch let error as NSError {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
self.saveContextAsync(self.mainContext)
}
}
func saveContext(_ context: NSManagedObjectContext) {
guard context != mainContext else {
saveContext()
return
}
context.performAndWait {
do {
try context.save()
} catch let error as NSError {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
self.saveContext(self.mainContext)
}
}
func saveContextAsync() {
let context = mainContext
// Save all the changes just made and reset the context to free the cache.
context.perform {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
// Reset the context to clean up the cache and low the memory footprint.
context.reset()
}
}
func saveContext() {
let context = mainContext
// Save all the changes just made and reset the context to free the cache.
context.performAndWait {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
// Reset the context to clean up the cache and low the memory footprint.
context.reset()
}
}
This is how I save data to core, now I want to delete specific object from whole data. How can I do that?
This is how I save data:
func saveProductDetails(product : Product) {
// set user_id
product.user_id = UserDefaults.sharedInstace.getCustomerId()
//save data locally for the time been
let entity = NSEntityDescription.entityForName("Product", inManagedObjectContext: self.writeContext)
let category = NSEntityDescription.entityForName("Category", inManagedObjectContext: self.writeContext)
let brand = NSEntityDescription.entityForName("Brand", inManagedObjectContext: self.writeContext)
var entityProduct = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:self.writeContext)
var entityCategory = NSManagedObject(entity: category!, insertIntoManagedObjectContext:self.writeContext)
var entityBrand = NSManagedObject(entity: brand!,insertIntoManagedObjectContext: self.writeContext)
entityProduct = product.settingManagedObject(entityProduct)
entityProduct.setValue(String(Utill.sharedInstace.getTimeStamp()), forKey: "time_stamp")
entityProduct.setValue(Constant.Status.STATUS_NOT_VERIFIED, forKey: "status")
if product.categoryObject != nil{
product.categoryObject.user_id = UserDefaults.sharedInstace.getCustomerId();
entityCategory = product.categoryObject.settingManagedObject(entityCategory)
}
if product.brandObject != nil{
product.brandObject.user_id = UserDefaults.sharedInstace.getCustomerId();
entityBrand = product.brandObject.settingManagedObject(entityBrand)
}
entityProduct.setValue(entityCategory, forKey:"category")
entityProduct.setValue(entityBrand, forKey: "brand")
writeContext.performBlock {
do {
try self.writeContext.save()
self.managedContext.performBlock({
do{
try self.managedContext.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
})
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
return
}
}
}
This Product object has relationship to two others and I want to delete only specific object, not all. That means delete (which the product.purchase_id == "selected_purchse_id"), in UITableView.
How to do that?
Check this code for swift 3 core data operations
import CoreData
class CoreDataOperations: NSObject {
// MARK: Save data
func saveData() -> Void {
let managedObjectContext = getContext()
let personData = NSEntityDescription.insertNewObject(forEntityName: "Person", into: managedObjectContext) as! Person
personData.name = "Raj"
personData.city = "AnyXYZ"
do {
try managedObjectContext.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
// MARK: Fetching Data
func fetchData() -> Void {
let moc = getContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
do {
let fetchedPerson = try moc.fetch(fetchRequest) as! [Person]
print(fetchedPerson.count)
for object in fetchedPerson {
print(object.name!)
}
} catch {
fatalError("Failed to fetch employees: \(error)")
}
}
// MARK: Delete Data Records
func deleteRecords() -> Void {
let moc = getContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
let result = try? moc.fetch(fetchRequest)
let resultData = result as! [Person]
for object in resultData {
moc.delete(object)
}
do {
try moc.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
// MARK: Update Data
func updateRecords() -> Void {
let moc = getContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
let result = try? moc.fetch(fetchRequest)
let resultData = result as! [Person]
for object in resultData {
object.name! = "\(object.name!) Joshi"
print(object.name!)
}
do{
try moc.save()
print("saved")
}catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
// MARK: Get Context
func getContext () -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
}
You can get more from https://github.com/rajkumar24u/CoreDataOperations
I get EXC_BAD_INSTRUCTION error when I try to save context (second call to privateManagedObjectContext.hasChanges method).
Here is my method to save context:
func saveContext(#wait: Bool, completion: ((error: NSError?) -> ())?) {
if mainManagedObjectContext.hasChanges || privateManagedObjectContext.hasChanges {
if mainManagedObjectContext.hasChanges {
mainManagedObjectContext.performBlockAndWait({ () -> Void in
var error: NSError? = nil
if !self.mainManagedObjectContext.save(&error) {
println("Error saving main managed object context \(error)")
if completion != nil {
completion!(error: error)
}
return
}
})
}
let saveBlock: () -> Void = {
var error: NSError? = nil
if self.privateManagedObjectContext.save(&error) {
if completion != nil {
completion!(error: nil)
}
} else {
println("Error saving private managed object context \(error)")
if completion != nil {
completion!(error: error)
}
}
}
if self.privateManagedObjectContext.hasChanges {
if (wait) {
self.privateManagedObjectContext.performBlockAndWait(saveBlock)
} else {
self.privateManagedObjectContext.performBlock(saveBlock)
}
return
}
if completion != nil {
completion!(error: nil)
}
} else {
// No changes
if completion != nil {
completion!(error: nil)
}
}
}
I declared contexts like so:
privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateManagedObjectContext.persistentStoreCoordinator = storeCoordinator
mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainManagedObjectContext.parentContext = privateManagedObjectContext
What did I wrong here?