A small set of utilities to make working with CoreData and Swift a bit easier.

Last update: Jul 19, 2022

SuperRecord

Build Status Carthage compatible CocoaPods

===================

SUPPORTS SWIFT 2.0 from Version >= 1.4 **
SUPPORTS SWIFT 1.2 from Version <= 1.3
Both iOS and WatchOS

A Swift CoreData Framework consisting of several Extensions and Helpers to bring some love and take the hassle out of common CoreData tasks.


Each piece of functionality can be used independently and discreetly, so there is no need for a "buy in" to the whole project. For example, you could use your own NSFetchedResultsController or NSManagedObjectContext with any of the finders or even the SuperFetchedResultsControllerDelegate

I'd like to make a big shout-out to MagicalRecord, which I think lay great foundations for these kind of projects. Although its had its ups and downs, it seems under heavy development. This Swift SuperRecord project was obviously heavily inspired by work done in MagicalRecord.

Features

SuperRecord consists of several Extensions to add MagicalRecord/ActiveRecord style "finders" to your NSManagedObject subclasses, a FetchResultsControllerDelegate class to handle safe batch updates to both UITableView and UICollectionView and an experimental Boilerplate CoreData Stack Singleton.

The project has been built over several versions of Swift so some choices may seem strange at first.

Adding SuperRecord to your project

Method 1 (CocoaPods)

To integrate SuperRecord into your Xcode project using CocoaPods, specify it in your Podfile:

use_frameworks!
pod 'SuperRecord'

or for watch support use the subspec

    pod 'SuperRecord/watch'
  • Build
  • Add import SuperRecord

Method 2 (Carthage)

To integrate SuperRecord into your Xcode project using Carthage, specify it in your Cartfile:

github "michaelarmstrong/SuperRecord"

Method 3 (Submodule)

git submodule add https://github.com/michaelarmstrong/SuperRecord.git SuperRecord
  • Drag the checked out submodule into Xcode
  • Click on your main application target
  • Open "Build Phases"
  • Add SuperRecord.framework under "Target Dependencies"

Method 4 (Manually)

git clone https://github.com/michaelarmstrong/SuperRecord.git

Now add the source files into your project directly.

Core Files

  • NSManagedObjectExtension.swift This extension is responsible for most of the "finder" functionality and has operations such as deleteAll(), findOrCreateWithAttribute() createEntity() and allows you to specify your own NSManagedObjectContext or use the default one (running on the main thread).

  • NSFetchedResultsControllerExtension.swift In constant development, this Extension allows the easy creation of FetchedResultsControllers for use with UICollectionView and UITableView that utilise the SuperFetchedResultsControllerDelegate for safe batch updates.

  • SuperFetchedResultsControllerDelegate.swift heavily inspired by past-projects i've worked on along with other popular open source projects. This handles safe batch updates to UICollectionView and UITableView across iOS 7 and iOS 8. It can be used on its own with your NSFetchedResultsController or alternatively, its automatically used by the NSFetchedResultsControllerExtension methods included in SuperRecord.

  • SuperCoreDataStack.swift a boilerplate experimental main thread CoreData stack. Can be used either as a sqlite store or in memory store. Simply by calling SuperCoreDataStack.defaultStack() for SQLite or SuperCoreDataStack.inMemoryStack() for an in memory store. Of course you have access to your context .context / .saveContext()

Usage

Create a new Entity

Assuming you have an NSManagedObject of type "Pokemon" you could do the following

let pokemon = Pokemon.createNewEntity() as Pokemon

Please add @objc(className) above the class name of all your NSManagedObject subclasses (as shown in the demo project) for now. Better support will be coming in the future.

Creating an NSFetchedResultsController

There are many factory methods for your convenience that SuperRecord adds to NSFetchedResultsController to make your life simpler, yet still powerful. As always, you don't have to use these with SuperRecord, however they are there for your convenience. Many of the SuperRecord factory methods will handle safe batch updates for you to your passed collectionView or tableView. No more song and dance.

lazy var fetchedResultsController: NSFetchedResultsController = self.superFetchedResultsController()

