for the life of me I cannot figure out why I'm not getting anything saved to CoreData. I added CoreData.framework in the build phases but still nothing when I look at the Model.sqlite.
AppDelegate
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model")
container.loadPersistentStores(completionHandler: {
(storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
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)")
}
}
}
then in my MainViewController
var timer : Timer?
var distance = Measurement(value: 0, unit: UnitLength.meters)
var run: Run! //name of the entity
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
//...
private func saveRun() {
print("saveRun")
let newRun = Run(context: context)
print("got past core data")
newRun.distance = distance.value
newRun.duration = Int16(seconds)
newRun.timestamp = Date()
for locations in locationList {
let locationObject = Location(context: context)
locationObject.timestamp = locations.timestamp
locationObject.latitude = locations.coordinate.latitude
locationObject.longitude = locations.coordinate.longitude
newRun.addToLocations(locationObject)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
run = newRun
}
}
So... I'm a complete idiot. Its working with the code I posted. The screwup was with an error in my making code and not knowing how to use Liya properly. Thanks for everyones help
#Francesco Deliro
Good to know. I'll keep that in mind
Thanks for everyones help
Core Data expects to be run on a single thread. Even though that thread doesn’t have to be the main thread, Core Data was not designed to be accessed from different threads.
Related
I am new to Swift / IOS development and I wanted to follow along with a tutorial in an attempt to learn https://github.com/paulw11/CallKitTutorial
This code is old and was made for IOS 12 and I am trying to get it working for IOS 15. The error I am getting when running it is:
Exception NSException * "An instance of NSFetchedResultsController requires a non-nil fetchRequest and managedObjectContext" 0x0000000280c9da10
The default code that was in that project was
lazy var persistentContainer: NSPersistentContainer = {
let momdName = "CallKitTutorial"
let groupName = "group.me.wilko.CallKitTutorial"
let fileName = "demo.sqlite"
guard let modelURL = Bundle(for: type(of: self)).url(forResource: momdName, withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
guard let baseURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupName) else {
fatalError("Error creating base URL for \(groupName)")
}
let storeUrl = baseURL.appendingPathComponent(fileName)
let container = NSPersistentContainer(name: momdName, managedObjectModel: mom)
let description = NSPersistentStoreDescription()
description.url = storeUrl
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
public var context: NSManagedObjectContext {
return self.persistentContainer.viewContext
}
// MARK: - Core Data Saving support
public 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)")
}
}
}
I was getting this error with that code:
Fatal error: Unresolved error Error Domain=NSCocoaErrorDomain Code=512 "The file couldn’t be saved." UserInfo={reason=Failed to create file; code = 1}, ["reason": Failed to create file; code = 1]
So I changed it to this
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "CallKitTut")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
public var context: NSManagedObjectContext {
return self.persistentContainer.viewContext
}
public 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)")
}
}
And now I am getting this error
Exception NSException * "An instance of NSFetchedResultsController requires a non-nil fetchRequest and managedObjectContext" 0x0000600003585020
ViewController
class ViewController: UIViewController {
#IBOutlet weak var callerType: UISegmentedControl!
#IBOutlet weak var tableView: UITableView!
private var showBlocked: Bool {
return self.callerType.selectedSegmentIndex == 1
}
lazy private var callerData = CallerData()
private var resultsController: NSFetchedResultsController<Caller>!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.dataSource = self
self.tableView.delegate = self
self.loadData()
}
Help would be very much appreciated. All my code should be the same as the github repository besides the code I mentioned above that I changed. Thank you to anyone who can help
Fixed!
In the first version that I posted before I edited it, I didn't add the correct group name. In fact I didn't even know what app groups were and still don't. I was able to create one and put the correct name in and it started working!
I want to start learning to use NSAsynchronousFetchRequest by referring to https://www.marcosantadev.com/coredata_crud_concurrency_swift_2/
I have the simplest use case of NSAsynchronousFetchRequest
NSAsynchronousFetchRequest
// Call from UI main thread
func X() {
let fetchRequest = NSFetchRequest<NSPlainNote>(entityName: "NSPlainNote")
let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in
guard let result = asynchronousFetchResult.finalResult as? [NSPlainNote] else { return }
}
let coreDataStack = CoreDataStack.INSTANCE
// backgroundContext created via persistentContainer.newBackgroundContext()
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
do {
try backgroundContext.execute(asynchronousFetchRequest)
} catch let error {
backgroundContext.rollback()
error_log(error)
}
}
}
However, running the above code will get me the following error
CoreData`+[NSManagedObjectContext
Multithreading_Violation_AllThatIsLeftToUsIsHonor]:
If I modify the code by using NSFetchRequest directly.
NSFetchRequest
// Call from UI main thread
func X() {
let fetchRequest = NSFetchRequest<NSPlainNote>(entityName: "NSPlainNote")
let coreDataStack = CoreDataStack.INSTANCE
// backgroundContext created via persistentContainer.newBackgroundContext()
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
do {
let nsPlainNotes = try fetchRequest.execute()
} catch let error {
backgroundContext.rollback()
error_log(error)
}
}
}
Thing works fine. May I know, what's wrong with my NSAsynchronousFetchRequest version of code?
This is my CoreDataStack.swift for reference purpose.
CoreDataStack.swift
import CoreData
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "wenote")
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
// TODO: Not sure these are required...
//
//container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
//container.viewContext.undoManager = nil
//container.viewContext.shouldDeleteInaccessibleFaults = true
return container
}()
private(set) lazy var backgroundContext: NSManagedObjectContext = {
let backgroundContext = persistentContainer.newBackgroundContext()
// Similar behavior as Android's Room OnConflictStrategy.REPLACE
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
// TODO: Not sure these are required...
//backgroundContext.undoManager = nil
return backgroundContext
}()
}
Additional information
Do note that, in NSAsynchronousFetchRequest example, even if backgroundContext.perform is not used.
// Call from UI main thread
func X() {
let fetchRequest = NSFetchRequest<NSPlainNote>(entityName: "NSPlainNote")
let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { asynchronousFetchResult in
guard let result = asynchronousFetchResult.finalResult as? [NSPlainNote] else { return }
}
let coreDataStack = CoreDataStack.INSTANCE
// backgroundContext created via persistentContainer.newBackgroundContext()
let backgroundContext = coreDataStack.backgroundContext
do {
try backgroundContext.execute(asynchronousFetchRequest)
} catch let error {
backgroundContext.rollback()
error_log(error)
}
}
Same fatal error still occur.
Please note that, this fatal error will only be triggered, by editing the schema with Arguments Passed On Launch
-com.apple.CoreData.ConcurrencyDebug 1
I even try to execute some simple project from https://github.com/abhishekbedi1432/Core-Data-Asynchronous-Fetching/tree/master which is using NSAsynchronousFetchRequest.
If I do not enable -com.apple.CoreData.ConcurrencyDebug 1, the sample project from github able to perform asynchronous fetch without issue. However, once the -com.apple.CoreData.ConcurrencyDebug 1 is enabled, it will also be getting the same fatal error.
You have to make a new context either a child or a root parent for present Core Data container, like so:
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.parent = persistentContainer.viewContext
Exhaustive explanation of using multiple contexts is here.
For me, the async fetch request works as expected when I provide an estimatedResultCount value to it.
Before upgrading my apps to Swift 3 and iOS 10, I had no problems with using CoreData as a data store for simple objects. Lightweight Migrations were simple, saving and fetching was simple, ect. But ever since the recent upgrade, I have had nothing but trouble with CoreData.
My question is in two parts. First, does anyone know of any good resources to help me learn how CoreData works behind the scenes so I can get better at debugging it? Apple's docs are extremely limiting, and all the articles I read act like the new CoreData is so simple. I have decent experience with relational databases, so CoreData is adding an uncomfortable layer of abstraction for me.
Second, what is wrong with the following code? Lightweight migrations aren't working as they did before iOS 10, using this code. Objects are saving to CoreData (I can interact with them in app after saving), but then disappear after the app is restarted.
lazy var persistentContainer: NSPersistentContainer = {
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
let container = NSPersistentContainer(name: "MY_APP")
container.persistentStoreDescriptions = [description]
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
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)")
}
}
}
I'm using a separate file to abstract the storing of my objects:
class Repository
{
func getContext () -> NSManagedObjectContext
{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
func delete<T>(_ a: some T)
{
getContext().delete(a as! NSManagedObject)
}
// ----------- Password Repo ----------
func savePassword(name: String, hint: String, un: String) {
let context = getContext()
//retrieve the entity that we just created
let entity = NSEntityDescription.entity(forEntityName: "Password", in: context)
let transc = NSManagedObject(entity: entity!, insertInto: context)
//set the entity values
transc.setValue(name, forKey: "name")
transc.setValue(hint, forKey: "thing")
transc.setValue(un, forKey: "newThing")
//save the object
do {
try context.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
func updatePassword(pas: Password) -> Password
{
let context = getContext()
// sec.is_new = false
// TODO, add updates
// Try updating the model in the DB
do {
try context.save()
} catch {
print(error)
}
return pas
}
func fetchPasswords() -> [Password]
{
let context = getContext()
//create a fetch request, telling it about the entity
let fetchRequest: NSFetchRequest<Password> = Password.fetchRequest() as! NSFetchRequest<Password>
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
//go get the results
let searchResults = try getContext().fetch(fetchRequest)
return searchResults
} catch {
print("Error with request: \(error)")
}
return []
}
// ----------- End Password Repo ----------
// ----------- Hints Repo ----------
func saveHint (name: String, hint: String) {
let context = getContext()
//retrieve the entity that we just created
let entity = NSEntityDescription.entity(forEntityName: "Hint", in: context)
let transc = NSManagedObject(entity: entity!, insertInto: context)
//set the entity values
transc.setValue(value1, forKey: "some_string")
transc.setValue(value2, forKey: "some_thing")
//save the object
do {
try context.save()
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
func fetchHints() -> [Hint]
{
let context = getContext()
//create a fetch request, telling it about the entity
let fetchRequest: NSFetchRequest<Hint> = Hint.fetchRequest() as! NSFetchRequest<Hint>
let sortDescriptor = NSSortDescriptor(key: "my_key", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
do {
//go get the results
let searchResults = try getContext().fetch(fetchRequest)
return searchResults
} catch {
print("Error with request: \(error)")
}
return []
}
}
Then I call this Repository class like so:
Repository().savePassword(name: nameText.text!, hint: hintSoFarLabel.text!, un: "Hey")
The Repository class is working, until I restart the app...
I'm also trying to migrate to a new version of my Core Data Model which simply adds a non-optional String attribute (with default value), which would be a simple lightweight migration in iOS 9/Swift 2. What am I missing about swift 3 lightweight migrations?
I have no doubt that the problem is me not understanding CoreData in iOS 10 well enough. I've been a software engineer for a while, but I've only been working with iOS and Swift for a few months, so please be gentle. And thank's in advance!
Well, I feel like an idiot. I added a non-optional string and didn't give it a default value. I always gave default values to non-string types, but the way Xcode is set up made it appear that it would just give strings a default value of "" if no other was given.
Adding a default value to the new attribute allowed the migration to work in Swift3/iOS10. Noob mistake, but maybe it will help someone here in the future.
I am describing some color information in a Core Data diagram. The entity is a color, and the attributes are color components.
I am struggling with two aspects: how to delete an color object from the graph, and secondly, (bonus question?), how could I identify duplicate colors?
In my AppDelegate, I have a core data stack like this:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DD")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replacing this implementation with code to handle the error appropriately.
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
print(#function)
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)")
}
}
}
and where I'm trying to delete the color, I have this:
func deleteColor(_ sender:UIButton) {
let i : Int = (sender.layer.value(forKey: "index")) as! Int
print(#function, "object to delete: ", i)
let managedContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
colors.remove(at: i)
do {
try managedContext.save()
} catch let error as NSError {
print("Error While Saving Data: \(error.userInfo)")
}
recentColorCollection!.reloadData()
}
Variables are:
var colors = [RecentColorEntity]()
var colorEntity = "RecentColorEntity"
I'm not getting any errors, but the objects are not being deleted.. Can someone help me figure out what I'm doing wrong
colors.remove(at: i)
just removes the color from your colors array in memory. You need to delete the actual object, like this
context.delete(colorObject)
and save.
I am having issue while using private managedObjectContextfor saving data in background. I am new to CoreData. I am using Parent-Child approach for NSManagedObjectContext but facing several issues.
Errors arise when I tap reload button multiple times
Errors:
'NSGenericException', reason: Collection <__NSCFSet: 0x16e47100> was mutated while being enumerated
Some times : crash here try managedObjectContext.save()
Sometimes Key value coding Compliant error
My ViewController class
class ViewController: UIViewController {
var jsonObj:NSDictionary?
var values = [AnyObject]()
#IBOutlet weak var tableView:UITableView!
override func viewDidLoad() {
super.viewDidLoad()
getData()
saveInBD()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.saved(_:)), name: "kContextSavedNotification", object: nil)
}
//Loding json data from a json file
func getData(){
if let path = NSBundle.mainBundle().pathForResource("countries", ofType: "json") {
do {
let data = try NSData(contentsOfURL: NSURL(fileURLWithPath: path), options: NSDataReadingOptions.DataReadingMappedIfSafe)
do {
jsonObj = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
} catch {
jsonObj = nil;
}
} catch let error as NSError {
print(error.localizedDescription)
}
} else {
print("Invalid filename/path.")
}
}
**Notification reciever**
func saved(not:NSNotification){
dispatch_async(dispatch_get_main_queue()) {
if let data = DatabaseManager.sharedInstance.getAllNews(){
self.values = data
print(data.count)
self.tableView.reloadData()
}
}
}
func saveInBD(){
if jsonObj != nil {
guard let nameArray = jsonObj?["data#"] as? NSArray else{return}
DatabaseManager.sharedInstance.addNewsInBackGround(nameArray)
}
}
//UIButton for re-saving data again
#IBAction func reloadAxn(sender: UIButton) {
saveInBD()
}
}
**Database Manager Class**
public class DatabaseManager{
static let sharedInstance = DatabaseManager()
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
private init() {
}
func addNewsInBackGround(arr:NSArray) {
let jsonArray = arr
let moc = managedObjectContext
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = moc
privateMOC.performBlock {
for jsonObject in jsonArray {
let entity = NSEntityDescription.entityForName("Country",
inManagedObjectContext:privateMOC)
let managedObject = NSManagedObject(entity: entity!,
insertIntoManagedObjectContext: privateMOC) as! Country
managedObject.name = jsonObject.objectForKey("name")as? String
}
do {
try privateMOC.save()
self.saveMainContext()
NSNotificationCenter.defaultCenter().postNotificationName("kContextSavedNotification", object: nil)
} catch {
fatalError("Failure to save context: \(error)")
}
}
}
func getAllNews()->([AnyObject]?){
let fetchRequest = NSFetchRequest(entityName: "Country")
fetchRequest.resultType = NSFetchRequestResultType.DictionaryResultType
do {
let results =
try managedObjectContext.executeFetchRequest(fetchRequest)
results as? [NSDictionary]
if results.count > 0
{
return results
}else
{
return nil
}
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
return nil
}
}
func saveMainContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
let nserror = error as NSError
print("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
You can write in background and read in the main thread (using different MOCs like you do). And actually you're almost doing it right.
The app crashes on the try managedObjectContext.save() line, because saveMainContext is called from within the private MOC's performBlock. The easiest way to fix it is to wrap the save operation into another performBlock:
func saveMainContext () {
managedObjectContext.performBlock {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
let nserror = error as NSError
print("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Other two errors are a little more tricky. Please, provide more info. What object is not key-value compliant for what key? It's most likely a JSON parsing issue.
The first error ("mutated while being enumerated") is actually a nasty one. The description is pretty straight forward: a collection was mutated by one thread while it was enumerated on the other. Where does it occur?
One possible reason (most likely one, I would say) is that it is indeed a Core Data multithreading issue. Despite the fact that you can use several threads, you can only use core data objects within the thread they were obtained on. If you pass them to another thread, you'll likely run into an error like this.
Look through your code and try to find a place where such situation might occur (for instance, do you access self.values from other classes?). Unfortunately, I wasn't able to find such place in several minutes. If you said upon which collection enumeration this error occurs, it would help).
UPDATE:
P.S. I just thought that the error might be related to the saveMainContext function. It is performed right before a call to saved. saveMainContext is performed on the background thread (in the original code, I mean), and saved is performed on the main thread. So after fixing saveMainContext, the error might go away (I'm not 100% sure, though).
You are violating thread confinement.
You cannot write to CoreData in Background, and read in MainThread.
All operation on CoreData must be done in the same thread