Faults are an essential component of Core Data. Even though the term sounds ominous, faults are inherent to the life cycle of a Core Data record. In this tutorial, you'll learn what faults are, how to handle them, and how to debug issues related to faulting.
Prerequisites
Core Data is an advanced topic so I'm going to assume that you're already familiar with Xcode and iOS development. Even though I'll be using the Swift programming language for this tutorial, everything in this tutorial is also applicable to Objective-C.
Wat Is a Fault?
Core Data is very good at what it does thanks to the hard work of Apple's Core Data team. Core Data is highly optimized to keep its memory footprint low without sacrificing performance. Faulting is one of the techniques Core Data uses to consume as little memory as possible.
Faulting isn't unique to Core Data. A similar technique is used in many other frameworks, such as Ember and Ruby on Rails. The idea is simple, only load data when it's needed. To make faulting work, Core Data does a bit of magic under the hood by creating custom subclasses at compile time that represent the faults. Let's see how this works with an example.
I've created a simple sample application to work with. Download the Xcode project from GitHub and open it in Xcode. The project uses Swift 2.1, which means that you need Xcode 7.1 or higher to satisfy the compiler.
The data model contains two entities, List and Item. A list can have zero or more items and an item is always linked to a list. It's a classic example of a one-to-many relationship.
Run the application and populate the persistent store, a SQLite database, with some data by creating a few lists and items. Quit the application when you're finished.
Firing Faults
You should now have an application with some data. Let's see how faults work in practice by adding some print statements. Open ListsViewController.swift and look for prepareForSegue(_:sender:)
. In this method, we fetch the list the user has selected in the table view. Uncomment the print statements in prepareForSegue(_:sender:)
as shown in the implementation below.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == SegueListViewController { guard let indexPath = tableView.indexPathForSelectedRow else { return } // Fetch List let list = self.fetchedResultsController.objectAtIndexPath(indexPath) as! List print("1: \(list)") if let items = list.items { print("2: \(items)") print("3: \(items.count)") print("4: \(items)") if let item = items.anyObject() { print("5: \(item)") print("6: \(item.name)") print("7: \(item)") } } print("8: \(list)") // Fetch Destination View Controller let listViewController = segue.destinationViewController as! ListViewController // Configure View Controller listViewController.list = list } }
Run the application and tap one of the lists in the lists view controller. The example is only interesting if you tap a list that has one or more items associated with it. Let's inspect the output of the print statements step by step.
1: <Faulting.List: 0x154e5d0b0> (entity: List; id: 0xd0000000001c0000 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/List/p7> ; data: { items = "<relationship fault: 0x154e3d5b0 'items'>"; name = "List 6"; })
The first thing to note is the class name, Faulting.List
. This is what we expect since the module is named Faulting and the class is named List. In Swift, the complete class name of a class is made up of the module's name and the class's name.
The output in the console also shows the name of the entity, List, and a dictionary of data. The name attribute is set to List 6 while the items attribute is marked as a relationship fault. This is the first type of fault in Core Data. Core Data understands that there's no need to load the relationship. Instead, it marks the relationship as a fault. The second print statement confirms this as you can see below.
2: Relationship 'items' fault on managed object (0x154e5d0b0) <Faulting.List: 0x154e5d0b0> (entity: List; id: 0xd0000000001c0000 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/List/p7> ; data: { items = "<relationship fault: 0x154e3d5b0 'items'>"; name = "List 6"; })
The third print statement, however, is more interesting. It prints the number of items associated with the list.
3: 2
Core Data can only do this by firing the relationship fault. It asks the persistent store for the items associated with the list. This, however, is only part of the story as illustrated by the fourth print statement.
4: Relationship 'items' on managed object (0x154e5d0b0) <Faulting.List: 0x154e5d0b0> (entity: List; id: 0xd0000000001c0000 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/List/p7> ; data: { items = ( "0xd000000000540002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p21>", "0xd000000000580002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p22>" ); name = "List 6"; }) with objects {( <Faulting.Item: 0x154e83d60> (entity: Item; id: 0xd000000000540002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p21> ; data: <fault>), <Faulting.Item: 0x154e55e50> (entity: Item; id: 0xd000000000580002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p22> ; data: <fault>) )}
We no longer see a relationship fault, but we still see a fault. What is that about? Core Data can only give us the number of items for the list by firing or resolving the relationship fault. However, this doesn't mean that Core Data resolves the items of the relationship. The output in the console confirms this.
We can see that the records for the items are there, including the identifier Core Data uses internally. The data dictionary, however, is marked as a fault. Again, Core Data only gives us what we ask for. Fortunately, the nitty gritty details are handled by Core Data.
Let's dig a little deeper and fetch one of the items from the list. We do this by calling anyObject()
on the items
object. We print the resulting item in the fifth print statement.
5: <Faulting.Item: 0x144d7f100> (entity: Item; id: 0xd000000000540002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p21> ; data: <fault>)
The output shouldn't surprise you. The output confirms that we're dealing with an Item entity. Unsurprisingly, the data dictionary is still marked as a fault. In the sixth print statement, we print the name attribute of the item.
6: Item 0
Because we ask for the value of an attribute of the record, Core Data fires the fault to give us that value. It fetches the data for the item and populates the data dictionary. The seventh print statement confirms these findings.
7: <Faulting.Item: 0x144d7f100> (entity: Item; id: 0xd000000000540002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p21> ; data: { list = "0xd0000000001c0000 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/List/p7>"; name = "Item 0"; })
The data dictionary contains the name attribute as well as the list relationship. The eighth and last print statement shows that the relationship fault of the list
object is resolved.
8: <Faulting.List: 0x144e55da0> (entity: List; id: 0xd0000000001c0000 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/List/p7> ; data: { items = ( "0xd000000000540002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p21>", "0xd000000000580002 <x-coredata://804E0602-42B4-4EB5-A1D0-653848363595/Item/p22>" ); name = "List 6"; })
Unable to Fulfill a Fault
I decided to write this article to explain a problem many developers using Core Data run into at one point or another, firing a fault that cannot be fulfilled. The problem is simple. Let's assume you have a list with a number of items and, at some point, the user deletes the list. What happens to the items of that list? What happens if Core Data attempts to fire the list relationship of one of the items that belonged to that list? Let's find out.
Let's revisit the project you've downloaded from GitHub. Open Faulting.xcdatamodeld, the project's data model. Select the items relationship of the List entity and open the Data Model Inspector on the right. What's of interest to us is the Delete Rule, which is currently set to Cascade. This means that every item of the list is deleted when the list is deleted. This makes sense since we don't want to have abandoned items that aren't associated with a list.
Select the list relationship of the Item entity and open the Data Model Inspector. The Delete Rule of this relationship is set to Nullify. This means that the destination of the relationship, the list object, is set to null when the destination record, the item, is deleted. This is the default delete rule for a relationship.
Run the application, create a few lists and items, and tap the Items button at the top left to show every item you've created. Tap Cancel in the top left, delete one of the lists, and tap the Items button again to see what's changed. As expected, the items associated with the deleted list have also been deleted by Core Data. This is no magic. Core Data simply executes the delete rules we defined in the data model.
We can conclude that the relationships are set up correctly for this type of application. But what happens if we don't configure the delete rules correctly. Open the data model and set the delete rule of both relationships, items and list, to No Action. Launch the application again and create a few lists and items. If you delete a list and tap the Items button in the top left to see every item in the persistent store, the items associated with the deleted list should still be there. They weren't deleted even though the list to which they belong has been removed.
Tap one of the lists and see what happens. If you are running the application on iOS 8, then the application will crash due to an uncaught exception. If you are running the application on iOS 9, then you will only see an error in the console. Stop scratching your head and let's figure this out together.
Object Inaccessible
Even though the application crashes on iOS 8, the output we see in the console is clear and to the point.
Faulting[7189:2427906] *** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x175b2ba0 <x-coredata://3920E0C9-6613-47E6-AA19-66AF6555140A/List/p1>''
The first thing to notice is that the application crashed due to an uncaught exception. What's more important for our discussion, however, is the reason the exception was thrown. Core Data tells us that it was unable to fulfill a fault for a relationship. The relationship Core Data refers to is the list relationship of the item we tapped in the table view.
If you look at the implementation of tableView(tableView:didSelectRowAtIndexPath:)
, then you'll understand why Core Data threw an exception. In this method, we fetch the item the user tapped and print the name of the list to the console.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { tableView.deselectRowAtIndexPath(indexPath, animated: true) if let item = self.fetchedResultsController.objectAtIndexPath(indexPath) as? Item { print(item.list?.name) } }
Because the list relationship is a fault, Core Data needs to fire the fault to resolve it. This means that Core Data asks the persistent store for the list record. The problem is that the record is no longer in the persistent store, which is why an exception was thrown.
Delete Inaccessible Faults
Up until iOS 9, this has always been the default behavior. As of iOS 9, Core Data no longer throws an exception. Instead, it logs a cryptic message to the console. Despite the message being unclear, it still contains the reason of the problem.
Faulting[2806:1306995] CoreData: warning: An NSManagedObjectContext delegate overrode fault handling behavior to silently delete the object with ID '0xd000000000140000 <x-coredata://396CF04E-B8B4-4108-8719-3C651F880C33/List/p5>' and substitute nil/0 for all property values instead of throwing.
The gist is that Core Data no longer throws an exception when it is unable to fulfill a fault. The somewhat good news is that your application no longer crashes if you don't catch the exception.
The reason for not throwing an exception on iOS 9 is due to the introduction of a new property, shouldDeleteInaccessibleFaults
, and a new method, shouldHandleInaccessibleFault(_:forObjectID:triggeredByProperty:)
, on the NSManagedObjectContext
class. I won't cover these additions in this tutorial.
What you need to remember is that iOS 9, by default, sets the shouldDeleteInaccessibleFaults
to true
. This means that an inaccessible managed object is marked as deleted and its properties are zeroed out. If shouldDeleteInaccessibleFaults
is set to false
, Core Data reverts to the old behavior by throwing an exception.
Of course, the shouldDeleteInaccessibleFaults
property goes hand in hand with shouldHandleInaccessibleFault(_:forObjectID:triggeredByProperty:)
. If you override this method, you can handle inaccessible objects more elegantly.
Handling Faults
When you encounter an exception due to Core Data not being able to fulfill a fault, you may think Core Data is being a bit aggressive. This reaction is quite common for people new to the framework. The truth is that the developer is at fault. Core Data was designed with a specific set of goals in mind and faulting is an essential component to realize these goals.
Despite their name, faults should always be fulfillable. If a fault cannot be fulfilled, then that means the data model isn't set up correctly or the application isn't respecting the rules set out by the Core Data framework.
In the Core Data Programming Guide, Apple lists a number of scenarios that may lead to faults that can no longer be fulfilled. The most common scenarios are incorrectly configured relationships (delete rules) and deleting a managed object while the application still has a strong reference to that managed object.
Be warned, there are other possible scenarios. From my experience, developers often run into similar issues due to a problem with the Core Data stack. For example, if the application keeps a strong reference to a managed object and, at some point, deallocates the managed object context of that managed object, then Core Data is no longer able to fulfill any faults for that managed object. I hope you understand that this shouldn't happen if you play by Core Data's rules.
Conclusion
Core Data faults are incredibly useful and a key component of Apple's persistence framework. I hope this article has taught you how to deal with faults and where to look for if you run into faults that cannot be fulfilled. If you have any questions, feel free to leave them in the comments below or reach out to me on Twitter.
Comments