func superFetchedResultsController() -> NSFetchedResultsController {
return NSFetchedResultsController.superFetchedResultsController("Pokemon", tableView: tableView)
}

Or for some more advanced usage (collectionView with multiple sections and a predicate with automatic batch updates):

let sortDescriptors = [NSSortDescriptor(key: "evolutionLevel", ascending: false),NSSortDescriptor(key: "level", ascending: false)]
let predicate = NSPredicate(format: "trainer = %@", self.trainer)!
let tempFetchedResultsController = NSFetchedResultsController.superFetchedResultsController("Pokemon", sectionNameKeyPath: "evolutionLevel", sortDescriptors: sortDescriptors, predicate: predicate, collectionView: self.collectionView, context: context)	

With Pokemon being the entity name of your NSManagedObject.

Delete Entities

I'm planning on adding much more powerful functionality around Delete soon, such as deleteAllWithPredicate() or deleteEntity(), right now all that is available is

Pokemon.deleteAll()

Method Listing

This isn't an exhaustive list of all methods and classes, however it includes some of the most useful ones.

NSManagedObjectExtension

  • findAllWithPredicate(predicate: NSPredicate!, context: NSManagedObjectContext) -> NSArray
  • findAllWithPredicate(predicate: NSPredicate!) -> NSArray
  • deleteAll(context: NSManagedObjectContext) -> Void
  • deleteAll() -> Void
  • findAll(context: NSManagedObjectContext) -> NSArray
  • findAll() -> NSArray
  • findFirstOrCreateWithPredicate(predicate: NSPredicate!) -> NSManagedObject
  • findFirstOrCreateWithPredicate(predicate: NSPredicate!, context: NSManagedObjectContext) -> NSManagedObject
  • createNewEntity() -> NSManagedObject
  • findFirstOrCreateWithAttribute(attribute: NSString!, value: NSString!, context: NSManagedObjectContext) -> NSManagedObject
  • findFirstOrCreateWithAttribute(attribute: NSString!, value: NSString!) -> NSManagedObject

NSFetchedResultsControllerExtension

NSFetchedResultsControllers created using the below methods will automatically handle safe batch updates to the passed UITableView or UICollectionView

  • superFetchedResultsController(entityName: NSString!, collectionView: UICollectionView) -> NSFetchedResultsController
  • superFetchedResultsController(entityName: NSString!, tableView: UITableView) -> NSFetchedResultsController
  • superFetchedResultsController(entityName: NSString!, sectionNameKeyPath: NSString?, sortDescriptors: NSArray?, predicate: NSPredicate?, collectionView: UICollectionView!, context: NSManagedObjectContext!) -> NSFetchedResultsController
  • superFetchedResultsController(entityName: NSString!, sectionNameKeyPath: NSString?, sortDescriptors: NSArray?, predicate: NSPredicate?, tableView: UITableView!, context: NSManagedObjectContext!) -> NSFetchedResultsController

NSFetchedResultsControllers created using the below methods require you to use your own NSFetchedResultsControllerDelegate class

  • superFetchedResultsController(entityName: NSString!, sectionNameKeyPath: NSString?, sortedBy: NSString?, ascending: Bool, tableView: UITableView!, delegate: NSFetchedResultsControllerDelegate) -> NSFetchedResultsController
  • superFetchedResultsController(entityName: NSString!, sectionNameKeyPath: NSString?, sortedBy: NSString?, ascending: Bool, collectionView: UICollectionView!, delegate: NSFetchedResultsControllerDelegate) -> NSFetchedResultsController
  • superFetchedResultsController(entityName: NSString!, sectionNameKeyPath: NSString?, sortDescriptors: NSArray?, predicate: NSPredicate?, collectionView: UICollectionView!, delegate: NSFetchedResultsControllerDelegate) -> NSFetchedResultsController
  • superFetchedResultsController(entityName: NSString!, sectionNameKeyPath: NSString?, sortDescriptors: NSArray?, predicate: NSPredicate?, tableView: UITableView!, delegate: NSFetchedResultsControllerDelegate) -> NSFetchedResultsController
  • superFetchedResultsController(entityName: NSString!, sectionNameKeyPath: NSString?, sortDescriptors: NSArray?, predicate: NSPredicate?, collectionView: UICollectionView!, delegate: NSFetchedResultsControllerDelegate, context: NSManagedObjectContext!) -> NSFetchedResultsController
  • superFetchedResultsController(entityName: NSString!, sectionNameKeyPath: NSString?, sortDescriptors: NSArray?, predicate: NSPredicate?, tableView: UITableView!, delegate: NSFetchedResultsControllerDelegate, context: NSManagedObjectContext!) -> NSFetchedResultsController

