A minimalistic, thread safe, non-boilerplate and super easy to use version of Active Record on Core Data.

Overview

Skopelos

logo

Build Status Version License Platform

A minimalistic, thread-safe, non-boilerplate and super easy to use version of Active Record on Core Data. Simply all you need for doing Core Data. Swift 4 flavour.

Objective-C version

General notes

This component aims to have an extremely easy interface to introduce Core Data into your app with almost zero effort.

The design introduced here involves a few main components:

  • CoreDataStack
  • AppStateReactor
  • DALService (Data Access Layer)

CoreDataStack

If you have experience with Core Data, you might know that creating a stack is an annoying process full of pitfalls. This component is responsible for the creation of the stack (in terms of chain of managed object contexts) using the design described here by Marcus Zarra.

      Managed Object Model <------ Persistent Store Coordinator ------> Persistent Store
                                                ^
                                                |
                           Root Context (NSPrivateQueueConcurrencyType)
                                                ^
                                                |
              ------------> Main Context (NSMainQueueConcurrencyType) <-------------
              |                                 ^                                  |
              |                                 |                                  |
       Scratch Context                   Scratch Context                    Scratch Context
(NSPrivateQueueConcurrencyType)   (NSPrivateQueueConcurrencyType)    (NSPrivateQueueConcurrencyType)

An important difference from Magical Record, or other third-party libraries, is that the savings always go in one direction, from scratch contexts down (up direction in the above diagram) to the persistent store. Other components allow you to create scratch contexts that have the private context as parent and this causes the main context not to be updated or to be updated via notifications to merge the context. The main context should be the source of truth and it is tied the UI: having a much simpler approach helps to create a system easier to reason about.

AppStateReactor

You should ignore this one. It sits in the CoreDataStack and takes care of saving the in-flight changes back to disk if the app goes to background, loses focus or is about to be terminated. It's a silent friend who takes care of us.

DALService (Data Access Layer) / Skopelos

If you have experience with Core Data, you might also know that most of the operations are repetitive and that we usually call performBlock/performBlockAndWait on a context providing a block that eventually will call save: on that context as last statement. Databases are all about readings and writings and for this reason our APIs are in the form of read(statements: NSManagedObjectContext -> Void) and writeSync(changes: NSManagedObjectContext -> Void)/writeAsync(changes: NSManagedObjectContext -> Void): 2 protocols providing a CQRS (Command and Query Responsibility Segregation) approach. Read blocks will be executed on the main context (as it's considered to be the single source of truth). Write blocks are executed on a scratch context which is saved at the end; changes are eventually saved asynchronously back to the persistent store without blocking the main thread. The completion handler of the write methods calls the completion handler when the changes are saved back to the persistent store.

In other words, writings are always consistent in the main managed object context and eventual consistent in the persistent store. Data are always available in the main managed object context.

Skopelos is just a subclass of DALService, to give a nice name to the component.

How to use

Import Skopelos.

To use this component, you could create a property of type Skopelos and instantiate it like so:

") ">
self.skopelos = Skopelos(sqliteStack: "<#ModelURL>")

or

", securityApplicationGroupIdentifier: "<#GroupID>") ">
self.skopelos = Skopelos(sqliteStack: "<#ModelURL>", securityApplicationGroupIdentifier: "<#GroupID>")

or

") ">
self.skopelos = Skopelos(inMemoryStack: "<#ModelURL>")

N.B. All the above methods also accept an extra optional argument allowsConcurrentWritings (which defaults to false) to allow using a dedicated scratch context per writing operation. For simple applications, reusing the same scratch context (i.e. using the default value) on writings helps avoiding race conditions when the changes are pushed to the main context.

While it would be acceptable to treat Skopelos as a singleton, it's always best to not use such patter but rather explicitly instantiate a single instance and inject it to parts of the app via dependency injection. Generally speaking, we don't like singletons. They are not testable by nature, clients don't have control over the lifecycle of the object and they break some principles. For these reasons, the library comes free of singletons.

You could inherit from Skopelos to:

  • wrap it into an interface that is specific to you use-case
  • override handleError(_error: NSError) to perform specific actions whenever an error is encountered

Here is an example:

").url(forResource: "<#DataModel>", withExtension: "momd")! weak var delegate: SkopelosClientDelegate? class func sqliteStack() -> Skopelos { return Skopelos(sqliteStack: modelURL) } class func inMemoryStack() -> Skopelos { return Skopelos(inMemoryStack: modelURL) } override func handleError(_ error: NSError) { DispatchQueue.main.async { self.delegate?.handle(error) } } } ">
protocol SkopelosClientDelegate: class {
    func handle(_ error: NSError)
}

class SkopelosClient: Skopelos {

    static let modelURL = Bundle(identifier: "<#com.mydomain.myapp>").url(forResource: "<#DataModel>", withExtension: "momd")!

    weak var delegate: SkopelosClientDelegate?

    class func sqliteStack() -> Skopelos {
        return Skopelos(sqliteStack: modelURL)
    }

    class func inMemoryStack() -> Skopelos {
        return Skopelos(inMemoryStack: modelURL)
    }

    override func handleError(_ error: NSError) {
        DispatchQueue.main.async {
            self.delegate?.handle(error)
        }
    }
}

Readings and writings

Speaking of readings and writings, let's do now a comparison between some standard Core Data code and code written with Skopelos.

Standard Core Data reading:

__block NSArray *results = nil;

NSManagedObjectContext *context = ...;
[context performBlockAndWait:^{

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:NSStringFromClass(User)
    inManagedObjectContext:context];
    [request setEntity:entityDescription];

    NSError *error;
    results = [context executeFetchRequest:request error:&error];
}];

