NSFetchedResultsController Core Data Swift 3

Why Use NSFetchedResultsController?

So far, we’re at exactly the same point we were using the SQLite3 method. However, we didn’t have to write nearly as much code (notice the absence of a FailedBankDatabase class constructing raw SQL statements), and adding other functionality such as insert/delete operations would be much simpler.

However, there’s one notable thing that we could add pretty easily with Core Data that could give us huge benefits to performance: use NSFetchedResultsController.

Right now we’re loading all of the FailedBankInfo objects from the database into memory at once. That might be fine for this app, but the more data we have the slower this will be, and could have a detrimental impact to the user.

Ideally we’d like to load only a subset of the rows, based on what the user is currently looking at in the table view. Luckily, Apple has made this easy for us by providing a great utility class called NSFetchedResultsController.

So, start by opening up file and adding a new NSFetchedResultsController instead, Ok, now onto the fun part – creating our fetched results controller!

 fileprivate lazy var fetchedResultsController : NSFetchedResultsController<Messages> = {
 // Initialize Fetch Request
 let fetchRequest: NSFetchRequest<Messages> = Messages.fetchRequest()
 
 fetchRequest.predicate = NSPredicate(format: "topic.userId == %d && topic.status == %d && topic.type == %d", argumentArray: [self.ForUser, 0, 1])
 
 // Add Sort Descriptors
 let sortDescriptor = NSSortDescriptor(key: "time", ascending: true)
 fetchRequest.sortDescriptors = [sortDescriptor]
 
 // Initialize Fetched Results Controller
 let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.context, sectionNameKeyPath: nil, cacheName: nil)
 fetchedResultsController.delegate = self
 
 return fetchedResultsController
 }()

This should look pretty familiar to the code that we used to have in viewDidLoad, to create a fetch request to pull out the FailedBankInfo objects. However, there’s a lot of new stuff here so let’s discuss…

First, any time we use an NSFetchedResultsController, we need to set a sort descriptor on the fetch request. A sort descriptor is just a fancy term for an object we set up to tell Core Data how we want our results sorted.

The cool thing about sort descriptors is they are very powerful. Not only can you sort on any property of the object you are returning, but you can sort on properties of related objects – just like we see here! We want to sort the objects based on the close date in the FailedBankDetails, but still only receive the data in FailedBankInfo – and Core Data can do this!

A very important part is the next statement – to set the batch size on the fetch request to some small size. In fact, this is the very reason we want to use the fetched results controller in this case. This way, the fetched results controller will only retrieve a subset of objects at a time from the underlying database, and automatically fetch mroe as we scroll.

So once we finish tweaking the fetch request with the sort descriptor and batch size, we just create a NSFetchedRequestController and pass in the fetch request. Note it takes a few other parameters too:

  • For the managed object context, we just pass in our context.
  • The section name key path lets us sort the data into sections in our table view. We could sort the banks by State if we wanted to, for example, here.
  • The cacheName the name of the file the fetched results controller should use to cache any repeat work such as setting up sections and ordering contents.

So now that we have a method to return a fetched results controller, let’s modify our class to use it rather than our old array method. First, update viewDidLoad as follows:

//Activate fetchedResultsController
 do {
 try fetchedResultsController.performFetch()
 } catch {
 let fetchError = error as NSError
 print("Unable to Fetch Messages")
 print("\(fetchError), \(fetchError.localizedDescription)")
 }

All we do here is get a handle to our fetchedResultsController (which implicitly creates it as well) and call performFetch to retrieve the first batch of data.

Then, update numberOfRowsInSection:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
 return fetchedResultsController.sections![section].numberOfObjects
 }

And update cellForRowAtIndexPath like the following:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
//fetch message
 let message = fetchedResultsController.object(at: indexPath)

cell.LblMessage.text = message.message

return cell
}

Ok one more thing – we need to implement the delegate methods for the NSFetchedResultsController.

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
 //Begin table update on will change content
 tableView.beginUpdates()
 }
 
 func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
 
 //Reload table indexpaths accorgind to the change in database
 switch type {
 
 case .insert:
 tableView.insertRows(at: [newIndexPath!], with: .fade)
 
 case .update:
 tableView.reloadRows(at: [indexPath!], with: .fade)
 
 default:
 tableView.reloadData()
 }
 
 }
 
 func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
 //End table update on did change content
 tableView.endUpdates()
 }

