I am trying to save data in CoreData. When the app is running everything is ok. I receive info that data is saved and receive messages that it is fetching correct. But when I close the app data just disappeared somewhere.
When I created the project I do not check Core Data, so I added xcdatamodel, import CoreData everywhere, updated AppDelegate with the correct NSPersistentContainer name (the name is name of my xcdatamodel) also in Project-General-Frameworks added CoreData.framework.
Here is part of saving, fetching, and deleting data. The file is separate from VC. I do not receive any type of errors.
In my VC I just call savedata(), to save the data. It works before the app is closed.
import UIKit
import CoreData
var cgsTeams = [TeamsCoreData]()
func savedata() {
saveTeams { (complete) in
if complete {print("TeamsSaved")}
}
}
func saveTeams(completion: (_ finished: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else {return}
let privateManagedContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedContext.parent = managedContext
for team in teams {
let teamsCoreData = TeamsCoreData(context: privateManagedContext)
teamsCoreData.team = team.name
teamsCoreData.score = Int32(team.score)
teamsCoreData.number = Int32(team.number)
do{
try privateManagedContext.save()
debugPrint("Succesfully saved teamsCoreData")
completion(true)
} catch{
debugPrint("Could not save - \(error)")
completion(false)
}
}
}
func fetchTeams(completion: (_ complete: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let fetchRequest = NSFetchRequest<TeamsCoreData>(entityName: "TeamsCoreData")
do {
cgsTeams = try managedContext.fetch(fetchRequest)
print("cgsGameRules fetched")
teams = [team]()
for cgsTeam in cgsTeams {
print("team - \(cgsTeam.team!) added")
teams.append(team(name:cgsTeam.team!, number: Int(cgsTeam.number), score: Int(cgsTeam.score)))
}
if cgsTeams.count > 1{completion(true)} else {completion (false); print("No teams")}
} catch {
debugPrint("Could not fetch: \(error.localizedDescription)")
completion(false)
}
}
func deleteTeams(){
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let fetchRequest = NSFetchRequest<TeamsCoreData>(entityName: "TeamsCoreData")
let objects = try! managedContext.fetch(fetchRequest)
for obj in objects {
managedContext.delete(obj)
}
do {
try managedContext.save()
} catch {
print("error on delete Team")
}
}
When you save changes in Core Data, the context saves only to its parent context. If it doesn't have a parent context, it saves changes to the persistent store file. You're saving changes on privateManagedObjectContext, which is a child context of viewContext. But you're never saving changes on viewContext. So your child context is telling the parent context about the changes, but the parent never saves those changes anywhere.
You need to either (a) save changes on viewContext, or (b) make privateManagedObjectContext its own stand-alone context, not a child context.
Related
My app's users have reported their data got lost when they updated to a new version. The update has nothing to do with Coredata (nothing's changed about it). So, I'm really puzzled why they experience that. I would like your input so I get a clue where I should start looking. I personally have never experienced the data loss on my device, but I am using my app directly installed from Xcode.
// MARK: - Core Data stack
func getManagedObjectContext() -> NSManagedObjectContext {
if managedObjectContext.persistentStoreCoordinator == nil {
let coordinator : NSPersistentStoreCoordinator = getPersistentStoreCoordinator()
managedObjectContext.persistentStoreCoordinator = coordinator
}
return managedObjectContext
}
func getManagedObjectModel() -> NSManagedObjectModel {
guard let modelURL = Bundle.main.url(forResource: "Model", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
return NSManagedObjectModel.init(contentsOf: modelURL)!
}
func getPersistentStoreCoordinator() -> NSPersistentStoreCoordinator {
guard let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else {
fatalError("Unable to resolve document directory")
}
let storeURL = docURL.appendingPathComponent("Model.sqlite")
if managedObjectModel == nil {
managedObjectModel = getManagedObjectModel()
}
let persistentStoreCoordinator = NSPersistentStoreCoordinator.init(managedObjectModel: managedObjectModel!)
do {
// MAIN LINE OF CODE TO ADD
let mOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true]
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: mOptions)
//The callback block is expected to complete the User Interface and therefore should be presented back on the main queue so that the user interface does not need to be concerned with which queue this call is coming from.
return persistentStoreCoordinator
} catch {
fatalError("Error migrating store: \(error)")
}
}
func saveCoreData (managedObjectContext: NSManagedObjectContext) {
DispatchQueue.main.async {
if managedObjectContext.hasChanges {
var saveDebugTextWhenDone = false
do {
try managedObjectContext.save()
if self.currentParentRecordNameNeedsUpdate {
NotificationCenter.default.post(name: NSNotification.Name("cloudUpdatedCoreDataCurrentParentRecordName"), object: self)
self.currentParentRecordNameNeedsUpdate = false
}
} 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
}
}
}
}
I want to delete a specific item in a core data binary data set. I have added all of my code below. I tried to follow what I was doing to save the data which worked but trying to apply it to deleting is not currently working. Do not know how to proceed to solve this. I am getting a runtime error at context delete at the helper class.
BASE CLASS
func deleteImage(imageNo:Int) {
// first check the array bounds
let info = DataBaseHelper.shareInstance.fetchImage()
if info.count > imageNo {
// check if the data is available
if let imageData = info[imageNo].img {
DataBaseHelper.shareInstance.deleteImage(data: imageData)
} else {
// no data
print("data is empty")
}
} else {
// image number is greater than array bounds
print("you are asking out of bounds")
}
}
override func viewDidLoad() {
super.viewDidLoad()
deleteImage(imageNo: 2)}
HELPER CLASS
class DataBaseHelper {
static let shareInstance = DataBaseHelper()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func deleteImage(data: Data) {
let imageInstance = Info(context: context)
imageInstance.img = data
do {
try context.delete(data)
print("Image is saved")
} catch {
print(error.localizedDescription)
}
}}
error
What you are doing wrong above is
func deleteImage(data: Data) {
// here you create a new object with context
let imageInstance = Info(context: context)
// assigning data to object img property
imageInstance.img = data
// deleting the unsaved object which cause error
do {
try context.delete(data)
print("Image is saved")
} catch {
// this part will be execute because object is not saved
print(error.localizedDescription)
}
}
So the thing is clear first you should have saved object to delete it otherwise it cause error.
So how to delete a specific object
class DataBaseHelper {
static let shareInstance = DataBaseHelper()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
// pass the object in parameter it will delete the specific info object from CoreData that you will provide in argument
func deleteInfo(info: Info) {
do {
try context.delete(info)
print("Image is saved")
} catch {
print(error.localizedDescription)
}
}
// if you want to remove image but not the info row/object just assign it nil and save again
func deleteImage(info: Info) {
info.img = nil
do {
try context.save()
print("Image is removed but info still here")
} catch {
print(error.localizedDescription)
}
}
}
this how you delete image
if let info = info[imageNo]{
DataBaseHelper.shareInstance.deleteImage(info: info)
}
I have an app that uses Core Data but now I'm adding CloudKit to my app and I'm having issues on synchronising all the old Core Data.
Does anyone have the same issue? Does anyone have any idea how to synchronise old Core Data?
func fetch(completion: (_ comple: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let fetchRequest = NSFetchRequest<Goal>(entityName: "Goal")
do {
goalPasswords = try managedContext.fetch(fetchRequest)
completion(true)
} catch {
debugPrint("Fetch Error \(error.localizedDescription)")
completion(false)
}
}
I have an application using Core Data as its database, I'm trying to write test cases for my DB queries which I have written inside my NSManagedObject subclass and they will return true if the insertion works successfully. I'm trying to insert an object into my entity like this:
func testDBInser() {
let appDelegate: AppDelegate = AppDelegate()
let managedObjectContext = appDelegate.persistentContainer.viewContext
let myObject: MyManagedObject = MyManagedObject(context: managedObjectContext)
myObject.id = 10
myObject.name = "testObj"
XCTAssertTrue(UserProfileModel().insertObjectToUserProfile(myObject))
}
But I'm getting this error for my test:
An NSManagedObject may only be in (or observed by) a single NSManagedObjectContext
Life is a lot easier with CoreStore:
func testInsert {
// 1. Arrange
let dataStack: DataStack = {
let dataStack = DataStack(xcodeModelName: "ModelName")
do {
try dataStack.addStorageAndWait()
} catch let error {
XCTFail("Cannot set up database storage: \(error)")
}
return dataStack
}()
// 2. Action
do {
try dataStack.perform(synchronous: { transaction in
let object = transaction.create(Into<TestObject>())
object.name = "Test"
})
} catch let error {
XCTFail("Cannot perform database transaction: \(error)")
}
// 3. Assert
do {
try dataStack.perform(synchronous: { transaction in
guard transaction.fetchOne(From<TestObject>(), Where("name", isEqualTo: "Test")) != nil else {
XCTFail("Cannot get database object")
}
})
} catch let error {
XCTFail("Cannot perform database transaction: \(error)")
}
}
I have been struggling with this problem for exactly 8 days now. So I believe it's time to ask for help for the Gurus over here.
Ok, so I am trying to implement an app following Uncle Bob's Clean Architecture, so I have the ViewControllers, Models, Interactors, Repositories and Presenters all setup.
My AppDelegate does not have any traces of CoreData in it, nothing. All that is done in the MyAppCoreData class.
The repository is injected in the AppDelegate and the Interactor access the injected object to access not only CoreData stuff, but also Parse and another private API. The Parse and private API are working great.
The CoreData repository also "works". It does not throw any exception. But no data is inserted in CoreData and when I fetch, it's empty.
I believe I am having some problem with the Persistent Store Coordinator + Main Context + Private Context... but have not yet managed to find out what it is.
So let's go to the code:
AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let container = Container() { c in
c.register(UserDataStore.self) { _ in ParseRepository() }
c.register(BabyNamesDataStore.self) { _ in BabyNameRepository.sharedInstance }
c.register(GenericRepository.self) { r in
GenericRepository(_userDataStore: r.resolve(UserDataStore.self)!, _babyNameDataStore: r.resolve(BabyNamesDataStore.self)!)
}
}
...
Then, my interactors have the following init() function:
class MainInteractor: MainInteractorInput
{
var output: MainInteractorOutput!
var worker: GenericRepository?
init() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
worker = appDelegate.container.resolve(GenericRepository.self)
}
Then, finally, my CoreDataStore is a singleton (before it wasn't and I could see that it was being instantiated twice for some reason by Swinject):
class BabyNamesCoreDataStore : BabyNamesDataStore{
var mainManagedObjectContext: NSManagedObjectContext
var privateManagedObjectContext: NSManagedObjectContext
static let sharedInstance = BabyNamesCoreDataStore()
init()
{
QL1("BabyNamesCoreDataStore init")
// This resource is the same name as your xcdatamodeld contained in your project.
guard let modelURL = NSBundle.mainBundle().URLForResource("BabyNameMe", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOfURL: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainManagedObjectContext.persistentStoreCoordinator = psc
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let docURL = urls[urls.endIndex-1]
/* The directory the application uses to store the Core Data store file.
This code uses a file named "DataModel.sqlite" in the application's documents directory.
*/
let storeURL = docURL.URLByAppendingPathComponent("BabyNameMe.sqlite")
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
fatalError("Error migrating store: \(error)")
}
//let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateManagedObjectContext.parentContext = mainManagedObjectContext
}
deinit
{
do {
try self.mainManagedObjectContext.save()
} catch {
fatalError("Error deinitializing main managed object context")
}
}
func fetchBabyNames(completionHandler: (babyNames: [BabyNames], error: StoreError?) -> Void) {
privateManagedObjectContext.performBlock {
do {
let fetchRequest = NSFetchRequest(entityName: "ManagedBabyNames")
let predicate = NSPredicate(format: "liked == nil")
fetchRequest.predicate = predicate
let results = try self.privateManagedObjectContext.executeFetchRequest(fetchRequest) as! [ManagedBabyNames]
let result = results.map { $0.toBabyName() }
completionHandler(babyNames: result, error: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
completionHandler(babyNames: [], error: StoreError.CannotFetch("Cannot fetch baby names"))
}
}
}
func insertBabyNames(babyNames: BabyNames, completionHandler: (error: StoreError?) -> Void) {
privateManagedObjectContext.performBlock {
do {
let managedBabyNames = NSEntityDescription.insertNewObjectForEntityForName("ManagedBabyNames", inManagedObjectContext: self.privateManagedObjectContext) as! ManagedBabyNames
managedBabyNames.fromBabyName(babyNames)
try self.privateManagedObjectContext.save()
completionHandler(error: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
completionHandler (error: StoreError.CannotCreate("Cannot create baby names with id \(babyNames.id)"))
}
}
}
...
And this is pretty much it. I get no exceptions but it is simply not working.
Could anyone please help this desperate man ? :)
Thanks!
UPDATE
I am following Raymund's Clean Store idea to implement my own, found here: https://github.com/Clean-Swift/CleanStore
I found out that the function that receives the response from the Presenter, inside the View Controller is NOT in the main thread. So I had to add this:
func displaySomething(viewModel: LoginViewModel) {
if viewModel.loginStatus {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.prepareSlideOutVC()
//When user is not logged in, the SlideOut Menu is not loaded. So when he logs in, we must load it.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
dispatch_async(dispatch_get_main_queue()) {
self.router.navigateToMainScene()
}
}
}
}
adding the dispatch_async sorted out the problem in a few cases, but not all of them... still trying to figure out.
So, basically, after I login and receive the response from the Presenter, I activate the segue to go to MainVC but that was in a different thread.
Still trying to work this out.