return results;

Standard Core Data writing:

NSManagedObjectContext *context = ...;
[context performBlockAndWait:^{

    User *user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(User)
    inManagedObjectContext:context];
    user.firstname = @"John";
    user.lastname = @"Doe";

    NSError *error;
    [context save:&error];
    if (!error)
    {
        // continue to save back to the store
    }
}];

Skopelos reading:

skopelosClient.read { context in
    let users = User.SK_all(context)
    print(users)
}

Skopelos writing:

// Sync
skopelosClient.writeSync { context in
    let user = User.SK_create(context)
    user.firstname = "John"
    user.lastname = "Doe"
}

skopelosClient.writeSync({ context in
    let user = User.SK_create(context)
    user.firstname = "John"
    user.lastname = "Doe"
    }, completion: { (error: NSError?) in
        // changes are saved to the persistent store
})

// Async
skopelosClient.writeAsync { context in
    let user = User.SK_create(context)
    user.firstname = "John"
    user.lastname = "Doe"
}

skopelosClient.writeAsync({ context in
    let user = User.SK_create(context)
    user.firstname = "John"
    user.lastname = "Doe"
}, completion: { (error: NSError?) in
    // changes are saved to the persistent store
})

Skopelos also supports chaining:

skopelosClient.writeSync { context in
    user = User.SK_create(context)
    user.firstname = "John"
    user.lastname = "Doe"
}.writeSync { context in
    if let userInContext = user.SK_inContext(context) {
        userInContext.SK_remove(context)
    }
}.read { context in
    let users = User.SK_all(context)
    print(users)
}

The NSManagedObject category provides CRUD methods always explicit on the context. The context passed as parameter should be the one received in the read or write block. You should always use these methods from within read/write blocks. Main methods are:

static func SK_create(context: NSManagedObjectContext) -> Self
static func SK_numberOfEntities(context: NSManagedObjectContext) -> Int
func SK_remove(context: NSManagedObjectContext)
static func SK_removeAll(context: NSManagedObjectContext)
static func SK_all(context: NSManagedObjectContext) -> [Self]
static func SK_all(predicate: NSPredicate, context:NSManagedObjectContext) -> [Self]
static func SK_first(context: NSManagedObjectContext) -> Self?

Mind the usage of SK_inContext: to retrieve an object in different read/write blocks (same read blocks are safe).

Thread-safety notes

All the accesses to the persistence layer done via a DALService instance are guaranteed to be thread-safe.

