100% Swift Simple Boilerplate Free Core Data Stack. NSPersistentContainer

Last update: Jun 7, 2022

DATAStack

DATAStack helps you to alleviate the Core Data boilerplate. Now you can go to your AppDelegate remove all the Core Data related code and replace it with an instance of DATAStack (ObjC, Swift).

  • Easier thread safety
  • Runs synchronously when using unit tests
  • No singletons
  • SQLite and InMemory support out of the box
  • Easy database drop method
  • Shines with Swift
  • Compatible with Objective-C
  • Free

Table of Contents

Running the demos

  • Clone the repository
  • Open the Demo.xcodeproj
  • Enjoy!

Initialization

You can easily initialize a new instance of DATAStack with just your Core Data Model name (xcdatamodel).

Swift

let dataStack = DATAStack(modelName:"MyAppModel")

Objective-C

DATAStack *dataStack = [[DATAStack alloc] initWithModelName:@"MyAppModel"];

There are plenty of other ways to intialize a DATAStack:

  • Using a custom store type.
let dataStack = DATAStack(modelName:"MyAppModel", storeType: .InMemory)
  • Using another bundle and a store type, let's say your test bundle and .InMemory store type, perfect for running unit tests.
let dataStack = DATAStack(modelName: "Model", bundle: NSBundle(forClass: Tests.self), storeType: .InMemory)
  • Using a different name for your .sqlite file than your model name, like CustomStoreName.sqlite.
let dataStack = DATAStack(modelName: "Model", bundle: NSBundle.mainBundle(), storeType: .SQLite, storeName: "CustomStoreName")
  • Providing a diferent container url, by default we'll use the documents folder, most apps do this, but if you want to share your sqlite file between your main app and your app extension you'll want this.
let dataStack = DATAStack(modelName: "Model", bundle: NSBundle.mainBundle(), storeType: .SQLite, storeName: "CustomStoreName", containerURL: sharedURL)

Main Thread NSManagedObjectContext

Getting access to the NSManagedObjectContext attached to the main thread is as simple as using the mainContext property.

self.dataStack.mainContext

or

self.dataStack.viewContext

Background Thread NSManagedObjectContext

You can easily create a new background NSManagedObjectContext for data processing. This block is completely asynchronous and will be run on a background thread.

To be compatible with NSPersistentContainer you can also use performBackgroundTask instead of performInNewBackgroundContext.

Swift

func createUser() {
    self.dataStack.performInNewBackgroundContext { backgroundContext in
        let entity = NSEntityDescription.entityForName("User", inManagedObjectContext: backgroundContext)!
        let object = NSManagedObject(entity: entity, insertIntoManagedObjectContext: backgroundContext)
        object.setValue("Background", forKey: "name")
        object.setValue(NSDate(), forKey: "createdDate")
        try! backgroundContext.save()
    }
}

Objective-C

- (void)createUser {
    [self.dataStack performInNewBackgroundContext:^(NSManagedObjectContext * _Nonnull backgroundContext) {
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:backgroundContext];
        NSManagedObject *object = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:backgroundContext];
        [object setValue:@"Background" forKey:@"name"];
        [object setValue:[NSDate date] forKey:@"createdDate"];
        [backgroundContext save:nil];
    }];
}

When using Xcode's Objective-C autocompletion the backgroundContext parameter name doesn't get included. Make sure to add it.

Clean up

Deleting the .sqlite file and resetting the state of your DATAStack is as simple as just calling drop.

Swift

self.dataStack.drop()

Objective-C

[self.dataStack forceDrop];

Testing

DATAStack is optimized for unit testing and it runs synchronously in testing enviroments. Hopefully you'll have to use less XCTestExpectations now.

You can create a stack that uses in memory store like this if your Core Data model is located in your app bundle:

Swift

let dataStack = DATAStack(modelName: "MyAppModel", bundle: NSBundle.mainBundle(), storeType: .InMemory)

Objective-C

DATAStack *dataStack = [[DATAStack alloc] initWithModelName:@"MyAppModel"
                                                     bundle:[NSBundle mainBundle]
                                                  storeType:DATAStackStoreTypeInMemory];

If your Core Data model is located in your test bundle:

Swift

let dataStack = DATAStack(modelName: "MyAppModel", bundle: NSBundle(forClass: Tests.self), storeType: .InMemory)

Objective-C

DATAStack *dataStack = [[DATAStack alloc] initWithModelName:@"MyAppModel"
                                                     bundle:[NSBundle bundleForClass:[self class]]
                                                  storeType:DATAStackStoreTypeInMemory];