Compile and run your project, and it should look the same. However, if you examine the debug output you will see something very amazing…

SELECT 0, t0.Z_PK FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
    ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK
    ORDER BY t1.ZCLOSEDATE DESC
total fetch execution time: 0.0033s for 234 rows.

SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSTATE, t0.ZCITY,
    t0.ZDETAILS FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
    ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK WHERE
    t0.Z_PK IN  (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
    ORDER BY t1.ZCLOSEDATE DESC LIMIT 20
total fetch execution time: 0.0022s for 20 rows.

SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSTATE, t0.ZCITY,
    t0.ZDETAILS FROM ZFAILEDBANKINFO t0 LEFT OUTER JOIN
    ZFAILEDBANKDETAILS t1 ON t0.ZDETAILS = t1.Z_PK WHERE
    t0.Z_PK IN  (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
    ORDER BY t1.ZCLOSEDATE DESC LIMIT 20
total fetch execution time: 0.0017s for 20 rows.

You can see here that behind the scenes, the NSFetchedResultsController is getting a list of IDs from FailedBankInfo, in the proper sort order. Then, as the user pages through the table, it loads one batch at a time – rather than loading all of the objects into memory at once!

This would have been a lot more code to do with raw SQLite – and is just one of the many reasons why using Core Data can save time and increase performance.

NSFetchedResultsController Core Data Swift 3

Core Data Delete Rules Swift 3

Delete Rules

What happens if a note is deleted? Should the category the note belongs to also be deleted? No. But what happens if a category is deleted? Should it be possible to have notes without a category?

This brings us to delete rules. Every relationship has a delete rule. A delete rule defines what happens when the record that owns the relationship is deleted.

A delete rule defines what happens when the record that owns the relationship is deleted.

Select the notes relationship of the Category entity and open the Data Model Inspector on the right. By default, the delete rule of a relationship is set to nullify. Core Data supports four delete rules:

  • No Action
  • Nullify
  • Cascade
  • Deny

No Action Delete Rule

If the delete rule of a relationship is set to No Action, nothing happens. Let me illustrate this with an example. We have a category that contains several notes. If the category is deleted, the notes are not notified of this event. The notes on the other end of the relationship believe that they are still associated with the deleted category.

I have never had a need to use this delete rule in a project. In most situations, you want to take some action when a record is deleted. And that is where the other delete rules come into play.

Nullify Delete Rule

If the delete rule of a relationship is set to Nullify, the destination of the relationship is nullified when the record is deleted.

For example, if a category has several notes and the category is deleted, the relationships pointing from the notes to the category are nullified. This is the default delete rule and the delete rule you will find yourself using most often.

Cascade Delete Rule

The Cascade delete rule is useful if the data model includes one or more dependencies. Let me give you an example. If a note should always have a category, the deletion of a category should automatically delete the notes associated with that category. In other words, the deletion of the category cascades or trickles down to the notes linked to the category. Even though this may make sense on paper, the user probably won’t like it when you automatically delete its notes. The Deny delete rule is a better option in this scenario (see below).

If you are dealing with a Many-To-Many relationship, this is often not what you want. If a note can have several tags and a tag can be linked to several notes, deleting a tag should not result in the deletion of every note with that tag. The notes could be associated with other tags, for example.

Deny Delete Rule

Remember the previous example in which the deletion of a category resulted in the deletion of every note that belonged to that category. It may be better to apply the Deny delete rule.

Deny is another powerful and useful pattern. It is the opposite of the Cascade delete rule. Instead of cascading the deletion of a record, it prevents the deletion of the record.

For example, if a category is associated with several notes, the category can only be deleted if it is no longer tied to any notes. This configuration prevents the scenario in which notes are no longer associated with a category.

Core Data Delete Rules Swift 3

Write Mockup Classes For Unit Testing Of UserDefaults, Core Data & UrlSessions In Swift 3 & XCode 8

URLSession

Source Code

public protocol CLSession {
func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
}

extension URLSession: CLSession { }

public final class URLSessionMock: CLSession {

var url: URL?
var request: URLRequest?
private let dataTaskMock: URLSessionDataTaskMock

public convenience init?(jsonDict: [String: Any], response: URLResponse? = nil, error: Error? = nil) {
guard let data = try? JSONSerialization.data(withJSONObject: jsonDict, options: []) else { return nil }
self.init(data: data, response: response, error: error)
}

public init(data: Data? = nil, response: URLResponse? = nil, error: Error? = nil) {
dataTaskMock = URLSessionDataTaskMock()
dataTaskMock.taskResponse = (data, response, error)
}

public func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
self.url = url
self.dataTaskMock.completionHandler = completionHandler
return self.dataTaskMock
}

public func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
self.request = request
self.dataTaskMock.completionHandler = completionHandler
return self.dataTaskMock
}

final private class URLSessionDataTaskMock : URLSessionDataTask {

typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void
var completionHandler: CompletionHandler?
var taskResponse: (Data?, URLResponse?, Error?)?

override func resume() {
DispatchQueue.main.async {
self.completionHandler?(self.taskResponse?.0, self.taskResponse?.1, self.taskResponse?.2)
}
}
}

}

