{"id":955,"url":"https://github.com/jmfieldman/cadmium","last_synced_at":"2025-12-25T03:44:46.940Z","repository":{"id":56905541,"uuid":"53806593","full_name":"jmfieldman/Cadmium","owner":"jmfieldman","description":"A Swift framework that wraps CoreData, hides context complexity, and helps facilitate best practices.","archived":false,"fork":false,"pushed_at":"2017-10-27T12:54:02.000Z","size":1287,"stargazers_count":122,"open_issues_count":0,"forks_count":5,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-04-24T09:04:55.084Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jmfieldman.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-03-13T20:40:11.000Z","updated_at":"2024-04-23T14:37:15.000Z","dependencies_parsed_at":"2022-08-21T02:20:50.765Z","dependency_job_id":null,"html_url":"https://github.com/jmfieldman/Cadmium","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FCadmium","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FCadmium/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FCadmium/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FCadmium/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jmfieldman","download_url":"https://codeload.github.com/jmfieldman/Cadmium/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228179080,"owners_count":17881134,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-01-05T20:15:35.497Z","updated_at":"2025-12-25T03:44:46.932Z","avatar_url":"https://github.com/jmfieldman.png","language":"Swift","funding_links":[],"categories":["Core Data","Libs"],"sub_categories":["Linter","Data Management","Other free courses"],"readme":"![Cadmium](/Assets/Banner.png)\n\nCadmium is a Core Data framework for Swift that enforces best practices and raises exceptions for common Core Data pitfalls exactly where you make them.\n\nCadmium was written as a reaction to the complexity of dealing with multiple managed object contexts for standard database-like use cases. It is still important to understand what a managed object context is and how they are used, but for typical CRUD-style usage of Core Data it is a complete nuisance.\n\nWith Cadmium, the user never sees a ```NSManagedObjectContext``` or derived class. You interact only with argument-less transactions, and object fetch/manipulation tasks. The contexts are managed in the background, which makes Core Data feel more like Realm.\n\n\n# Design Goals\n\n* Create a minimalist/concise framework API that provides for most Core Data use cases and guides the user towards best practices.\n* Aggressively protect the user from performing common Core Data pitfalls, and raise exceptions immediately on the offending statement rather than waiting for a context save event.\n\n---\n\nHere's an example of a Cadmium transaction that gives all of your employee objects a raise:\n\n```swift\nCd.transact {\n    try! Cd.objects(Employee.self).fetch().forEach {\n        $0.salary += 10000\n    }\n}\n```\n\nYou might notice a few things:\n\n* Transaction usage is dead-simple.  You do not declare any parameters for use inside the block.\n* You never have to reference the managed object context, we manage it for you.\n* The changes are committed automatically upon completion (you can disable this.)\n\n### What Cadmium is Not\n\nCadmium is not designed to be a 100% complete wrapper around Core Data.  Some of the much more\nadvanced Core Data features are hidden behind the Cadmium API.  If you are creating an enterprise-level\napplication that requires meticulous manipulation of Core Data stores and contexts to optimize heavy lifting, then\nCadmium is not for you.\n\nCadmium is for you if want a smart wrapper that vastly simplifies most Core Data tasks and warns you\nimmediately when you inadvertently manipulate data in a way you shouldn't.\n\n# Installing\n\nYou can install Cadmium by adding it to your [CocoaPods](http://cocoapods.org/) ```Podfile```:\n\n```ruby\npod 'Cadmium'\n```\n\nOr you can use a variety of ways to include the ```Cadmium.framework``` file from this project into your own.\n\n### Swift Version Support\n\n\u003e Swift 3.1: Use Cadmium 1.1\n\n\u003e Swift 3.0: Use Cadmium 1.0\n\n\u003e Swift 2.3: Use Cadmium 0.13.x\n\n\u003e Swift 2.2: Use Cadmium 0.12.x\n\nCocoapods:\n\n```ruby\npod 'Cadmium', '~\u003e 1.2'  # Swift 4.0\npod 'Cadmium', '~\u003e 1.1'  # Swift 3.1 \npod 'Cadmium', '~\u003e 1.0'  # Swift 3.0\npod 'Cadmium', '~\u003e 0.13' # Swift 2.3\npod 'Cadmium', '~\u003e 0.12' # Swift 2.2\n```\n\n\n# How to Use\n\n### Context Architecture\n\nCadmium uses the same basic context architecture as CoreStore, with a root save context running on a private queue that\nhas one read-only child context on the main queue and any number of writeable child contexts running on background queues.\n\n![Cadmium Core Data Architecture](/Assets/core_data_arch.png)\n\nThis means that your main thread will never bog down on write transactions, and will only be used to merge changes (in memory)\nand updating any UI elements dependent on your data.\n\nIt also means that you cannot initiate modifications to managed objects on the main thread!  All of your write operations\nmust exist inside transactions that occur in background threads.  You will need to design your app to support the idea\nof asynchronous write operations, which is what you *should* be doing when it comes to database modification.\n\n### Managed Object Model\n\nThe creation and use of the managed object model is very similar to typical Core Data flow.  Create your managed object model as\nusual, and generate the corresponding ```NSManagedObject``` classes.  Then, simply change the hierarchy so that your class\nimplementations derive from ```CdManagedObject``` instead of ```NSManagedObject```.\n\n```CdManagedObject``` is a child class of ```NSManagedObject```.\n\n### Initialization\n\nSet up Cadmium with a single initialization call:\n\n```swift\ndo {\n    try Cd.initWithSQLStore(momdInbundleID: nil,\n                            momdName:       \"MyObjectModel.momd\",\n                            sqliteFilename: \"MyDB.sqlite\",\n                            options:        nil /* Optional */)\n} catch let error {\n    print(\"\\(error)\")\n}\n```\n\nThis loads the object model, sets up the persistent store coordinator, and initializes important contexts.\n\nIf your object model is in a framework (not your main bundle), you'll have to pass the framework's bundle identifier to the first argument.\n\nThe ```options```  argument flows through to the options passed in addPersistentStoreWithType: on the NSPersistentStoreCoordinator.\n\nYou can pass nil to the sqliteFilename parameter to create an NSInMemoryStoreType database.\n\n### Querying\n\nCadmium offers a chained query mechanism.  This can be used to query objects from the main thread (for read-only purposes), or from inside a transaction.\n\nQuerying starts with ```Cd.objects(..)``` and looks like this:\n\n```swift\ndo {\n    for employee in try Cd.objects(Employee.self)\n                          .filter(\"name = %@\", someName)\n                          .sorted(\"salary\", ascending: true)\n                          // See CdFetchRequest for more functions\n                          .fetch() {\n        /* Do something */\n        print(\"Employee name: \\(employee.name)\")\n    }\n} catch let error {\n    print(\"\\(error)\")\n}\n```\n\nYou begin by passing the managed object type into the parameter for ```Cd.objects(..)```.  This constructs a ```CdFetchRequest``` for managed objects of that type.\n\nChain in as many filter/sort/modification calls as you want, and finalize with ```fetch()``` or ```fetchOne()```.  ```fetch()``` returns an array of objects, and ```fetchOne()``` returns a single optional object (```nil``` if none were found matching the filter).\n\n### Transactions\n\nYou can only initiate changes to your data from inside of a transaction.  You can initiate a transaction using either:\n\n```swift\nCd.transact {\n    //...\n}\n```\n\n```swift\nCd.transactAndWait {\n    //...\n}\n```\n\n```Cd.transact``` performs the transaction asynchronously (the calling thread continues while the work in the transaction is performed).   ```Cd.transactAndWait``` performs the transaction synchronously (it will block the calling thread until the transaction is complete.)\n\nTo ensure best practices and avoid potential deadlocks, you are not allowed to call ```Cd.transactAndWait``` from the main thread (this will raise an exception.)\n\n### Implicit Transaction Commit\n\nWhen a transaction completes, the transaction context automatically commits any changes you made to the data store.  For most transactions this means you do not need to call any additional commit/save command.\n\nIf you want to turn off the implicit commit for a transaction (e.g. to perform a rollback and ignore any changes made), you can call ```Cd.cancelImplicitCommit()``` from inside the transaction.  A typical use case would look like:\n\n```swift\nCd.transact {\n\n    modifyThings()\n\n    if someErrorOccurred {\n        Cd.cancelImplicitCommit()\n        return\n    }\n\n    moreActions()\n}\n```\n\nYou can also force a commit mid-transaction by calling ```Cd.commit()```.  You may want to do this during long transactions when you want to save changes before possibly returning with a cancelled implicit commit.  A use case might look like:\n\n```swift\nCd.transact {\n\n    modifyThingsStepOne()\n    Cd.commit() //changes in modifyThingsStepOne() cannot be rolled back!\n\n    modifyThingsStepTwo()\n\n    if someErrorOccurred {\n        Cd.cancelImplicitCommit()\n        return\n    }\n\n    moreActions()\n}\n```\n\n### Forced Serial Transactions\n\n**NOTE: Advanced Feature**\n\nCore Data, and Cadmium, are asynchronous APIs by nature.  You generally initiate fetches and modify data asynchronously from the main thread.  This\ntight coupling with asynchronous behavior may be detrimental if you find that the context modifications you perform often conflict with each other.\n\nFor example, take the following transaction that might occur when the user taps a button to visit a place:\n\n```swift\nCd.transact {\n    if let place = try! Cd.objects(Place.self).filter(\"id = %@\", myID).fetchOne() {\n        place.visits += 1\n    }\n}\n```\n\nWhat if the user spams the visit button?  Because the transactions occur in separate context queues, it's not 100% guaranteed that ```place.visits```\nwill increment serially.  There is a remote possibility that a race condition will cause two of these contexts to see ```place.visits``` as the same\nvalue before incrementing.\n\nTo help resolve this problem and ensure that transactions are executed serially, you may pass an optional ```serial``` parameter to your\ntransactions:\n\n```swift\nCd.transact(serial: true) {\n    if let place = try! Cd.objects(Place.self).filter(\"id = %@\", myID).fetchOne() {\n        place.visits += 1\n    }\n}\n```\n\nThis guarantees that your transactions will occur serially (even waiting for the finalized context save) before proceeding to the next one -- thus\nyour transactions can be considered atomic.   \n\nNote that this atomic behavior is limited to the top-most transaction.  Transactions-inside-transactions are not executed on the serial queue to\nprevent deadlocks.\n\n```swift\n/* In this odd case, the inside transactAndWait will ignore the serial parameter, since it is already\n   inside a transaction.  The prevents deadlocks waiting on the serial queue. */\n\nCd.transact(serial: true) {\n    Cd.transactAndWait(serial: true) {\n    }\n}\n```\n\nIt is annoying to pass the serial parameter every time you want this behavior, especially if you want in *most of the time*.  If you want your\ntransactions to be serial by default, pass ```true``` to the ```serialTX``` parameter in the Cd.init function:\n\n```swift\ntry Cd.initWithSQLStore(momdInbundleID: \"org.fieldman.CadmiumTests\",\n                        momdName:       \"CadmiumTestModel\",\n                        sqliteFilename: \"test.sqlite\",\n                        serialTX:       true)\n```\n\nWhen you pass ```true``` to the init, you can override this per-transaction by passing ```false``` to the ```serial``` parameter:\n\n```swift\nCd.transact(serial: false) {\n    ...\n}\n```\n\nNot specifying the ```serial``` parameter, or passing ```nil```, will use the default determined during initialization.\n\nMost use cases will be fine setting the default serial usage to true and leaving it at that. You will incur a performance hit with\nserial transactions in the cases that you attempt to execute lots of concurrent\nor long-running transactions on unrelated objects (since these will all execute serially instead of in parallel).\n\nAs an advanced workaround to this issue, if you have many concurrent long-running transactions on different subsets of your\ndata store, you can pass your own dispatch queue to the ```on``` parameter:\n\n```swift\nCd.transact(on: mySerialDispatchQueue) {\n    ...\n}\n```\n\nThe transaction block itself will occur in the context's queue -- but this occurs synchronously in the dispatch queue you\nprovide.  You can provide different dispatch queues for transactions that affect different objects.\n\nNote that the dispatch queue you provide will target the internal default serial queue, which keeps the save context\nsynchronized no matter which queues you use.  It also means that any transactions you run on the default serial queue\nwill block all other queues you may pass in (so avoid running very long transactions on the default serial queue).\n\n### Creating and Deleting Objects\n\nObjects can be created and deleted inside transactions.\n\n```swift\nCd.transact {\n    let newEmployee    = try! Cd.create(Employee.self)\n    newEmployee.name   = \"Bob\"\n    newEmployee.salary = 10000\n}\n\nCd.transact {\n    Cd.delete(try! Cd.objects(Employee.self).filter(\"name = %@\", \"Bob\").fetch())\n}\n```                  \n\nYou can also delete objects directly from a CdFetchRequest:\n\n```swift\nCd.objects(Employee.self).filter(\"salary \u003e 100000\").delete()\n```\n\nIf called:\n\n* Outside a transaction: will delete the objects asynchronously in a background transaction.\n* Inside a transaction: will perform the delete synchronously inside the transaction.\n\n\n### Modifying Objects from Other Contexts\n\nYou will often need to modify a managed object from one context inside of another context.  The most\ncommon use case is when you want to modify objects you've queried from the main thread (which are read-only).\n\nYou can use ```Cd.useInCurrentContext``` to get a copy of the object that is suitable for\nmodification in the current context:\n\n```swift\n/* Acquire a read-only employee object somewhere on the main thread */\nguard let employee = try! Cd.objects(Employee.self).fetchOne() else {\n    return\n}\n\n/* Modify it in a transaction */\nCd.transact {\n    guard let txEmployee = Cd.useInCurrentContext(employee) else {\n        return\n    }\n\n    txEmployee.salary += 10000    \n}\n```\n\nNote that an object must have been inserted and committed in a transaction before it can be accessed from another context.\nIf a transient object has not been inserted yet, it will not be available with this method.\n\nIf you are only using one object from another context, consider ```Cd.transactWith``` instead:\n\n```swift\n/* Acquire a read-only employee object somewhere on the main thread */\nguard let employee = try! Cd.objects(Employee.self).fetchOne() else {\n    return\n}\n\n/* Modify it in a transaction */\nCd.transactWith(employee) { txEmployee in\n    if let txEmployee = txEmployee {\n        txEmployee.salary += 10000\n    }\n}\n```\n\nYou can also pass an array into ```Cd.transactWith``` to get an array of objects in the new context.\n\n### Notifying the Main Thread\n\nBecause transactions occur on the transaction context's private queue, calls to ```Cd.commit()``` are synchronous and only\nreturn after the save has propagated to the persistent store.\n\nYou can use this fact to notify the main thread that a commit has completed in your transaction:\n\n```swift\nCd.transact {\n\n    modifyThings()\n    Cd.commit()\n\n    /* only called after the commit saves up to the persistent store */\n    DispatchQueue.main.async {\n        notifyOthers()\n    }    \n}\n```\n\nYou must also call ```Cd.commit()``` if you want to dispatch objects created in a transaction back to the main thread, since\ncalling ```Cd.commit()``` will save created objects to the persistent store and give them permanent IDs.\n\n```swift\nCd.transact {\n    let newItem = try! Cd.create(ExampleItem.self)\n    newItem.name = \"Test\"\n\n    /* Synchronously saves newItem to the persistent store */\n    try! Cd.commit()\n\n    Cd.onMainWith(newItem) { mainItem in\n        guard let item = mainItem else {\n            return\n        }\n\n        print(\"created item in transaction: \\(item.name)\")        \n    }\n}\n```\n\n### Fetched Results Controller\n\nFor typical uses of ```NSFetchedResultsController```, you should use the built-in subclass ```CdFetchedResultsController```.  This\nsubclass wraps the normal functionality of ```NSFetchedResultsController``` onto the protected main queue context.\n\nYou can use the ```CdFetchedResultsController``` as you would a ```NSFetchedResultsController``` with the following in mind:\n\n* The objects in the fetch results exist in the main thread read-only context and cannot be modified.  Use ```Cd.useInCurrentContext```\nto modify them in a transaction.\n* You can pass a ```UITableView``` into the ```automateDelegation``` method to perform the standard insert/delete commands on sections and\nrows when your fetched results controller has changes.  This can help save a few lines in your own view controllers.\n\n### Using the Update Handler\n\nEvery instance of ```CdManagedObject``` has a property called ```updateHandler``` that can store a block to be called when it\nis updated.  You may only attach a block to ```updateHandler``` on objects belonging to the main thread context.  This can be\nuseful in situations where you want to monitor objects without using an ```NSFetchedResultsController```.\n\nAn example might look like:\n\n```swift\n/* ... from the example above, transferring a new item to the main thread: */\nDispatchQueue.main.async {\n    if let mainItem = Cd.useInCurrentContext(newItem), name = mainItem.name {\n        print(\"created item in transaction: \\(name)\")\n        mainItem.updateHandler = { event in\n            print(\"event occurred on object \\(name): \\(event)\")\n        }\n    }\n}\n```\n\nBe aware that you can only install one ```updateHandler``` per instance.  If you need a solution that requires dispatching to\nmore listeners, you can use the handler to post a ```NSNotification```, or use another toolkit like ReactiveCocoa (see the\nfile ```Cadmium+ReactiveCocoa.swift``` in the Examples directory.)\n\n\n### Aggressively Identifying Coding Pitfalls\n\nMost developers who use Core Data have gone through the same gauntlet of discovering the various pitfalls and complications of creating a multi-threaded Core Data application.  \n\nEven seasoned veterans are still susceptible to the occasional ```1570: The operation couldn’t be completed``` or ```13300: NSManagedObjectReferentialIntegrityError```\n\nMany of the common issues arise because the standard Core Data framework is lenient about allowing code that does the Wrong Thing and only throwing an error on the eventual attempt to save (which may not be proximal to the offending code.)\n\nCadmium performs aggressive checking on managed object operations to make sure you are coding correctly, and will raise exceptions on the offending lines rather than waiting for a save to occur.\n\n# PromiseKit Extension\n\nYou can enable the Cadmium PromiseKit extension by adding\n\n```\npod 'Cadmium/PromiseKit'\n```\n\nTo your ```Podfile```.  This will enable the following functionality:\n\n### Transaction Promises\n\nWith the PromiseKit extension, ```Cd.transact``` and ```Cd.transactWith``` are now given promise-return overrides.  In most\ncases the compiler should be able to deduce these as long as you treat the return of the transaction like a promise:\n\n```swift\nCd.transact {\n    //...\n}.then { _ -\u003e Void in\n\n}\n\nCd.transactWith(obj) { txObj in\n    //...\n}.then { _ -\u003e Void in\n\n}\n```\n\nThe implementation is such that the transaction changes are committed before the promise is fulfilled, so the\ntransaction promise is fully atomic from the perspective of the promise chain.\n\nNote that the compiler may need you to be somewhat explicit about the promise chain.  If you see odd errors, try\nexplicitly defining the promise signatures instead of letting the compiler try to infer them (e.g. how the example\nabove adds ```_ -\u003e Void in``` instead of leaving it empty).\n\n### Transactions Inside the Chain\n\nIf you would like to use a transaction inside the promise chain, some more options are available to you:\n\n```swift\nfirstly {\n    somePromise()\n}.thenTransact(serial: ..., on: ...) { // Optionally use serial: and on: arguments\n    // This block occurs inside a Cadmium transaction\n    // Commit is called before the promise is fulfilled.\n}.then {\n\n}\n\n// Or use the transactWith variant when the previous\n// promise is fulfilled with a CdManagedObject\n\nfirstly {\n    return employeeVarFromMainThread // \u003c- CdManagedObject\n}.thenTransactWith(serial: ..., on: ...) { (employee: Employee) -\u003e Void in\n    // This block occurs inside a Cadmium transaction with a\n    // version of the argument belonging to the current context.\n}.then {\n\n}\n```\n\nThere are times when you need to funnel a ```CdManagedObject``` from a transaction\nback to the main thread.  For this, use ```thenOnMainWith```:\n\n```swift\nfirstly {\n    return employeeVarFromMainThread\n}.thenTransactWith(serial: ..., on: ...) { (employee: Employee) -\u003e Employee in\n    employee.salary += 10000\n    return employee\n}.thenOnMainWith { (employee: Employee) -\u003e Void\n    // Here, employee is a read-only CdManagedObject from the main thread context.\n    print(\"The salary is \\(employee.salary)\")\n}\n\n```\n\nIt should be noted that promise block for ```thenTransactWith``` can receive an optional in the pipe, but does not pass an optional as its block argument.\nIt is considered a promise error if the fulfillment value to ```thenTransactWith``` is ```nil```,\nor if the internal ```Cd.useInCurrentContext``` returns a ```nil``` value.  In these cases the\nchain will be rejected with ```CdPromiseError.NotAvailableInCurrentContext(value)```.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmfieldman%2Fcadmium","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjmfieldman%2Fcadmium","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmfieldman%2Fcadmium/lists"}