An open API service indexing awesome lists of open source software.

https://github.com/oreillymedia/flapjack

A Swift data persistence API with support for Core Data.
https://github.com/oreillymedia/flapjack

cocoapods core-data ios macos swift

Last synced: 21 days ago
JSON representation

A Swift data persistence API with support for Core Data.

Awesome Lists containing this project

README

        

# Flapjack

Flapjack is an iOS/macOS/tvOS framework with 2 primary goals.

1. Help you abstract your model-focused database persistence layer from the rest of your app
2. Simplify the database layer's API into an easy-to-use, easy-to-remember, full Swift one

It lets you _skip_ the boilerplate commonly associated with database layers like Core Data and lets you introduce structured, sane data persistence in your app _sooner_, letting you spend more of your time creating the app you really want. We use it at [O'Reilly Media][orm] for our iOS apps, and if you like what you see, perhaps you will too.

## Getting started

### Swift Package Manager

Swift Package Manager is the preferred way to use Flapjack. Add the following as a dependency to the `dependencies` array in your `Package.swift` file:

```swift
.package(name: "Flapjack", url: "https://github.com/oreillymedia/flapjack.git", .upToNextMajor(from: "0.8.1"))
```

Then you'll specify `Flapjack` as a dependency of the target in which you wish to use it. You can also import `FlapjackCoreData` and `FlapjackUIKit`.

```swift
.package(name: "FlapjackCoreData", url: "https://github.com/oreillymedia/flapjack.git", .upToNextMajor(from: "0.8.1"))
.package(name: "FlapjackUIKit", url: "https://github.com/oreillymedia/flapjack.git", .upToNextMajor(from: "0.8.1"))
```

### CocoaPods

Flapjack is also available through [CocoaPods][cpd]. To install it, simply add the following line to your Podfile:

```ruby
pod 'Flapjack', '0.8.1'
# If you're using Core Data...
pod 'Flapjack/CoreData', '0.8.1'
# If you're targeting iOS and want some helpers...
pod 'Flapjack/UIKit', '0.8.1'
```

And run `pod install` at the command line.

## Usage

Full documentation is forthcoming, but here's a good thorough run-through of what Flapjack has to offer.

In your iOS project (like perhaps in your `UIApplicationDelegate`), kick things off with the following code (if you're using Core Data; support for more databases planned).

```swift
import Flapjack

// Create the DataAccess object, your main point-of-entry for persistence.
// You can also pass in `.sql(filename: "YourCoreDataStore.sql")`.
let dataAccess = CoreDataAccess(name: "YourCoreDataStore", type: .memory)

// Then tell the stack to configure itself.
dataAccess.prepareStack(asynchronously: true) { error in
if let error = error {
print(error.localizedDescription)
}

// Make sure you retain your `dataAccess` variable, and now you're all
// ready to go!
}
```

For your model objects to take part in the simplified API provided by Flapjack, you'll need to make sure they conform to `DataObject`. [For a class such as `Pancake`][pcm] that has the fields `identifier`, `flavor`, and `radius` defined in a Core Data model, this would look like the following.

```swift
extension Pancake: DataObject {
// The type of your primary key, if you have one of your own.
public typealias PrimaryKeyType = String
// The name of the entity as Core Data knows it.
public static var representedName: String {
return "Pancake"
}
// The key path to your model's primary key.
public static var primaryKeyPath: String {
return #keyPath(identifier)
}
// An array of sorting criteria.
public static var defaultSorters: [SortDescriptor] {
return [
SortDescriptor(#keyPath(flavor), ascending: true, caseInsensitive: true),
SortDescriptor(#keyPath(radius), ascending: false)
]
}
}
```

Now you're cookin'. Interacting with the data store is even easier.

```swift
// Get every pancake.
let pancakes = dataAccess.mainContext.objects(ofType: Pancake.self)
// Get just the chocolate chip ones.
let pancakes = dataAccess.mainContext.objects(ofType: Pancake.self, attributes: ["flavor": "Chocolate Chip"])
// Create your own.
let pancake = dataAccess.mainContext.create(Pancake.self, attributes: ["flavor": "Rhubarb"])
// Save your changes.
let error = context.persist()
```

Granted you don't want to do expensive data operations on the main thread. Flapjack's Core Data support follows best practices for such a thing:

```swift
dataAccess.performInBackground { [weak self] context in
let pancake = context.create(Pancake.self, attributes: ["flavor": flavor, "radius": radius, "height": height])
let error = context.persist()

DispatchQueue.main.async {
guard let `self` = self else {
return
}
let foregroundPancake = self.dataAccess.mainContext.object(ofType: Pancake.self, objectID: pancake.objectID)
completion(foregroundPancake, error)
}
}
```

Sick of your database? There's a function for that, too.

```swift
dataAccess.deleteDatabase(rebuild: true) { error in
if let error = error {
print(error.localizedDescription)
}

// It's almost as if it never happened.
}
```

## Data sources

This wouldn't be nearly as much fun if Flapjack didn't provide a way to automatically listen for model changes. The `DataSource` and `SingleDataSource` protocols define a way to listen for changes on a collection of persisted objects _or_ a single object, respectively. If you're targeting Core Data, the two implementations of those protocols (`CoreDataSource` and `CoreSingleDataSource`) are powered by `NSFetchResultsController` and listening to `.NSManagedObjectContextObjectsDidChange`, respectively.

```swift
import Flapjack

let dataSourceFactory = CoreDataSourceFactory(dataAccess: dataAccess)
let queryAttributes = ["radius": 2.0, "flavor": "Chocolate Chip"]
let dataSource: CoreDataSource = dataSourceFactory.vendObjectsDataSource(attributes: queryAttributes, sectionProperty: "flavor", limit: 100)

// Prepare yourself for pancakes, but only chocolate chip ones bigger than a 2" radius, and no more than 100.
// This block fires every time the data source picks up an insert/change/deletion.
dataSource.onChange = { itemChanges, sectionChanges in
// If you've added `Flapjack/UIKit` to your Podfile, you get helper extensions!
self.tableView.performBatchUpdates(itemChanges, sectionChanges: sectionChanges)

// Get a specific pancake:
print("\(String(describing: dataSource.object(at: IndexPath(item: 0, section: 0))))")
}

// Kick off a call to start listening (and immediately fire `.onChange` with all existing results).
dataSource.execute()
```

For a more complete example on how to use `CoreDataSource`, see [AutomaticViewController.swift][avc]. To see the steps you'd have to go through to access stored data _without_ it, see [ManualViewController.swift][mvc].

## Migrations

Support for "easier" Core Data migrations is currently evolving, but here's what you can expect right now. Flapjack has a `Migrator` class that you can conform to, and it's this object you'll use to provide your `DataAccess` class with a way to migrate your data store. It's a relatively sparse protocol right now, but if you look at the Core Data implementation of this object (`CoreDataMigrator`), you can see how this comes together. This is a pretty close adaptation of the way we handle migrations in our iOS apps at O'Reilly Media. Here's what happens, step by step.

- By conforming to `DataAccessDelegate`, you'll be notified when the stack is ready for a `Migrator`.
- In response to this delegate call, you'll initialize and return a `CoreDataMigrator` by providing the `storeURL` and `bundle` where the data store file and compiled model can be found, respectively.
- Then the `DataAccess` object should handle the rest, which is essentially a call to `migrate()`.
- Upon invocation of `migrate()`, a temporary folder is made to house any intermediary files.
- Then your compiled data model is scanned for all available model versions, and then we also try and figure out which version is the _current_ version, and then we build an iterative list of versions by which to migrate (support for supplying a custom list of versions to migrate is forthcoming).
- Then, between each version, we either process a heavyweight migration (if an explicit mapping model is found) or a lightweight migration (if an implicit mapping model can be inferred).

## Authors

- Matt Blackmon ([@mblackmon][mbl])
- Laura Dickey ([@lj-dickey][ljd])
- Ben Kreeger ([@kreeger][krg])
- Scott Starr ([@awaltzforvenus][sst])

## License

Flapjack is available under the MIT license. See [LICENSE][lic] file for more info.

[orm]: https://oreilly.com
[cpd]: https://cocoapods.org
[pcm]: https://github.com/oreillymedia/flapjack/blob/master/Example/Flapjack/Core%20Data/Pancake.swift
[avc]: https://github.com/oreillymedia/flapjack/blob/master/Example/Flapjack/AutomaticViewController.swift
[mvc]: https://github.com/oreillymedia/flapjack/blob/master/Example/Flapjack/ManualViewController.swift
[mbl]: https://github.com/mblackmon
[ljd]: https://github.com/lj-dickey
[krg]: https://github.com/kreeger
[sst]: https://github.com/awaltzforvenus
[lic]: https://github.com/oreillymedia/flapjack/blob/master/LICENSE
[spm]: https://swift.org/package-manager/