It is highly suggested to enable the flag -com.apple.CoreData.ConcurrencyDebug 1 in your project to make sure that you don't misuse Core Data in terms of threading and concurrency (by accessing managed objects from different threads and similar errors).

This component doesn't aim to introduce interfaces with the goal of hiding the concept of ManagedObjectContext: it would open up the doors to threading issues in clients' code as developers should be responsible to check for the type of the calling thread at some level (that would be ignoring the benefits that Core Data gives us). Therefore, our design forces to make all the readings and writings via the DALService and the ManagedObject category methods are intended to always be explicit on the context (e.g. SK_create).

Clients

Skopelos is used in production in the following products:

Comments
  • Update README.md terminology for managed object contexts

    Update README.md terminology for managed object contexts

    This PR replaces occurrences of 'slave' with 'child' in the README.

    Hi @albertodebortoli ! 😄

    The "slave/master" terminology can be unwelcoming for some people. There are precedents for this naming scheme, but also precedents to change it to avoid potentially offensive language.

    Also, Core Data documentation uses "parent/child" nomenclature — so this makes your docs more consistent with those.

    Hope you'll accept this PR! 😊

    opened by jessesquires 4
  • Swift 3 version?

    Swift 3 version?

    Hello! Just wanted to play with CoreData, but xCode can't let me import Swift-Legacy libraries in project... Any news about swift3 version of Skopelos?

    opened by Luccifer 3
  • Add persistent store and migration (if needed) off the main thread re…

    Add persistent store and migration (if needed) off the main thread re…

    This PR moves the initialization off the main thread regardless of the provided callback. This should protect the caller from having the app killed by the WatchDog if the SQLite is too big to migrate in few seconds.

    opened by gigisommo 2
  • Double allocating main and root moc?

    Double allocating main and root moc?

    Hi, first of all, great code you provide.

    By looking at the code at CoreDataStack.swift class, init which calls the initialize method at the end. Both of method creates the main and root contexts. Perhaps, designated initializer initializes the global variable, but I don't understand the reason initialize method calls again. Is there any reason?

    opened by YoonLee 1
  • Add shouldAddStoreAsynchronously flag to CoreDataStack and Skopelos

    Add shouldAddStoreAsynchronously flag to CoreDataStack and Skopelos

    • [x] Make explicit the possibility to add the store in background
    • [x] Use the property naming used by NSPersistentStoreDescription API
    • [x] Refactor and expand unit tests
    opened by albertodebortoli 0
  • Improve error handling

    Improve error handling

    • [x] Rename handle(error: NSError) to handleError(_ error: NSError)
    • [x] Call handleError in case the stack fails pushing/saving the changes to the main context
    opened by albertodebortoli 0
  • Use a single slave context to avoid undesired concurrent updates

    Use a single slave context to avoid undesired concurrent updates

    Skopelos has been developed with simplicity in mind. Concurrency is hard and allowing multiple scratch contexts to perform writing operations can cause unexpected behaviours when saving them to the main context and ultimately to the persistent store. For instance, performing the same async operation twice could save obj1 in the slaveContext1, obj2 in the slaveContext2 ultimately causing the object (i.e. the instances obj1 and obj2) to be duplicated in the persistent store even though there should be only one. Having a single context for the writings still allows nested writings. Nested readings were already allowed.

    opened by albertodebortoli 0
  • Bump tzinfo from 1.2.5 to 1.2.10

    Bump tzinfo from 1.2.5 to 1.2.10

    Bumps tzinfo from 1.2.5 to 1.2.10.

    Release notes

    Sourced from tzinfo's releases.

    v1.2.10

    TZInfo v1.2.10 on RubyGems.org

    v1.2.9

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    TZInfo v1.2.9 on RubyGems.org

    v1.2.8

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    TZInfo v1.2.8 on RubyGems.org

    v1.2.7

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    TZInfo v1.2.7 on RubyGems.org

    v1.2.6

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.

    TZInfo v1.2.6 on RubyGems.org

    Changelog

    Sourced from tzinfo's changelog.

    Version 1.2.10 - 19-Jul-2022

    Version 1.2.9 - 16-Dec-2020

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    Version 1.2.8 - 8-Nov-2020

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    Version 1.2.7 - 2-Apr-2020

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    Version 1.2.6 - 24-Dec-2019

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.
    Commits
    • 0814dcd Fix the release date.
    • fd05e2a Preparing v1.2.10.
    • b98c32e Merge branch 'fix-directory-traversal-1.2' into 1.2
    • ac3ee68 Remove unnecessary escaping of + within regex character classes.
    • 9d49bf9 Fix relative path loading tests.
    • 394c381 Remove private_constant for consistency and compatibility.
    • 5e9f990 Exclude Arch Linux's SECURITY file from the time zone index.
    • 17fc9e1 Workaround for 'Permission denied - NUL' errors with JRuby on Windows.
    • 6bd7a51 Update copyright years.
    • 9905ca9 Fix directory traversal in Timezone.get when using Ruby data source
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Bump cocoapods-downloader from 1.2.2 to 1.6.3

    Bump cocoapods-downloader from 1.2.2 to 1.6.3

    Bumps cocoapods-downloader from 1.2.2 to 1.6.3.

    Release notes

    Sourced from cocoapods-downloader's releases.

    1.6.3

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1

    Enhancements
    • None.
    Bug Fixes
    • Fix "can't modify frozen string" errors when pods are integrated using the branch option
      buju77 #10920

    1.5.0

    ... (truncated)

    Changelog

    Sourced from cocoapods-downloader's changelog.

    1.6.3 (2022-04-01)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2 (2022-03-28)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1 (2022-03-23)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0 (2022-03-22)

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1 (2021-09-07)

    Enhancements
    • None.

    ... (truncated)

    Commits
    • c03e2ed Release 1.6.3
    • f75bccc Disable Bazaar tests due to macOS 12.3 not including python2
    • 52a0d54 Merge pull request #128 from CocoaPods/validate_before_dl
    • d27c983 Ensure that the git pre-processor doesn't accidentally bail also
    • 3adfe1f [CHANGELOG] Add empty Master section
    • 591167a Release 1.6.2
    • d2564c3 Merge pull request #127 from CocoaPods/validate_before_dl
    • 99fec61 Switches where we check for invalid input, to move it inside the download fun...
    • 96679f2 [CHANGELOG] Add empty Master section
    • 3a7c54b Release 1.6.1
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • How to use NSBatchUpdateRequest

    How to use NSBatchUpdateRequest

    Here is the sample code that I am using:

    let dataStore = SkopelosClient.sqliteStack()
    dataStore.writeSync({ context in
                
                // Update Records
                let batchRequest = NSBatchUpdateRequest(
                    entityName: "User"
                )
                batchRequest.propertiesToUpdate = ["lastname": "T123T"]
                batchRequest.resultType = .updatedObjectsCountResultType
                
                do {
                    let result = try? context.execute(batchRequest)
                    print("Success")
                } catch {
                    print("Error")
                }
                
    }).read({ context in
                let users = User.SK_all(context)
                print(users)
    })
    

    I am trying to batch update using Skopelos library. But its not printing log Success or Error. Breakpoint is hitting at line let result = try? context.execute(batchRequest) but not moving ahead.

    Any help will be appriciated.

    opened by shardul89 1
