Skip to content

Commit

Permalink
Core Data guide
Browse files Browse the repository at this point in the history
Summary:
Issue answered: #407 #460 #461

I did not run any tool to generate documentation

- [x] All tests pass. Demo project builds and runs.
- [x] I added tests, an experiment, or detailed why my change isn't tested.
- [x] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes.
- [x] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md)
Closes #515

Differential Revision: D4621212

Pulled By: jessesquires

fbshipit-source-id: 110e3d37d08e7c763b6a6cde70bc83280f7a2bb3
  • Loading branch information
jessesquires authored and facebook-github-bot committed Feb 27, 2017
1 parent 85396f1 commit fb5f15e
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Guides/Best Practices and FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ This feature is on the `master` branch only and hasn't been officially tagged an

**Does `IGListKit` work with...?**

- Core Data ([#460](https://github.com/Instagram/IGListKit/issues/460), [#461](https://github.com/Instagram/IGListKit/issues/461))
- Core Data ([Working with Core Data](https://instagram.github.io/IGListKit/working-with-core-data.html) Guide)
- AsyncDisplayKit ([AsyncDisplayKit/#2942](https://github.com/facebook/AsyncDisplayKit/pull/2942))
- ComponentKit ([ocrickard/IGListKit-ComponentKit](https://github.com/ocrickard/IGListKit-ComponentKit))
- React Native
Expand Down
4 changes: 4 additions & 0 deletions Guides/Getting Started.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ func emptyView(for listAdapter: IGListAdapter) -> UIView? {

You can return an array of _any_ type of data, as long as it conforms to `IGListDiffable`.

### Immutability

The data should be immutable. If you return mutable objects that you will be editing later, `IGListKit` will not be able to diff the models accurately. This is because the instances have already been changed. Thus, the updates to the objects would be lost. Instead, always return a newly instantiated, immutable object and implement `IGListDiffable`.

## Diffing

`IGListKit` uses an algorithm adapted from a paper titled [A technique for isolating differences between files](http://dl.acm.org/citation.cfm?id=359467&dl=ACM&coll=DL) by Paul Heckel. This algorithm uses a technique known as the *longest common subsequence* to find a minimal diff between collections in linear time `O(n)`. It finds all **inserts**, **deletes**, **updates**, and **moves** between arrays of data.
Expand Down
166 changes: 166 additions & 0 deletions Guides/Working with Core Data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Working with Core Data

This guide provides details on how to work with [Core Data](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/index.html) and `IGListKit`.

## Background

The main difference in the setup and architecture of a Core Data and `IGListKit` application is the configuration of the model layer. Core Data operates with a mutable model layer, where objects are always passed by reference and the same instance is modified when an object is edited.

`IGListKit` requires an immutable model in order to correctly calculate the diffing between model snapshots and to correctly animate the `UICollectionView`.

In order to satisfy these prerequisites, Core Data `NSManagedObject`s should not be used directly as `IGListDiffable` objects. Instead, a view model (or some sort of token object) should be used to mimic (or act as a placeholder for) the data that will be displayed in the collection view.

## Further discussion

There are further discussions on this topic at [#460](https://github.com/Instagram/IGListKit/issues/460), [#461](https://github.com/Instagram/IGListKit/issues/461), [#407](https://github.com/Instagram/IGListKit/issues/407).

## Basic Setup

The basic setup for Core Data and `IGListKit` is the same as the normal setup that is found in the [Getting Started Guide][https://instagram.github.io/IGListKit/getting-started.html]. The main difference will be in the setup of the model in the datasource.

## Working with view model

### Creating a view model

Suppose the Core Data model consist of:

```swift
extension User {
@NSManaged var firstName: String
@NSManaged var lastName: String
@NSManaged var address: String
@NSManaged var someVariableNotNeededInUI: String
}
```

A `ViewModel` object will contain only the necessary information needed to build UI. The properties of the `ViewModel` will be immutable:

```swift
class UserViewModel: NSObject {
let firstName: String
let lastName: String
let address: String
}
```

We recommend writing a helper method to translate Core Data objects into `ViewModel` objects:

```swift
extension UserViewModel {
static func fromCoreData(user: User) -> UserViewModel {
// - Note: For avoiding Core Data threading violation, the following code should be wrapped in a
// user.managedObjectContext?.performAndWait {}
return UserViewModel(firstName: user.firstName, lastName: user.lastName, address: user.lastName)
}
}
```

The `IGListDiffable` protocol is implemented on the `ViewModel` layer:

```swift
extension UserViewModel: IGListDiffable {

public func diffIdentifier() -> NSObjectProtocol {
return NSString(string: firstName + lastName)
}

public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
guard let toObject = object as? UserViewModel else { return false }

return self.firstName == toObject.firstName
&& self.lastName == toObject.lastName
&& self.address == toObject.address
}
}
```

## Setting up the view model in the adapter data source

Steps to configure the `UICollectionView` with the `ViewModel`:

- Retrieve Core Data objects
- Transform Core Data objects into ViewModel objects and return them
- Track changes to Core Data objects and update the datasource with them

### Retrieve Core Data objects

The way objects are retrieved from Core Data is depends on the project.

Example: Suppose there is a delegate `Provider` class with the role of fetching Core Data objects and checking for updates. It can use an `NSFetchedResultsController` to leverage on the Core Data framework and rely on automatic notifications for updates.

```swift
final class UserProvider: NSObject {

private lazy var userFetchResultController: NSFetchedResultsController<User> = {
let fetchRequest: NSFetchRequest<User> = NSFetchRequest(entityName: "User")

// sort descriptors and predicates
// ...

let fetchResultController = NSFetchedResultsController(
fetchRequest: tripsFetchRequest,
managedObjectContext: self.coreDataStack.mainQueueManagedObjectContext,
sectionNameKeyPath: nil,
cacheName: nil)

// Set delegate to track CoreData changes
fetchResultController.delegate = self

return fetchResultController
}()

init(coreDataStack: CoreDataStack) {
self.coreDataStack = coreDataStack
super.init()
do {
try userFetchResultController.performFetch()
}
catch {
fatalError("Cannot Fetch! \(error)")
}
}
}
```

### Transform Core Data objects into view models

```swift
func getUsers() -> [UserViewModel]? {
guard let users = self.userFetchResultController.fetchedObjects else { return nil }
// Here we transform and return ViewModel objects!
return users.flatMap { UserViewModel.fromCoreData(user: $0) }
}
```

### Track changes to Core Data

The `Provider` will track changes to the Core Data model by listening to the `NSFetchedResultsController` methods and inform the application about this changes via KVO, notifications, delegation, etc.

```swift
extension UserProvider: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.delegate?.performUpdatesForCoreDataChange(animated: true)
}
}
```

### Configure the datasource

The data source retrieves ViewModels and configures the `IGListSectionController` with them:

```swift
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
return self.userProvider.getUsers()
}
```

### Reacting to Core Data changes in UI

The `UIViewController` containing the `UICollectionView`, will react to the `NSFetchedResultController` messages by updating the UI:

```swift
func performUpdatesForCoreDataChange(animated: Bool) {
// Updating contents of collection view
self.adapter.performUpdates(animated: animated)
}
```

0 comments on commit fb5f15e

Please sign in to comment.