Images
Use

UserDefaults

Source Code

class MockUserDefaults: UserDefaults {
var loggedInUser = 0
override func set(_ value: Any?, forKey defaultName: String) {
if defaultName == “loggedInUser” {
loggedInUser += 1
}
}
}

Images

Use

CoreData

Source Code

// MARK: – Variables
//Core Data variables
var storeCoordinator: NSPersistentStoreCoordinator!
var managedObjectContext: NSManagedObjectContext!
var managedObjectModel: NSManagedObjectModel!
var store: NSPersistentStore!

//Managers
var apiManager: APIManager!
var dataManager: DataManager!

// MARK: – XCTest Methods

override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
//Set Managers
apiManager = APIManager.shared
dataManager = DataManager.shared

/* Core Data Mock Object Configuration */
// ——– Start ——-
managedObjectModel = NSManagedObjectModel.mergedModel(from: nil)
storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

do {
store = try storeCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
} catch {
print(error.localizedDescription)
}

managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = storeCoordinator
apiManager.context = managedObjectContext
dataManager.context = managedObjectContext
(UIApplication.shared.delegate as! AppDelegate).mockContext = managedObjectContext
// ——– End ——-
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
managedObjectContext = nil
apiManager = nil
dataManager = nil
(UIApplication.shared.delegate as! AppDelegate).mockContext = nil

//Check Store Removal
do {
try storeCoordinator.remove(store)
} catch {
XCTFail(“couldn’t remove persistant store: \(error)”)
}

super.tearDown()
}

func testThatStoreIsSetUp() {
XCTAssertNotNil(store, “no persitant store”)
}

Images

Use

Write Mockup Classes For Unit Testing Of UserDefaults, Core Data & UrlSessions In Swift 3 & XCode 8

Core Data Using Swift 3

User Data Model

user_data_model

Product Data Model

product_data_model

Data Model Structure

model_structure

Core Data Stack

core_data_stack

Core Data Saving Support

core_data_saving_support

Get Context To Work On Core Data Stack

persistance_store_context

Save Single Record

save_single_record

 Save Context To Update Database

save_context_for_update_database

Fetch Single Record

fetch_single_record

Add/Update Multiple Record (One-To-Many Relation)

save_update_multiple_records

Fetch Multiple Records (One To Many Relation)

fetch_multiple_record_one_to_many

Relations

Library Data Model

The Library entity has two attributes, name and location, both of type String. The Author entity also has two attributes, firstName and lastName, both of type String. The Book entity has three attributes, title of type String, publicationDate of type Date, and pages of type Integer 16. This is what the data model should look like in the data model graph.

figure-data-model-1

Planning Ahead

A library contains books and books are written by authors. That is the idea underlying the data model we are creating. This implies that the Author entity is tied to the Book entity. The Library and Book entities are also linked.

But this is one possible solution. We can also say that a library contains the books of authors and that authors have written books. Do you see the difference? In that scenario, the Library entity has a relationship with the Author entity, not the Bookentity.

When working with data models, it is critical to spend some time planning and thinking what the data model should look like. Once you have decided on the data model, it is often expensive to make drastic changes to it. We learn more about what that means for Core Data when we discuss migrations.

When working with data models, it is critical to spend some time planning and thinking what the data model should look like.

Relationships

To-One Relationships

A book belongs to a library. This means that we need to create a relationship that links a book to a library. Open Library.xcdatamodeld and switch to the editor’s tablestyle. Select the Book entity and click the plus button at the bottom of the Relationships table to add a relationship to the entity. Set Relationship to library and Destination to the Library entity. Leave Inverse empty for now.

figure-add-to-one-relationship-1

Switch to the editor’s graph style to see the visual representation of the relationship we defined.

