Swiftpack.co - Package - oreillymedia/flapjack
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.

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 for our iOS apps, and if you like what you see, perhaps you will too.

Getting started

CocoaPods

Flapjack will soon be available through CocoaPods. To install it for now, simply add the following line to your Podfile:

pod 'Flapjack', git: 'https://github.com/oreillymedia/flapjack.git', tag: '0.6.1'
# If you're using Core Data...
pod 'Flapjack/CoreData', git: 'https://github.com/oreillymedia/flapjack.git', tag: '0.6.1'
# If you're targeting iOS and want some helpers...
pod 'Flapjack/UIKit', git: 'https://github.com/oreillymedia/flapjack.git', tag: '0.6.1'

And run pod install at the command line.

Carthage

Make the following entry in your Cartfile:

github "oreillymedia/flapjack"

Then run carthage update. If this is your first time using Carthage in the project, you'll need to go through some additional steps as explained over at Carthage. Carthage will build three frameworks: Flapjack, FlapjackCoreData, and FlapjackUIKit. You'll need Flapjack as a base framework, and if you're using it with Core Data and/or UIKit, you'll want to bring in those frameworks as well (and reference those in your import statements where necessary).

Swift Package Manager

Support for Swift Package Manager is in its initial stage; the project does not build with support for Core Data or UIKit, since those frameworks are not packaged or available for Swift Package Manager (since SPM only builds for the host platform, UIKit is currently impossible to use as a dependency for FlapjackUIKit). In order to use Flapjack by itself, add the following as a dependency to the dependencies array in your Package.swift file:

.package(url: "https://github.com/oreillymedia/flapjack.git", .upToNextMajor(from: "0.4.5"))

Then you'll specify Flapjack as a dependency of the target in which you wish to use it.

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).

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 that has the fields identifier, flavor, and radius defined in a Core Data model, this would look like the following.

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.

// 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:

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.

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.

import Flapjack

let dataSourceFactory = CoreDataSourceFactory(dataAccess: dataAccess)
let queryAttributes = ["radius": 2.0, "flavor": "Chocolate Chip"]
let dataSource: CoreDataSource<Pancake> = 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. To see the steps you'd have to go through to access stored data without it, see ManualViewController.swift.

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

License

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

Github

link
Stars: 18

Releases

Crempog — better database recreation, end of iOS 11 support - 2020-11-23T17:11:09

This release fixes a bug that would occur when the database was deleted and data sources were still listening to it; those data sources now drop their objects and reconstruct their fetched results controllers when the database gets recreated. This release also drops support for iOS 11, thus the bump in the minor version number.

Cheesecake v0.6.2: proper SingleCoreDataSource notifications for delete/re-insert - 2020-06-05T15:19:21

This release fixes an issue where SingleCoreDataSources would only get a deletion notification if its watched object got deleted and re-inserted in the same Core Data background save.

Cheesecake v0.6.1: modifiable predicate/sorters on CoreDataSource - 2020-04-29T21:03:58

This release adds modifiability of the predicate and sort descriptors for CoreDataSource, adds a missing API update for UITableView for the enhancements made in 0.6.0, and fixes a bug with Swift Package Manager support.

Cheesecake: DataSource section/item change fixes - 2020-04-26T21:56:43

This release ensures that DataSourceChange and DataSourceSectionChange instances are collected in a specific order and provided to listeners in that specific order so they can be processed in that order, instead of being in an unordered Set. This is necessary to avoid collisions where some change sets could affect an object twice, and in those instances, actions need to happen in a specific order or else you'll end up with runtime errors.

Strawberry: improvements to Core Data support - 2020-02-28T15:28:49

This release changes the mergePolicy assigned to the DataContext vended by vendBackgroundContext to NSMergeByPropertyStoreTrumpMergePolicy to favor in-memory objects over those in the store, and fixes the way we deleteDatabase in CoreDataAccess to ensure potentially botched migration files get deleted no matter what.

Chocolate Chip v0.3.1: A bugfix - 2019-03-28T15:31:07

This release includes a fix for a bug that caused the SingleCoreDataSource.onChange handler to fire way too frequently.

Boysenberry: Swift 5 support, minor bugfixes - 2019-03-28T15:31:44

This release switches Flapjack to require Swift 5, and includes a bugfix from 0.3.1 that caused the SingleCoreDataSource.onChange handler to fire way too frequently.

Chocolate Chip: Rename class, 2 methods - 2019-03-22T21:54:43

This release merely renames CoreSingleDataSource to SingleCoreDataSource, and renames that class's (and CoreDataSource's) execute method to startListening for better clarity of purpose.

Blueberry: Compatibility updates and bugfixes - 2019-03-07T23:24:05

Fixes a handful of issues that came about when trying to integrate Flapjack with one of our internal apps. Many of these changes are things we had already done to similar code inside of the app, but those changes had never made their way back around to Flapjack.

  • DataContext.findOrCreate(_:attributes:) now returns a non-optional tuple
  • NSManagedObjectContext will only persist() if there are actual persistent changed values (thanks to isDirty)
  • Set<>s now get parsed properly when fed into an NSCompoundPredicate using an attributes dictionary
  • CoreSingleDataSource now hinges primarily on an NSPredicate instead of an attributes dictionary
  • CoreSingleDataSource also now listens for contexts being created and torn down, in case the data source's lifecycle outlives that of the context it was listening to
  • CoreSingleDataSource now has a few more convenience initializers
  • CoreDataSource no longer tracks its own NSPersistentStore array
  • Objects now properly get found from mixed-type object sets in an NSManagedObjectContext change notification
  • A new method exists for getting a single entity from a Layer inside of NSMigrationManager
  • A few properties have been marked as public that were previously internal or private
  • MigrationPolicy is now open for subclassing

Buttermilk: Initial release - 2019-03-07T00:31:24

Welcome to Flapjack! See our readme for more information.