Releases(2.4.0)
Owner
Alberto De Bortoli
Principal Software Engineer @ Just Eat Takeaway. Drive fast but think carefully.
Alberto De Bortoli
Eliminate your Core Data boilerplate code

SSDataKit There is a lot of boilerplate code required to write a Core Data application. This is annoying. In pretty much everything I've written since

Sam Soffes 455 Nov 16, 2022
Super Awesome Easy Fetching for Core Data!

MagicalRecord In software engineering, the active record pattern is a design pattern found in software that stores its data in relational databases. I

Magical Panda Software 10.9k Nov 13, 2022
JSON to Core Data and back. Swift Core Data Sync.

Notice: Sync was supported from it's creation back in 2014 until March 2021 Moving forward I won't be able to support this project since I'm no longer

Nes 2.5k Nov 21, 2022
Core Data Generator (CDG for short) is a framework for generation (using Sourcery) of Core Data entities from plain structs/classes/enums.

Core Data Generator Introduction Features Supported platforms Installation CDG Setup RepositoryType ModelType DataStoreVersion MigrationPolicy Basic U

Lotusflare 18 Sep 19, 2022
Super awesome Swift minion for Core Data (iOS, macOS, tvOS)

⚠️ Since this repository is going to be archived soon, I suggest migrating to NSPersistentContainer instead (available since iOS 10). For other conven