##Developer Notes

This whole project is a work in progress, a learning exercise and has been released "early" so that it can be built and collaborated on with feedback from the community. I'm using it in a project I work on everyday, so hopefully it'll improve and gain more functionality, thread-safety and error handling over time.

Currently work is in progress to replace the SuperCoreDataStack with a much better, more flexible and cleaner implementation + a wiki is in progress. If you'd like to help out, please get in touch or open a PR.


GitHub

https://github.com/michaelarmstrong/SuperRecord
Comments
  • 1. Remove @objc(className)

    In the current version of Swift NSStringFromClass(ClassName) returns // ProjectName.Pokemon.

    So we can easily add a new function in NSManagedObject that returns only the ClassName without forcing the user to add @objc(ClassName) to each NSManagedObject.

    What do you think @michaelarmstrong?

    Reviewed by PGLongo at 2015-03-11 21:30
  • 2. Add Sample Model with UnitTest

    Hi,

    I have created a sample test that could be a good starting point.

    Changes:

    1. Created sample Model, with two entities: Pokemon and Type.
    2. Created sample test
    3. Changed extensions visibility to public, to allow framework use.
    4. Added completionHandler (default values is nil) in findAllWithPredicate and findFirstOrCreateWithPredicate. The completion handler is necessary to allow async test and it should be added to all the function that call findAllWithPredicate or findFirstOrCreateWithPredicate
    5. Added SuperRecordTestsTestCase : XCTestCase, ovveriding setUp function initializing the test managedObjectContext.
    Reviewed by PGLongo at 2014-11-18 10:56
  • 3. Fixed random SQLite error code:522, 'not an error'

    I've fixed an error that can affect some Simulator (mine for example) running unit-test.

    2015-10-21 17:10:24.579 xctest[703:8687] CoreData: error: (522) I/O error for database at /Users//Library/Developer/CoreSimulator/Devices/5E7487DC-2883-45F6-8CC1-FB90A2EB73C1/data/Documents/Pokemon.sqlite. SQLite error code:522, 'not an error' fatal error: 'try!' expression unexpectedly raised an error: Error Domain=NSCocoaErrorDomain Code=522 "(null)" UserInfo={NSSQLiteErrorDomain=522, NSUnderlyingException=I/O error for database at /Users//Library/Developer/CoreSimulator/Devices/5E7487DC-2883-45F6-8CC1-FB90A2EB73C1/data/Documents/Pokemon.sqlite. SQLite error code:522, 'not an error', NSFilePath=/Users/*/Library/Developer/CoreSimulator/Devices/5E7487DC-2883-45F6-8CC1-FB90A2EB73C1/data/Documents/Pokemon.sqlite}: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-700.0.59/src/swift/stdlib/public/core/ErrorType.swift, line 50

    Running multiple tests the database seems to be corrupted, maybe caused by sqlite temporary files.

    I've improved NSPersistentStore deletion in tearDown.

    Thanks

    Reviewed by PGLongo at 2015-10-23 10:40
  • 4. Swift 1.2

    SuperRecord doesn't want to compile with the changes in Swift 1.2.

    The major issue is the introduction of the failable cast as! over as, and there are some minor issues with optionality in CoreData and UIKit, and a few type checking issues too. I have to ship something before weekend, so I'll be working on this. Have you started any work on it yet? Or shall I just get stuck into it?

    Thanks

    screen shot 2015-04-15 at 09 12 48

    Reviewed by adamwaite at 2015-04-15 08:25
  • 5. cannot build demo

    hello, the demo will not build.

    error is SuperRecordDemo.xcodeproj cannot be opened because it is missing its project.pbxproj file.

    xcode 6.1.1 iOS 8.1

    Reviewed by gaming-hacker at 2015-01-12 15:06
  • 6. Implementing magicalrecord style handling of store structure change

    So one of the things I liked about magical record is that it if your store changes.. instead of crashing on app launch (and loading of the persistent store from file system).. it attempts to just delete the existing store and create a new one.

    This is useful while your app is in development because currently if you're pushing out builds every day or couple of days to your team of internal test users, you have 3 options for handling database changes.

    1. Do a migration for every build
    2. Tell them if it crashes, to delete and reinstall

    With this implementation.. you can just tell them that they may lose data between installs.

    Discussion areas:

    1. This currently blindly deletes and recreates the store on any error.. would be interested in narrowing that.. and have some ideas for how to do that, but thought I'd ask for ideas on what other errors can come through this.
    2. Might be good to just have this as a feature that can be enabled/disabled. Or available for development only? Interested in thoughts!
    Reviewed by jyaunches at 2016-06-16 16:41
  • 7. Removed duplicate functions using optional values

    Hi!

    I have removed all:

    let context = SuperCoreDataStack.defaultStack.managedObjectContext!
    

    using swift optional values, so functions like

    class func findFirstOrCreateWithAttribute(attribute: NSString!, value: NSString!, context: NSManagedObjectContext) -> NSManagedObject 
    

    Became

    class func findFirstOrCreateWithAttribute(attribute: NSString!, value: NSString!, context: NSManagedObjectContext = SuperCoreDataStack.defaultStack.managedObjectContext!) -> NSManagedObject 
    
    Reviewed by PGLongo at 2014-11-17 18:01
  • 8. Rromanchuk swift 2.0

    We need to get this finalized and merged in ASAP... Swift 2.0 is out guys. @PGLongo @rromanchuk I've made a couple of small changes, but it needs some attention still.

    Reviewed by michaelarmstrong at 2015-09-18 12:35
  • 9. Invalid StackName if target contains space

    If the target contains space the model is automatically named with '_' instead of ' ', for example 'Pokemon Ruby Omega' became 'Pokemon_Ruby_Omega'

    let stackName = (infoDictionary!["CFBundleName"] as NSString
    

    We could change stackName like this

    let stackName = (infoDictionary!["CFBundleName"] as NSString).stringByReplacingOccurrencesOfString(" ", withString: "_")
    

    Maybe we can also change NSString to String.

    Reviewed by PGLongo at 2015-03-17 09:49
  • 10. Enable Travis

    @michaelarmstrong I have added Travis CI support, but I cannot change the settings of the repo so I can't enable the GitHub Webhook. It's very simple, follow this instructions.

    Thanks

    Reviewed by PGLongo at 2015-03-10 14:10
  • 11. Added more complex NSPredicate and predicate builder

    Added predicateBuilder that create a NSPredicate from 3 parameters:

    1. Attribute name
    2. Attribute value (any object)
    3. NSPredicateOperator
    
    enum NSPredicateOperator : String {
        case And = "AND"
        case Or = "OR"
        case In = "IN"
        case Equal = "=="
        case NotEqual = "!="
        case GreaterThan = ">"
        case GreaterThanOrEqual = ">="
        case LessThan = "<"
        case LessThanOrEqual = "<="
    }
    
        class func predicateBuilder(attribute: String!, value: AnyObject, predicateOperator: NSPredicateOperator ) -> NSPredicate? {
            var predicate = NSPredicate(format: "%K \(predicateOperator.rawValue) $value", attribute)
            predicate = predicate?.predicateWithSubstitutionVariables(["value" : value]);
            return predicate
        }
    

    For example:

    NSPredicate.predicateBuilder("level", value: 16, predicateOperator: .LessThan)
    NSPredicate(format: "level < 16")
    ...
    NSPredicate.predicateBuilder("name", value: "Charmender", predicateOperator: .Equal)
    NSPredicate(format: "name == \"Charmender\"")
    

    Using predicateBuilder I have improved findFirstOrCreateWithAttribute, now it works with all value and not only with strings

    class func findFirstOrCreateWithAttribute(attribute: String!, value: AnyObject!, context: NSManagedObjectContext = SuperCoreDataStack.defaultStack.managedObjectContext!, handler: ((NSError!) -> Void)! = nil) -> NSManagedObject {
            var predicate = NSPredicate.predicateBuilder(attribute, value: value, predicateOperator: .Equal)
            return findFirstOrCreateWithPredicate(predicate, context: context, handler)
    }
    

    I have also added a new init method to allow the creation of more complex NSPredicate (the kind of firstCondition AND ( secondCondition OR thirdCondition)

     convenience init?(firstPredicate : NSPredicate, secondPredicate: NSPredicate, predicateOperator: NSLogicOperator ) {
                self.init(format: "(\(firstPredicate)) \(predicateOperator.rawValue) (\(secondPredicate))")
    }
    
    Reviewed by PGLongo at 2014-12-14 16:44
  • 12. Use Generics in NSManagedObject Extension

    I think that we could change NSManagedObject extension using generics.

    For example:

    Now

    func createNewEntity(context:) -> NSManagedObject {}
    ...
    let pokemon = Pokemon.createNewEntity() as! Pokemon
    

    After

    class func createNewEntity <T> (context:) ->  T {}
    ...
    let pokemon : Pokemon = Pokemon.createNewEntity()
    

    In my opinion is more secure because you receive a compile error if the type is not explicit!

    Reviewed by PGLongo at 2015-10-23 16:25
  • 13. Nil pointer Exception if target contains a space

    If the target as a name with space like "Super Record" iOS replace the space with an "_" so the correct name for the model become "Super_Record.momd"

    I have solved temporarily changing:

    let infoDictionary = NSBundle.mainBundle().infoDictionary as NSDictionary?
    let stackName = (infoDictionary!["CFBundleName"] as! String)
    let storeName = stackName + ".sqlite"
    

    with

    let infoDictionary = NSBundle.mainBundle().infoDictionary as NSDictionary?
    let stackName = (infoDictionary!["CFBundleName"] as! String).stringByReplacingOccurrencesOfString(" ", withString: "_")
    let storeName = stackName + ".sqlite"
    

    @michaelarmstrong you should considering it if you are writing the new stack.

    Reviewed by PGLongo at 2015-04-27 14:19
  • 14. SuperCoreStack should be 100% decoupled from NSFetchedResultsControllerExtension

    This extension is a great class, but it needs to be decoupled. https://github.com/michaelarmstrong/SuperRecord/blob/master/SuperRecord/NSFetchedResultsControllerExtension.swift#L102

    We use a more advanced stack so this line gives us problems. In general it's probably better to force the callee to be responsible for sending the MOC, always. In general calling back up to the delegate for the moc is bad practice, especially when there isn't just a single MOC being used everywhere.

    Reviewed by rromanchuk at 2015-03-16 00:11
  • 15. SuperCoreDataStack has no flexibility and doesn't support multi-threading

    SuperCoreDataStack assumes your backing store is named the same as your App name. Additionally it uses the Apple boiler-plate and does not support any CoreData configuration other than a single main thread context.

    Calls also require an explicit mention of either defaultStack or inMemoryStack... this pattern needs improving to a single line of setup in the AppDelegate on app launch.

    It should firstly allow the user to specify which stack to use. Something like:

    SuperCoreDataStack.setup(type: SuperRecordStackType, storeURL: NSURL = someDefault)
    

    So you can do...

    SuperCoreDataStack.setup(.inMemoryStore)
    

    or something like the following.

     //let sharedURLForAppGroup = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("com.someidentifier")
    
    let sharedURLForCloud =      NSFileManager.defaultManager().URLForUbiquityContainerIdentifier("com.someidentifier")
    
    SuperCoreDataStack.setup(.persistentStore, sharedURLForAppGroup)
    

    which gives the user much more flexibility.

    This will also make the stack: property in all related calls purely optional and an override of this setting.

    I will begin this part of the work today.

    The threading model i'll open a separate issue for as I have some ideas which i'd like to share and get commentary back on.

    Reviewed by michaelarmstrong at 2015-03-13 15:08
Feb 13, 2022
­čÄ» PredicateKit allows Swift developers to write expressive and type-safe predicates for CoreData using key-paths, comparisons and logical operators, literal values, and functions.
­čÄ» PredicateKit allows Swift developers to write expressive and type-safe predicates for CoreData using key-paths, comparisons and logical operators, literal values, and functions.

?? PredicateKit PredicateKit is an alternative to NSPredicate allowing you to write expressive and type-safe predicates for CoreData using key-paths,

Aug 6, 2022
iOS app built with UIKit and programatic auto-layouts to learn and apply knowledge. Offline storage using CoreData and Caching

PurpleImage Search Pixabay for images and save them offline to share and view To use: Clone the GitHub project, build and run in Xcode. The API is com

May 10, 2022
A Swift framework that wraps CoreData, hides context complexity, and helps facilitate best practices.
A Swift framework that wraps CoreData, hides context complexity, and helps facilitate best practices.

Cadmium is a Core Data framework for Swift that enforces best practices and raises exceptions for common Core Data pitfalls exactly where you make the

May 17, 2022
A Swift framework that wraps CoreData, hides context complexity, and helps facilitate best practices.
A Swift framework that wraps CoreData, hides context complexity, and helps facilitate best practices.

Cadmium is a Core Data framework for Swift that enforces best practices and raises exceptions for common Core Data pitfalls exactly where you make them.

May 17, 2022
Domain Specific Language to safely build predicates and requests to fetch a CoreData store

SafeFetching This library offers a DSL (Domain Specific Language) to safely build predicates and requests to fetch a CoreData store. Also a wrapper ar

May 8, 2022
Simple IOS notes app written programmatically without storyboard using UIKit and CoreData
Simple IOS notes app written programmatically without storyboard using UIKit and CoreData

Notes Simple Notes app. Swift, UIKit, CoreData Description Simple IOS notes app

Aug 8, 2022
CoreData based Swift ORM
CoreData based Swift ORM

Swift ORM Features Pure swift objects - no more subclasses of NSManagedObject Extensible attribute system - store any type in CoreData storage by impl

Aug 5, 2022
CoreData + UI/Unit Tests + Pure Swift
CoreData + UI/Unit Tests + Pure Swift

What is it? ???? It's a Todo iOS Application with offline support by the use of Core Data which has been developed as a code challenge. It's written p

Jun 1, 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

Jun 2, 2022
App agenda with CoreData

iOS Agenda App with CORE DATA Ejemplo de c├│digo para una aplicaci├│n de agenda, gestionando el modelo de datos con CoreData. Built using XCode 13.0 (Sw

Oct 19, 2021
A type-safe, fluent Swift library for working with Core Data
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

Jun 27, 2020
A type-safe, fluent Swift library for working with Core Data
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

Jun 27, 2020
Example repo of working with Core Data in a SwiftUI application

CoreData in SwiftUI This repository serves many purpose. But mostly so I can experiment with Core Data in SwiftUI, and also so I can use it show my ap

Jan 30, 2022
Robust CloudKit synchronization: offline editing, relationships, shared and public databases, field-level deltas, and more.
Robust CloudKit synchronization: offline editing, relationships, shared and public databases, field-level deltas, and more.

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

Aug 3, 2022
A powerful and elegant Core Data framework for Swift.
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

Jul 19, 2022
CloudCore is a framework that manages syncing between iCloud (CloudKit) and Core Data written on native Swift.
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

Aug 3, 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

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

Aug 4, 2022
JSON to Core Data and back. Swift Core Data Sync.
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

Aug 9, 2022