Completely new to iOS Development and Swift so I hope you'll bear with me. I'm creating a simple note app based off the apple Notes app.
I am also completely new to core data. What I'm trying to do is save the text from the UITextView. Strangely enough this works but only after I remove this piece of code,
if let detail: Note = self.detailItem {
if let textView = self.textView {
textView.text = detail.noteText
}
create a new note, then reintroduce the code...
func configureView() {
// Update the user interface for the detail item.
if let detail: Note = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.timeStamp.description
}
if let detail: Note = self.detailItem {
if let textView = self.textView {
textView.text = detail.noteText
}
}
}
For a note that has already been created, this code is able to save the text in the textView. If I try to create a new note and then open it, the code above will crash the app.
This error comes up: Thread 1: EXC_BAD_ACCESS(code=1, address=0x0)
I think I have made a silly mistake like not initializing my model "Note.swift" class.
I'm using the boilerplate code for a master detail app with core data. I also have two "insertNewObject" functions. One is in the MasterViewController and the other is in the DetailViewController.
DetailViewController.swift
func insertNewObject(sender: AnyObject) {
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as! NSManagedObject
Here's the full code including the fetchedResultsController.
func insertNewObject(sender: AnyObject) {
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as! NSManagedObject
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
newManagedObject.setValue(self.textView.text, forKey: "noteText")
newManagedObject.setValue("I changed", forKey: "noteTitle")
// Save the context.
var error: NSError? = nil
if !context.save(&error) {
// 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.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
// MARK: - Fetched results controller
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Note", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master")
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
// 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.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillDisappear(animated: Bool) {
detailItem!.noteText = self.textView.text
detailItem!.noteTitle = "I changed"
var error: NSError? = nil
if !self.managedObjectContext!.save(&error) {
abort()
}
}
I was able to figure it out. The issue was that I didn't set a value for "noteText" when I first called func insertNewObject(sender: AnyObject). This resulted in a nil being set to textView.text and therefore crashing the app. Thanks for your help #BaseZen!
Related
My app has a Core Data entity (called Product) that has two attributes:
A name of type String
An image of type Binary Data (with "Allows External Storage" checked)
Right now since I am developing, I create a default object in my initial viewDidLoad with hardcoded values for name and image. After the object is created, I can pass it around and use it in my app without issue.
However, if I update the name attribute to a new string value, and re-run the app, the image attribute becomes nil (this behavior only happens after I re-run the app, or terminate and re-open it). Do you know what might be causing this behavior?
This is how I change the name value:
DataController.shared.viewContext.perform {
if mike {
product.name = "Mike"
} else {
product.name = "Bob"
}
try? DataController.shared.viewContext.save()
}
I am using a DataController class to interface with the persistent store:
class DataController {
static let shared = DataController(modelName: "my_model")
let persistentContainer: NSPersistentContainer
var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
private init(modelName: String) {
persistentContainer = NSPersistentContainer(name: modelName)
}
func configureContexts() {
viewContext.automaticallyMergesChangesFromParent = true
viewContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
}
}
I'm also using NSFetchedResultsController. This is how I set it up in my viewDidLoad:
fileprivate func setupFetchedResultsController() {
let fetchRequest:NSFetchRequest<Product> = Product.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "salesByVolume", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DataController.shared.viewContext, sectionNameKeyPath: nil, cacheName: "products")
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("The request could not be performed: \(error.localizedDescription)")
}
}
Thank you!
You might wanna try the following:
DataController.shared.viewContext.perform {
product.setValue("Mike", forKey: "name")
try? DataController.shared.viewContext.save()
}
You need to set value for a key to update it's value, then you can save it. I hope this helps
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 am having a strange crash while saving new data into Core Data. The relevant portions of the structure are as follows:
struct ParachuteDataController {
let databaseController = DatabaseController()
let context = DatabaseController().getContext()
func addParachute(newParachute: ParachuteData) {
let parachute = Parachute(context: context)
parachute.setValue(newParachute.diameter, forKey: CoreData.diameter)
parachute.setValue(newParachute.area, forKey: CoreData.area) //Measurement
parachute.setValue(newParachute.dragCoefficient, forKey: CoreData.dragCoefficient) //Measurement
parachute.setValue(newParachute.mass, forKey: CoreData.mass) //Measurement
parachute.setValue(newParachute.material.rawValue, forKey: CoreData.material) //String
parachute.setValue(newParachute.measurementType.rawValue, forKey: CoreData.measurementType)
parachute.setValue(newParachute.shape.rawValue, forKey: CoreData.shape) //String
parachute.setValue(newParachute.vented, forKey: CoreData.vented) //Bool
if newParachute.vented {
parachute.setValue(newParachute.ventDiameter, forKey: CoreData.ventDiameter)//Optional Measurement
}
saveInCoreData()
}
private func saveInCoreData() {
do {
try DatabaseController().getContext().save()
} catch let error {
print(error)
}
}
}
class DatabaseController: NSObject {
static let sharedInstance = DatabaseController()
override init() {}
public func getContext() -> NSManagedObjectContext {
return DatabaseController.sharedInstance.persistentContainer.viewContext
}
public var fetchPredicate : NSPredicate?
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Recovery")
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)")
}
}
}
}
CoreData is simply a constant Struct.
My model has attributes for each of the listed items in addParachute. When I try to add all of the items to the Core Data model, I get the following crash:
NSManagedObjectContextObjectsDidChangeNotification. -[NSMeasurement compare:]: unrecognized selector sent to instance 0x1c04211e0 with userInfo (null)
2018-04-03 08:28:48.533419-0400 Recovery[1210:643911] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSMeasurement compare:]: unrecognized selector sent to instance
If I pull out any ONE item of the attributes, it saves fine. It does not matter which item it is. I can't even narrow it down to any particular attribute as a result. I have other entities which save measurements, set up essentially identically and there are no problems.
Suggestions? Thanks in advance.
Edit:
I am on the right track as to the actual cause of my crash thanks to this post:
Extremely Odd Crash When saving a transformable NSAttributedString into Core Data.
I hadn't found it earlier as it is an Objective C question. However, the underlying issue is the same: my FetchedResultsController updating as a result of the Core Data save.
In my View Controller class, I have the following function:
func configureFetchedResultsController() {
let context = databaseController.getContext()
let parachutesFetchRequest = NSFetchRequest<Parachute>(entityName: CoreData.parachute)
let primarySortDescriptor = NSSortDescriptor(key: CoreData.material, ascending: true)
let secondarySortDescriptor = NSSortDescriptor(key: "diameter.value", ascending: true)
let sortDescriptor = [primarySortDescriptor, secondarySortDescriptor]
parachutesFetchRequest.sortDescriptors = sortDescriptor
self.fetchedResultsController = NSFetchedResultsController<Parachute>(
fetchRequest: parachutesFetchRequest,
managedObjectContext: context,
sectionNameKeyPath: CoreData.material,
cacheName: nil)
self.fetchedResultsController.delegate = self
}
The cause of the crash is let secondarySortDescriptor = NSSortDescriptor(key: "diameter", ascending: true). diameter is a Measurement and this does not seem to play well with the FRC's sorting. When I changed the code to let secondarySortDescriptor = NSSortDescriptor(key: "diameter.value", ascending: true) in hopes of having it sort by the value, I get the following error:
NSManagedObjectContextObjectsDidChangeNotification. [ valueForUndefinedKey:]: this class is not key value
coding-compliant for the key value. with userInfo {
NSTargetObjectUserInfoKey = " value: 0.304800 unit: m";
NSUnknownUserInfoKey = value; }
Is it possible to sort by a Measurement and, if so, how?
Thanks for all who provided suggest that helped me narrow the issue.
As I indicated in the edit, the issue had nothing to do with the Core Data save. The crash is caused once the save occurs and the FetchedResultsController(frc) is updated. It is resorted based on my sortDescriptors, one of which is a Measurement. While I have not been able to determine what, if any, syntax is necessary to sort on the Measurement, sorting on another attribute in Core Data prevents the crash. For someone looking into this in the future, I tried sorting on the Measurement itself and the value of the measurement, both of which caused runtime crashes. So, I am marking this question as solved for my initial question.
If you get a crash due to a compare statement, look to your frc as the cause.
I seem to be getting this error.
*** -[NSDecimalNumber retain]: message sent to deallocated instance 0x174222220
The first time I run the app, the code executes fine, but if I got back to that VC, the app crashes with the message above. I did some research and I think it means that some how that NSDecimal is being released. It's very odd, as I have 3 other decimals all set up the exact same way that are working.
The decimals are being stored in Core Data, and then being set to a label in my VC in side the cellForIndexAt method.
print("\(historyEntry.newAmount) new amount")
The very first time I get back the correct amount. But the second time or if I try move the tableview the app crashes with the message above. What would cause the decimal to release itself?
EDIT
I have a CoreDataStack swift file and the managedContext is being created like this:
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
I am fetching the object like this:
// load the data
let fetchRequest: NSFetchRequest<Statement> = Statement.fetchRequest()
fetchRequest.predicate = NSPredicate(format:"person.name == %# AND amountOwed >= 0", personName)
let sort = NSSortDescriptor(key: #keyPath(Statement.amountOwed), ascending: true)
fetchRequest.sortDescriptors = [sort]
positiveFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.managedContext, sectionNameKeyPath: nil, cacheName: nil)
do{
try positiveFetchedResultsController.performFetch()
}catch let error as NSError{
print("Fetching error: \(error), \(error.userInfo)")
}
positiveFetchedResultsController.delegate = self
I think pass the fetched object that I am using to another ViewController and access its properties like this:
print("\(historyEntry.changeAmount) change amount") // gives me back the correct amount that was saved evrytime.
This attribute however crashes after the first time
print("\(historyEntry.newAmount) new amount") // first time correct, after that error message from above.
EDIT
Here is the CoreDataStack class
import Foundation
import CoreData
class CoreDataStack {
private let modelName: String
init(modelName: String) {
self.modelName = modelName
}
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
private lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: self.modelName)
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
}
}
return container
}()
func saveContext () {
guard managedContext.hasChanges else { return }
do {
try managedContext.save()
} catch {
let nserror = error as NSError
print("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
What are the names of your NSDecimalNumber properties? If any of the property names start with "new", that will confuse ARC and lead to problems like this.
To quote from Apple's Introduction to ARC,
You cannot give an accessor a name that begins with new. This in turn
means that you can’t, for example, declare a property whose name
begins with new unless you specify a different getter.
I would try and make sure that the managed object context is not being deallocated and reallocated when you reenter your VC. That could explain why your MOCs are deallocated.
Other than that, I would enable "Zombie Objects" under Product/Scheme/Edit Scheme/Run/Diagnostics and see if that provides some more hints.
In my application I have used one class (say CoredataHandler.swift) for storing and retrieving objects. I have followed this tutorials . I have used Strategy 2: Parent/Child Managed Object Contexts.
But Objects are not stored in the coredata. Instead of using NSOperation I have used normal class object.
class CoreDataHandler: NSObject {
//static var sharedInstance:CoreDataHandler = CoreDataHandler()
var privateManagedObjectContext:NSManagedObjectContext?
var mainManagedObjectContext:NSManagedObjectContext?
override init() {
print("core data handler constructor called")
super.init()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let privateManagedObjectContextlocal = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
privateManagedObjectContextlocal.parentContext = appDelegate.managedObjectContext
self.privateManagedObjectContext = privateManagedObjectContextlocal
self.mainManagedObjectContext = appDelegate.managedObjectContext
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: #selector(CoreDataHandler.managedObjectContextDidSave(_:)), name: NSManagedObjectContextDidSaveNotification, object: privateManagedObjectContext)
}
private func insertData(entityName:String,dataDictionary:Dictionary<String, AnyObject?>){
synced(self) { () -> () in
// let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let entityDescription = NSEntityDescription.entityForName(entityName, inManagedObjectContext: self.privateManagedObjectContext!)
let newPerson = NSManagedObject(entity: entityDescription!, insertIntoManagedObjectContext: self.privateManagedObjectContext!)
for (myKey, myVal) in dataDictionary {
if myVal is Int {
if let result_number = myVal as? NSNumber
{
let result_string = "\(result_number)"
newPerson.setValue(result_string, forKey: myKey)
}
}else{
newPerson.setValue(myVal, forKey: myKey)
}
}
//print("insertData",newPerson)
do {
if ((self.privateManagedObjectContext?.hasChanges) != nil){
try self.privateManagedObjectContext!.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()
}
}
}
// MARK: - Insert
func insertOfferObjects(arrOffer : Array<FoodItem>?) {
synced(self) { () -> () in
//Step1: Adding Offer Items
if let _ = arrOffer {
var foodArr:Array<NSManagedObject> = Array()
for foodObj : FoodItem in arrOffer! {
let offerItemEntity = self.createFoodItemEntity(foodObj)
foodArr.append(offerItemEntity)
}
self.insertData("OfferCategory", dataDictionary: ["categoryTitle": "Offers", "foodItemArray": NSOrderedSet(array: foodArr)])
}
}
}
Values are not stored in the coredata. Please provide me the best way to do.
EDITED: Update :: From Answers, Need to save parent when child context
is saved
self.privateManagedObjectContext?.performBlockAndWait({
if ((self.privateManagedObjectContext?.hasChanges) != nil){
do {
print("It has changes...............")
try self.privateManagedObjectContext!.save()
self.mainManagedObjectContext?.performBlock({
do {
try self.mainManagedObjectContext!.save()
}catch{
}
})
}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()
}
}
})
Saving a child context just pushes those changes up to the parent context. Unless you're also saving the parent context (which pushes changes to the persistent store) then your changes will not be written to disk.
From the NSManagedObjectContext class reference:
When you save changes in a context, the changes are only committed “one store up.” If you save a child context, changes are pushed to its parent. Changes are not saved to the persistent store until the root context is saved. (A root managed object context is one whose parent context is nil.)
If you're new to core data, I'd suggest not worrying about concurrency and multiple contexts unless you actually have a problem that requires that to solve it. Unless you're dealing with thousands of records or you're interested in creating editing contexts for reversible changes, a single main thread context will do everything you need.