(Hint: Maybe you haven't found the best way to use NSFetchedResultsController, well here it is.)

Migrations

If DATAStack has troubles creating your persistent coordinator because a migration wasn't properly handled it will destroy your data and create a new sqlite file. The normal Core Data behaviour for this is making your app crash on start. This is not fun.

Installation

DATAStack is available through CocoaPods. To install it, simply add the following line to your Podfile:

use_frameworks!

pod 'DATAStack', '~> 6'

DATAStack is also available through Carthage. To install it, simply add the following line to your Cartfile:

github "SyncDB/DATAStack" ~> 6.0

Be Awesome

If something looks stupid, please create a friendly and constructive issue, getting your feedback would be awesome.

Have a great day.

Author

Elvis Nuñez, @3lvis

License

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

GitHub

https://github.com/3lvis/DATAStack
Comments
  • 1. Main thread context not working

    I am facing an issue where operations against the dataStack.mainContext are not effective; for example,

    print(dataStack.mainContext.hasChanges) // true try! dataStack.mainContext.save() print(dataStack.mainContext.hasChanges) // false

    However, when the application reloads, the changes have not been committed to disk (reloading the database programmatically "works", but application restart does not.)

    Simply changing to performInNewBackgroundContext{} makes everything work.

    What might be the cause?

    Reviewed by marcospolanco at 2015-11-27 14:40
  • 2. Create new main context

    Hello! Is it the way to create new context, that can be used in main thread in DATAStack? For example I have «Edit item» view, I load all data there in separate context and if user doesn’t click «Save» all changes in that separate context are reset.

    I’ve found only way to create separate background context (newBackgroundContext), or create newDisposableMainContext, but it is detached from saving to disk, so I can’t merge changes in it to my default mainContext.

    Reviewed by Sorix at 2016-07-23 13:58
  • 3. Data is never hitting persistent store

    I'm not sure what's going on, but I've been blocked by this issue for >= 48 hours. It appears as if none of the insertions on any context are making it to the persistent store coordinator. I've tried everything from changing the mergePolicy, to manually observing contexts. For whatever reason, changes made in a background context, even after invoking persistWithCompletion, never update the main context, even after explicit fetches until the application is killed and restarted.

    I've tweaked the DemoSwift.ViewController to demonstrate the issue:

    import UIKit
    import CoreData
    import DATASource
    
    class ViewController: UITableViewController {
    
        var dataStack: DATAStack
    
        var _backgroundContext: NSManagedObjectContext
    
        lazy var dataSource: DATASource = {
            let request: NSFetchRequest = NSFetchRequest(entityName: "User")
            request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
    
            let dataSource = DATASource(tableView: self.tableView, cellIdentifier: "Cell", fetchRequest: request, mainContext: self.dataStack.mainContext, configuration: { cell, item, indexPath in
                if let name = item.valueForKey("name") as? String, createdDate = item.valueForKey("createdDate") as? NSDate, score = item.valueForKey("score") as? Int {
                    cell.textLabel?.text =  name + " - " + String(score)
                }
            })
    
            return dataSource
        }()
    
        init(dataStack: DATAStack) {
            self.dataStack = dataStack
            self.dataStack.mainContext.stalenessInterval = 0.0
            self._backgroundContext = dataStack.newBackgroundContext("_Background Context")
            self._backgroundContext.stalenessInterval = 0.0
    
            super.init(style: .Plain)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
            self.tableView.dataSource = self.dataSource
    
            let backgroundButton = UIBarButtonItem(title: "Background", style: .Done, target: self, action: #selector(ViewController.createBackground))
            let customBackgroundButton = UIBarButtonItem(title: "Custom", style: .Done, target: self, action: #selector(ViewController.createCustomBackground))
            self.navigationItem.rightBarButtonItems = [customBackgroundButton, backgroundButton]
    
            let mainButton = UIBarButtonItem(title: "Main", style: .Done, target: self, action: #selector(ViewController.createMain))
            let dropAllButton = UIBarButtonItem(title: "Edit Main Object", style: .Done, target: self, action: #selector(ViewController.editMainObject))
            self.navigationItem.leftBarButtonItems = [dropAllButton, mainButton]
        }
    
        func createBackground() {
            self.dataStack.performInNewBackgroundContext { backgroundContext in
                let entity = NSEntityDescription.entityForName("User", inManagedObjectContext: backgroundContext)!
                let object = NSManagedObject(entity: entity, insertIntoManagedObjectContext: backgroundContext)
                object.setValue("Background", forKey: "name")
                object.setValue(NSDate(), forKey: "createdDate")
                try! backgroundContext.save()
            }
        }
    
        func createCustomBackground() {
            let backgroundContext = dataStack.newBackgroundContext("Sync Context")
    
            backgroundContext.performBlock {
                let entity = NSEntityDescription.entityForName("User", inManagedObjectContext: backgroundContext)!
                let object = NSManagedObject(entity: entity, insertIntoManagedObjectContext: backgroundContext)
                object.setValue(backgroundContext.name, forKey: "name")
                object.setValue(NSDate(), forKey: "createdDate")
                try! backgroundContext.save()
            }
        }
    
        func createMain() {
            let entity = NSEntityDescription.entityForName("User", inManagedObjectContext: self.dataStack.mainContext)!
            let object = NSManagedObject(entity: entity, insertIntoManagedObjectContext: self.dataStack.mainContext)
            object.setValue("Main", forKey: "name")
            object.setValue(1, forKey: "score")
            object.setValue(NSDate(), forKey: "createdDate")
            //try! self.dataStack.mainContext.save()
            self.dataStack.persistWithCompletion()
        }
    
        func editMainObject() {
            self.dataStack.performInNewBackgroundContext { _backgroundContext in
                _backgroundContext.stalenessInterval = 0.0
            //_backgroundContext.performBlock {
                let fetchRequest = NSFetchRequest(entityName: "User")
                fetchRequest.predicate = NSPredicate(format: "%K contains[c] %@", "name", "Main")
    
                let backgroundUsers = try! _backgroundContext.executeFetchRequest(fetchRequest) as! [NSManagedObject]
    
                var i: Int32 = 0
                for user in backgroundUsers {
                    i += 1
                    let number = NSNumber(int: (123 * i))
                    user.setValue(number, forKey: "score")
                }
    
                try! _backgroundContext.save()
            }
            //}
        }
    }
    
    

    In this example, pressing the "Main" button will create a new User with a score of 0. That User will not be modified by the editMainObject function when the "Edit Main Object" button is pressed.

    If the app is killed and restarted, the "Edit Main Object" button will cause the objects to be modified and updated.

    I'm really not sure how to proceed, or if I'm doing something wrong.

    Reviewed by justinmakaila at 2016-04-26 18:37
  • 4. Data on main context is not being saved

    When i run the DemoSwift app, then tap on bar button on the top left, new data being created. Then i quit the app (rebuild/force quit), the data disappear, althought all data from background is stored. Am i missing some thing? Or it is a bug?

    Reviewed by alberttra at 2016-04-20 21:34
  • 5. fix Xcode 7.3 TestCheck

    Xcode 7.3 breaks the TestCheck. The last comment in this thread fix it (http://stackoverflow.com/questions/24688512/what-is-the-proper-way-to-detect-if-unit-tests-are-running-at-runtime-in-xcode)

    Reviewed by wh33ler at 2016-04-11 14:26
  • 6. iCloud backup issue

    First of all sorry for my bad Engish.

    I have a project with DATAStack. A lot of users have a problem, when they set a new iPhone from iCloud backup. The data strored in CoreData doesn't exists on the new phone. Should I have to set something to "tell" Apple, that they should backup the sql file? Did you hear about similar issues?

    Thanks for your help!

    Reviewed by kaszap82 at 2018-04-13 13:07
  • 7. Optional relationship not returning data

    Let's assume core data entity structure like this:

    Cart:
    -> CartEntry - To-Many, Optional
    
    CartEntry:
    -> product -> To-One, Optional
    

    There is CartEntry with product in my Cart - coreData seems to be ok. I'm fetching Cart and as a result I've got:

    Cart:
    -> entries: [
          {
                  product: nil
          }
    ] 
    

    However when I changed

    Cart:
    -> CartEntry - To-Many
    
    

    Everything seems to be fine - fetch result got also products.

    Reviewed by pgawlowski at 2017-03-20 11:55
  • 8. MainContext doesn’t update from background contexts

    I’m using NSFetchedResultsController and DATAStack. My NSFetchedResultsController doesn’t update my table if I make any changes in another contexts.

    I use dataStack.mainContext in my NSFetchedResultsController. If I do any updates from mainContext everything is okay. But if I write such code for example:

            dataStack.performInNewBackgroundContext({ (backgroundContext) in
                // Get again NSManagedObject in correct context, 
                // original self.task persists in main context
                let backTask = backgroundContext.objectWithID(self.task.objectID) as! CTask
    
                backTask.completed = true
                let _ = try? backgroundContext.save()
            })
    

    NSFetchedResultsController doesn’t update cells. I will see updated data if I reload application or whole view for example.

    There is no error in data saving (I’ve checked).

    I’ve tried to check NSManagedObjectContextDidSaveNotification is received, but if I do .performFetch() after notification is received I again get old data in table.

    I’ve created a ticket at Stackoverflow, I’ve got reply that backgroundContext doesn’t merge changes with mainContext.

    Reviewed by Sorix at 2016-07-15 14:18
  • 9. Crash when using Sync

    I have quite a big sync process. After running it some times with different data I randomly get the following crash which doesn't happen when I get back to DataSTack version 4.3.0 and Sync version 1.6.5. Any Idea how to fix this?

    Crashed: com.apple.main-thread 0 CoreFoundation 0x181c93070 -[**NSSetM addObject:] + 476 1 CoreFoundation 0x181c8db00 -[NSMutableSet unionSet:] + 776 2 CoreData 0x183ca895c -[NSManagedObjectContext _mergeChangesFromDidSaveDictionary:usingObjectIDs:] + 3144 3 CoreData 0x183ca8c20 -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] + 500 4 DATAStack 0x10049e250 TPA__TFFC9DATAStack9DATAStack24backgroundContextDidSaveFzCSo14NSNotificationT_U_FT_T + 132 5 CoreData 0x183ca908c developerSubmittedBlockToNSManagedObjectContextPerform + 196 6 libdispatch.dylib 0x1817f547c _dispatch_client_callout + 16 7 libdispatch.dylib 0x1817fab84 _dispatch_main_queue_callback_4CF + 1844 8 CoreFoundation 0x181d60dd8 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE** + 12 9 CoreFoundation 0x181d5ec40 __CFRunLoopRun + 1628 10 CoreFoundation 0x181c88d10 CFRunLoopRunSpecific + 384 11 GraphicsServices 0x183570088 GSEventRunModal + 180 12 UIKit 0x186f55f70 UIApplicationMain + 204

    Reviewed by wh33ler at 2016-05-13 10:23
  • 10. Missing parameter name in completion block causes an error

    Hi!

    The method: performInNewBackgroundContext: is missing a parameter name in the completion block.

    //The method is declared like this in the current bridging header:
    [self.dataStack performInNewBackgroundContext:^(NSManagedObjectContext * _Nonnull) {
            <#code#>
        }];
    
    //It should be declared like this:
    [self.dataStack performInNewBackgroundContext:^(NSManagedObjectContext * _Nonnull backgroundContext) {
            <#code#>
        }];
    
    Reviewed by robinsalehjan at 2016-01-05 21:19
  • 11. The UnitTest check shouldn't be run on the production app.

    When I run app in production I don't want the code to run "Is it UnitTest target" all the time. The solution would be co create a separate subclass for DATAStack, that would override those methods and do them in sync way.

    Reviewed by kostiakoval at 2015-03-24 06:53
  • 12. Swift version error on Objective-C base project

    Hi,

    I try to install the library via Cocoapods (in a freshly created Objective-C demo project) and I got the error below:

    [!] Unable to determine Swift version for the following pods:

    • DATAStack does not specify a Swift version and none of the targets (DNP) integrating it have the SWIFT_VERSION attribute set. Please contact the author or set the SWIFT_VERSION attribute in at least one of the targets that integrate this pod.

    Unfortunately couldn't install the library.

    my Podfile line is below:

    pod 'DATAStack', '~> 6'

    Best

    Reviewed by gurhub at 2019-07-22 11:20
  • 13. Can't execute any requests

    My data model is being created, but when I try to write to it I get a nilError, this happens with a background context and with a main thread context, is there anything specific I should be looking into? I just copied the example code

    Delete requests produce nilError, write requests fail silently

    Reviewed by SolorzanoJose at 2019-06-26 16:50
  • 14. Datastack backup is empty

    Hi,

    I am using a DataStack created as follows: self.dataStack = DATAStack(modelName: "money", isExcludedFromBackup: false)

    This should then be included in iTunes/iCloud backups. I wanted to check this, so I downloaded iPhone Backup Extractor from here (https://www.iphonebackupextractor.com/blog/ios-data-recovery-apps-data-iphone-backups/), made a new iTunes backup and then checked the backup using this app. Although I can see the SQLite file (under com.myapp.title/Library/Application Support/myapp.sqlite), if I extract it and then open the SQLite file using a SQLite viewer, it doesn't contain any actual data from my app. It does contain the full database structure though.

    Do you have any ideas what might be causing the data to be missing from the backup?

    Thanks

    Reviewed by zanderxyz at 2019-04-07 07:32
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

Jun 17, 2022
The Big Nerd Ranch Core Data Stack
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

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

Jun 8, 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 (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

Feb 7, 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
QueryKit, a simple type-safe Core Data query language.
QueryKit, a simple type-safe Core Data query language.

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

Jun 16, 2022
The images are fetched by batch of 100 images per searche

Google Images Google Images API The images are fetched by batch of 100 images pe

Apr 4, 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

Jan 29, 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

May 23, 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

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

Jun 25, 2022
HitList is a Swift App shows the implementation of Core Data.
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

Dec 17, 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
This project server as a demo for anyone who wishes to learn Core Data in Swift.

CoreDataDemo This project server as a demo for anyone who wishes to learn Core Data in Swift. The purpose of this project is to help someone new to Co

May 3, 2022
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
JustPersist is the easiest and safest way to do persistence on iOS with Core Data support out of the box.
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

Mar 13, 2022
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

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

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

Jun 21, 2022