CoreData CRUD operations testing and no NSManagedObjectDidSave notification - ios

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

Related

Why do I have a crash while saving parent context?

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

How to delete all data from core data when persistentContainer is not in App delegate method in swift

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

Core Data is not persisting even after context.save()

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

Entity is empty using Core data in Today extension

I'd like to show photos in today extension using Core data. But, entity is empty. I don't any idea about that.
here is what I'm doing:
add app group in both of targets (app and extension)
check target membership in xcdatamodeld.
create CoreDataStack class for using Core data in today extension.
class CoreDataStack {
private init() {
NotificationCenter.default.addObserver(self,
selector: #selector(mainContextChanged(notification:)),
name: .NSManagedObjectContextDidSave,
object: self.managedObjectContext)
NotificationCenter.default.addObserver(self,
selector: #selector(bgContextChanged(notification:)),
name: .NSManagedObjectContextDidSave,
object: self.backgroundManagedObjectContext)
}
static let sharedStack = CoreDataStack()
var errorHandler: (Error) -> Void = {_ in }
#available(iOS 10.0, *)
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "name")
container.loadPersistentStores(completionHandler: { [weak self](storeDescription, error) in
if let error = error {
NSLog("CoreData error \(error), \(error._userInfo)")
self?.errorHandler(error)
}
})
return container }()
#available(iOS 10.0, *)
lazy var viewContext: NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
#available(iOS 10.0, *)
func performBackgroundTask(_ block: #escaping (NSManagedObjectContext) -> Void) {
self.persistentContainer.performBackgroundTask(block)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
lazy var libraryDirectory: URL = {
let urls = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group...")
return urls!
}()
lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = Bundle.main.url(forResource: "name", withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let coordinator = NSPersistentStoreCoordinator(managedObjectModel:
self.managedObjectModel)
let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.kr....")
let urls = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.kr....")!.path
let url = directory!.appendingPathComponent("name.sqlite")
do {
try coordinator.addPersistentStore(ofType:
NSSQLiteStoreType,
configurationName: nil,
at: url,
options: nil)
} catch {
// Report any error we got.
NSLog("CoreData error \(error), \(error._userInfo)")
self.errorHandler(error)
}
return coordinator }()
lazy var backgroundManagedObjectContext: NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
var privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedObjectContext.persistentStoreCoordinator = coordinator
return privateManagedObjectContext }()
#available(iOS 9.0, *)
lazy var managedObjectContext: NSManagedObjectContext = {
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator
return managedObjectContext }()
#objc func mainContextChanged(notification: NSNotification) {
backgroundManagedObjectContext.perform { [unowned self] in
self.backgroundManagedObjectContext.mergeChanges(fromContextDidSave: notification as Notification)
}
}
#objc func bgContextChanged(notification: NSNotification) {
managedObjectContext.perform{ [unowned self] in
self.managedObjectContext.mergeChanges(fromContextDidSave: notification as Notification)
}
}
func saveContext () {
if #available(iOS 10.0, *) {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
} else {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}
}
add function in today extension view controller
func fetchData() {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Quotes")
if #available(iOS 10.0,*) {
let context = CoreDataStack.sharedStack.viewContext
do {
let results = try context.fetch(fetchRequest)
self.quotes = results as? [Quotes]
print("result = \(results)")
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
return
}
}else {
//for ios 9.3~
let context = CoreDataStack.sharedStack.managedObjectContext
do {
let results = try context.fetch(fetchRequest)
self.quotes = results as? [Quotes]
print("result = \(results)")
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
return
}
}
for i in self.quotes! {
let image = UIImage(data: i.img! as Data, scale: 1.0)
self.quotesImgs.append(image!)
print("is that really image? \(image)")
print("app in for in sytax for coredata = \(self.quotesImgs)")
}
print("quotes \(self.quotes)")
}
print is result = [] / quotes Optional([])
What am I missing??

How can I delete object from core data in swift 3

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

Resources