Marko Tadić 306 Sep 23, 2022
QueryKit, a simple type-safe Core Data query language.

QueryKit QueryKit, a simple type-safe Core Data query language. Usage QuerySet<Person>(context, "Person")

QueryKit 1.5k Nov 6, 2022
A type-safe, fluent Swift library for working with Core Data

Core Data Query Interface (CDQI) is a type-safe, fluent, intuitive library for working with Core Data in Swift. CDQI tremendously reduces the amount o

null 31 Oct 26, 2022
A type-safe, fluent Swift library for working with Core Data

Core Data Query Interface (CDQI) is a type-safe, fluent, intuitive library for working with Core Data in Swift. CDQI tremendously reduces the amount o

null 31 Oct 26, 2022
DataKernel is a minimalistic wrapper around CoreData stack to ease persistence operations.

DataKernel What is DataKernel? DataKernel is a minimalistic wrapper around CoreData stack to ease persistence operations. It is heavily inspired by Su

Denis 16 Jun 2, 2022
A powerful and elegant Core Data framework for Swift.

A powerful and elegant Core Data framework for Swift. Usage Beta version. New docs soon... Simple do that: let query = persistentContainer.viewContext

null 782 Nov 6, 2022
CloudCore is a framework that manages syncing between iCloud (CloudKit) and Core Data written on native Swift.

CloudCore CloudCore is a framework that manages syncing between iCloud (CloudKit) and Core Data written on native Swift. Features Leveraging NSPersist

deeje cooley 120 Nov 16, 2022
Unleashing the real power of Core Data with the elegance and safety of Swift

Unleashing the real power of Core Data with the elegance and safety of Swift Dependency managers Contact Swift 5.4: iOS 11+ / macOS 10.13+ / watchOS 4

John Estropia 3.6k Nov 26, 2022
JustPersist is the easiest and safest way to do persistence on iOS with Core Data support out of the box.

JustPersist JustPersist is the easiest and safest way to do persistence on iOS with Core Data support out of the box. It also allows you to migrate to

Just Eat 167 Mar 13, 2022
JSQCoreDataKit - A swifter Core Data stack

JSQCoreDataKit A swifter Core Data stack About This library aims to do the following: Encode Core Data best practices, so you don't have to think "is

Jesse Squires 595 Nov 16, 2022
HitList is a Swift App shows the implementation of Core Data.

HitList HitList is a Swift App shows the implementation of Core Data. It is the demo app of Ray Wenderlich's tech blog. For details please reference G

Kushal Shingote 3 Dec 17, 2021
A synchronization framework for Core Data.

Core Data Ensembles Author: Drew McCormack Created: 29th September, 2013 Last Updated: 15th February, 2017 Ensembles 2 is now available for purchase a

Drew McCormack 1.6k Nov 16, 2022
Core Data code generation

mogenerator Visit the project's pretty homepage. Here's mogenerator's elevator pitch: mogenerator is a command-line tool that, given an .xcdatamodel f

Wolf Rentzsch 3k Nov 24, 2022
A feature-light wrapper around Core Data that simplifies common database operations.

Introduction Core Data Dandy is a feature-light wrapper around Core Data that simplifies common database operations. Feature summary Initializes and m

Fuzz Productions 33 May 11, 2022
The Big Nerd Ranch Core Data Stack

BNR Core Data Stack The BNR Core Data Stack is a small Swift framework that makes it both easier and safer to use Core Data. A better fetched results

Big Nerd Ranch 561 Jul 29, 2022