figure-add-to-one-relationship-2

Before we move on, there are a number of details we need to discuss. The relationship we added is a to-one relationship, a book can belong to only one library. This is visualized in the data model graph by the single arrow pointing from the Bookentity to the Library entity.

Note that the arrow points from Book to Library. There is no arrow pointing back from Library to Book. There is no inverse relationship because we haven’t defined one yet. This implies that the library the book belongs to does not know that the book belongs to the library. That is not what we want, though. A library should know what books it contains. Right?

Inverse Relationships

Switch to the editor’s table style and select the Library entity. Add a relationship and name it books, plural. Set Destination to Book and set Inverse to library. By setting the inverse relationship to library, the inverse relationship of the books relationship is automatically set to the library relationship of the Book entity.

figure-add-inverse-relationship-1

Switch to the data model graph to see what that looks like. The connection between Library and Book contains arrows pointing to and from each entity.

figure-add-inverse-relationship-2

Switch back to the editor’s table style and select the Book entity. Note that the inverse relationship of the library relationship is automatically set to books because we set the inverse relationship of the books relationship to library. Core Data is clever enough to take care of that for us.

To-Many Relationships

Both library and books are to-one relationships. This severely limits the data model. A library with only one book isn’t much of a library. It is time to fix that. Select the books relationship of the Library entity in the editor’s table style. Open the Data Model Inspector in the Utilities pane on the right to see the details of the relationship.

In the Data Model Inspector, you can modify the relationship’s destination and inverse relationship. You can also modify the relationship’s type or cardinality. Core Data supports To-One and To-Many relationships. Set Type to To-Many.

figure-add-to-many-relationship-1

Switch back to the data model graph to see what has changed. The relationship from Library to Book now has two arrows, indicating that books is a to-many relationship.

figure-add-to-many-relationship-2

Many-To-Many Relationships

Wait a second. Can a book only be in one library? That may be true for an exceptionally rare book. We need to turn the library relationship into a to-manyrelationship. Select the library relationship of the Book entity in the editor’s table style and set Type to To-Many in the Data Model Inspector.

Naming conventions are very important, especially if you are working in a team. Even though the library relationship is now a to-many relationship, the name of the relationship doesn’t reflect this change. Update the name of the relationship to libraries. That makes more sense. Developers new to the project will immediately understand that a book can belong to multiple libraries.

This is what the data model graph looks like after the changes. The relationship that links the Library and Book entities is now a many-to-many relationship.

figure-add-many-to-many-relationship-1

Delete Rules

Core Data is much more than a database and this becomes clear when working with relationships. Relationships in Core Data are powerful because the framework does a lot of the heavy lifting for you.

Delete rules are one of the conveniences that make working with Core Data great. Every relationship has a delete rule. It defines what happens when the record that owns the relationship is deleted. Let me explain what that means.

Select the books relationship of the Library entity and open the Data Model Inspector on the right. By default, the delete rule of a relationship is set to nullify. Core Data supports four delete rules:

  • No Action
  • Nullify
  • Cascade
  • Deny

No Action

If the delete rule of a relationship is set to No Action, nothing happens. Let me illustrate this with an example. We have a library record that contains several book records. If the library record is deleted, the book records are not notified of this event. Every book record thinks it is still associated with the deleted library.

I have never had a need to use this delete rule. In most situations, you want to take some action when a record is deleted. That is where the other delete rules come into play.

Nullify

If the delete rule of a relationship is set to Nullify, the destination of the relationship is nullified when the record is deleted. If a library has several books and the library is deleted, the relationship pointing from the books to the library is nullified. This is the default delete rule and the delete rule you will find yourself using most often.

Cascade

Cascade is useful if the data model includes one or more dependencies. For example, if an account record has a relationship to one or more user records, it may be desirable to delete every user if the account the user belongs to is deleted. In other words, the deletion of the account record cascades to the user records it is linked to.

If you are dealing with a many-to-many relationship, though, this is often not what you want. If a library with several books is deleted, you don’t want to delete every book in the library. The books could be associated with other libraries, for example.

Deny

Deny is another powerful and useful pattern. It is the opposite of the Cascade delete rule. Instead of cascading the deletion of a record, it prevents the deletion of a record. For example, if an account is associated with several users, the account can only be deleted if it is no longer tied to any users. This configuration prevents the scenario in which users are no longer associated with an account.

Core Data Using Swift 3