I created an application with Core Data. In the AppDelegate I have this code:
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "Teste")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// 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.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
I'd like to remove the fatalError and know how the typical reasons for an error can occurs that they talk about?
So you can remove or comment out the fatalError("Unresolved error \(error), \(error.userInfo)") line of code. If you read the first line in the comments "Replace this implementation with code to handle the error appropriately", it tells us that we are meant to use our own implementation, the fatalError() method is for your development purposes to be notified in debug that something went awry.
Typically caused by a misconfiguration of your project, or heaven forbid a failure to migrate between versions. In these situations you would hopefully have caught and fixed these issues before your project goes live, but if the error occurred because the device has run out of storage space or a permissions issue it would be best just to observe this error and whenever it occurs inform the user through an alert.
Related
I'm trying to produce an error in transaction and my question is when does transaction produces error ? I tried inserting random non existent collection and document, go offline, still it doesn't catch any error.
In what conditions does transaction error out ?
var db = Firestore.firestore()
let ref = db.collection("foo").document("bar")
db.runTransaction({ transaction, errorPointer -> Any? in
var document: DocumentSnapshot
do {
try document = transaction.getDocument(ref)
} catch let fetchError as NSError {
// no Error here even if ref doesn't exist or I go offline
errorPointer?.pointee = fetchError
return nil
}
return nil
}) { _, error in
if let error = error {
print("Transcation Completion Error: \(error)")
} else {
print("Transaction Succeeded!")
}
}
If you just want to test error handling then just throw your own error from within the transaction. The transaction closure has two arguments, the transaction object and an error pointer. Assign the error pointer an NSError and handle it in the completion block.
errorPointer?.pointee = NSError(domain: "yourDomain", code: 0, userInfo: nil)
Beyond this, a transaction could fail for a number of reasons, such as performing a read operation after a write operation, a network error, exceeding the allotted data-usage limit, or too many failed attempts at retrying the transaction (because the underlying documents were modified outside the transaction). Further reading at link below.
https://firebase.google.com/docs/firestore/manage-data/transactions#transaction_failure
According to Firebase documentation, a transaction can fail following the next options:
After the write operations, the transaction contains read operations.
Before any write operations, read operations must always occur first.
The transaction read a document that had been changed outside of it.
In this instance, the transaction is restarted automatically. A
certain number of times the transaction is retried.
The transaction's request size exceeds the 10 MiB limit.
The size of a transaction is determined by the size of the documents
and index items that it modifies. This contains the size of the
target document and the sizes of the index items eliminated due to
the operation.
When a transaction fails, it generates an error and does not write any data to the database. You don't have to roll back the transaction because Cloud Firestore does it for you.
Also, I would like to suggest you to check the NSError for Swift, here is a Github Repository that provides information of NSError, as well as this documentation that also describes the NSError of how to handle failed transactions and give appropriate error messages to the user as a feedback.
iOS Core Data - Serious application error - attempt to insert nil
Hello,
My app runs actualy stable, but in seldom cases it crashes with this error message...
2019-04-02 20:48:52.437172+0200 myAppName[4422:1595677] [error] error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)
2019-04-02 20:48:52.438246+0200 myAppName[4422:1595677] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'
...when it tries to save the current context (this part in my code is still in objc):
- (void)saveChanges
{
dispatch_async(dispatch_get_main_queue(), ^{
NSError *err = nil;
BOOL succesful = [self->context save:&err];
if (!succesful)
{
NSLog(#"ERROR MESSAGE DURING SAVING CONTEXT: %#", [err localizedDescription]);
}
});
}
'seldom' means:
Most Customers do never experience the issue, for few customers it happens several times per day.
I was able to produce it 2 times during the last two days although I tried several ways to force this error (see below).
This is the setup:
The respective data is in one Entity (table)
A NSFetchedResultsController shows the data in an UITableView
User can hit a button to add a new record.
New record has only some basic data and initiates two API calls to two webservers
Each webserver response does update the record
After both are done (or were cancelled due to timeout), I call the saveChanges function from above only once.
All functions use the same context created by NSPersistentContainer as follow (this part is already in swift)
#objc lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "myAppName")
let description = NSPersistentStoreDescription(url: SomeHelper.urlForFileInDocFolder("storev7.data"))
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
If I could reproduce the error somehow, I could find an appropriate solution, but as it almost never happens, I'm stuck.
Do you have an idea how I could reproduce the error from above? Or do you have a clue what could cause the error in my case?
What I tried already to reproduce the error:
Create hundereds of record
Create hundereds of record in a few seconds
Create hundereds of record during switching internet connection on / off / on / off /...
Create hundereds of record during mixed from background and main thread (I removed the dispatch from saveChanges for that)
Create hundereds of record with different delays on the API (added random sleep function on the webserver)
Long time execution, the app run for 24 hours on a real device and created record each 2 minutes
Mixes of all of them
NSManagedObjects are restricted to a single queue. They are not thread-safe for reading or writing. Reading an NSManagedObject can cause a fault, which is a write operation. That means that NSManagedObjects retrieved from a main queue context (like viewContext) cannot be passed to other queues.
The details of all of this are discussed in the Core Data Programming Guide:
NSManagedObject instances are not intended to be passed between queues. Doing so can result in corruption of the data and termination of the application. When it is necessary to hand off a managed object reference from one queue to another, it must be done through NSManagedObjectID instances.
The general approach with NSPersistentContainer is to use something like viewContext exclusively on the main queue, and to use performBackgroundTask to handle background operations, or you can use newBackgroundContext to generate a background context, and use perform or performAndWait on it to manipulate objects that are fetched from that context.
Moving an object between contexts is done by fetching the same objectID in the other context (keeping in mind that this will return a fresh instance from the store).
You can track down mistakes by adding -com.apple.CoreData.ConcurrencyDebug 1 to your scheme. When you do this, errors will immediately trap on the delightfully named __Multithreading_Violation_AllThatIsLeftToUsIsHonor__.
Edit3: Okay, it seems like it's an issue with Firebase, someone else tweeted about having the same issue. I also contacted support.
A piece of Swift code that handles creating documents suddenly stopped working. No errors are thrown, Firebase doesn't complain in the log and I can verify from the console that the document is not created, I can verify that the device has a healthy internet connection. I also disabled offline persistence for Firebase just to be sure.
When I try debugging it, the debugger jumps straight over the block that handles errors or successes, never running it (i.e. never finishing the Firestore request?).
Here is the code
func createConversation(){
let conversation : [String : Any] = ["owners" : [
UserProfile().getProfile().uid!],
"seeking" : true,
"timestamp" : Timestamp(date: Date())
]
var ref: DocumentReference? = nil
ref = DB().firestore().collection("Conversations").addDocument(data: conversation){ err in
if let err = err {
print("Error creating a convo: \(err)")
} else {
print("Conversation created with ID: \(ref!.documentID)")
StateMachine().action(a: .seekingStarted(ref!.documentID))
}
print("Conversation Creation finished")
}
let documentID = ref?.documentID
print(ref.debugDescription)
}
I'm not sure how to approach this issue, any ideas?
Edit: Okay, the issue is not limited to this block of code, it looks like Firebase is not communicating with the servers. I've waited for more than 5min for the addDocument to return(with error or success) but that never happened.
I noticed that at the initiation of the App BoringSSL complains a bit but this is not new and I don't have problems with the other Firebase services, they work just fine - reading and creating data with no problems.
Edit2: Apparently I can fetch collections and documents from Firestore, the issue seems to be limited to document/collection creation.
The document creation operation takes a little time, if you place the breakpoint inside the asynchronus completion block you will surely get an error or success.
My question are as follows:
Will creating a custom enum to handle the coredata errors be the best way to handle errors in this case
If the persistent store container doesn't load or we can't save the context then there is no need to use the app and crashing is the best option correct?
In order to present these error messages to the user, wouldn't I have to adapt UIAlertAction? Which would also mean I would need to register notifications?
By law we have to get permission to send notifications, would it be best to create a whole new file for notification & add the error enum in that file or would it make more sense to have the core data stack class conform to the Notification protocol?
The end goal is to notify the user that either the persistent store wouldn't load or the moc wouldn't save
Thanks for your time in advance!
enum CoreDataError: Error {
// This is my custom Error handling enum
case persistenStore(description: String )
case saveChanges(description: String )
}
func coreDataErrors(throwError: Bool) throws -> CoreDataError {
// This is my Error Handling function
}
class CoreDataStack {
lazy var managedObjectContext: NSManagedObjectContext = {
let container = self.persistentContainer
return container.viewContext
}()
private lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "SafeHouseCDPhotoVault")
container.loadPersistentStores() { storeDescription, error in
if let error = error as NSError? {
fatalError("Unresolved error: \(error), \(error.userInfo)")
}
}
return container
}()
}
/* In almost every app tutorial available we learn to deal with errors using this fatal error logic to speed through the project
*/
extension NSManagedObjectContext {
func saveChanges() {
if self.hasChanges {
do {
try save()
} catch {
fatalError("Error: \(error.localizedDescription)")
}
}
}
}
// Again the demos have us deal with errors using fatal error
I typically use enums for error cases, but I wouldn't name them according to their source. Name them according to how you will will them for recovery or error-handler behavior. If you can't handle them, there's not much point in generating them.
That gets to the second question; if you can't handle a case at all (such as the MOM missing on launch), crashing is about all you can do. I don't like alerts in that case. What's the user going to do with that information? A crash at least will be sent to Apple and you can see it and do something about it.
If the user can do something, then absolutely provide an error. If there is any hope that the error is transitory (such as a save failure, which may be due to a full disk), then maybe provide them an error/retry. But on iOS this generally is not worth the trouble and the risk of generating bugs. How are you going to test your error/retry system? If you can't test it, how do you know it's better than crashing? (This isn't an idle question; I once built a crash-catching system that had a bug and caused the crash handler to go into a tight loop and drain the battery rapidly. That's worse than crashing.)
If you're a beginner, then you're probably not in a place to handle uncommon Core Data errors and the best and safest thing you're going to do is crash. Handling these things well is quite complex and difficult to test, and I generally do not recommend it on iOS (macOS is a bit different because write-errors are much more often transitory).
That's up to you.
Exit the app properly with an alert and not by crashing it
Why would you need notifications for showing an alert?
This question makes no sense to me.
Here is a sample application application I created to mimic the case I'm working on. It is a single view application that uses CoreData and Swift 2.0 with Xcode 7 beta4
So in my View Controller I create a privateObjectContext that is a child of the mainManagedObjectContext
let mainMOC = AppDelegate().managedObjectContext
var privateObjectContext : NSManagedObjectContext?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
privateObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateObjectContext?.parentContext = mainMOC
}
I know that saving in the child MOC syncs it to the parent and then saving the parent will save it to the persistent store but saving the main MOC everytime I save my child MOC doesn't make sense and makes the purpose of child MOC redundant. SO after all my testing is done I save my parent MOC and it does get stored in the persistent Store as expected.
But when I simulate a crash on the application ( by going to the task manager and forcibly killing the application) it doesn't get stored in the persistent manager; which it should have because of this
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
func saveContext () {
if managedObjectContext.hasChanges {
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()
}
}
}
But managedObjectContext.hasChanges returns false, despite it having changes in the ViewController
Am I wrong somewhere in the lifecycle of the the MOC?
--EDIT
I use an extension to NSManagedObjectContext to create test objects in bulk
privateObjectContext?.performBlock {
self.privateObjectContext?.createTestObjects(100) {
(person: Person, index) in
person.name = "Test Person \(index)"
}
do {
try self.privateObjectContext?.save()
} catch {
print(error)
}
}
After this if I fetch from my main MOC then i get as expected 100 records.
print(mainMOC.hasChanges) //true
let persons = mainMOC.fetchAll(Person)
print(persons.count) //100
But after forcibly crashing the app the main MOC still shows no changes.
The application will terminate method is basically never used, so you shouldn't rely on it. You also shouldn't really be coding to save on crash, firstly your app shouldn't crash and secondly it's hard to know what data is invalid on crash so you may be corrupting otherwise good data.
In general you should either save immediately or batch save up to the persistent store.
Note that you can also construct your managed object contexts differently so things are processed and saved and then merged down to the main context. You probably only want to go to this effort if you're actually seeing problems at the UI when saving.
Strictly for your issue, the problem is that you aren't saving the child MOC (at least not in the code we can see). So, when you come to save the main MOC it doesn't have any of the child changes yet and there is nothing to be saved.
If you have lot of saves to be made, and as #Wain rightly pointed out that, it is not a good idea to perform saves on termination, there is a more recommended way to do this without choking the main thread. You will need to create a root context, that is associated with the store coordinator and runs in a non main queue. Your main context can be the child of this context.
Refer section ' Asynchronous Saving' in
https://www.cocoanetics.com/2012/07/multi-context-coredata/