{"id":13489200,"url":"https://github.com/terhechte/CoreValue","last_synced_at":"2025-03-28T04:30:56.781Z","repository":{"id":34631489,"uuid":"38581740","full_name":"terhechte/CoreValue","owner":"terhechte","description":"Lightweight Framework for using Core Data with Value Types","archived":false,"fork":false,"pushed_at":"2020-11-03T08:26:17.000Z","size":746,"stargazers_count":456,"open_issues_count":4,"forks_count":26,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-04-25T07:42:13.344Z","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/terhechte.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"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":"2015-07-05T19:50:49.000Z","updated_at":"2023-12-15T11:50:50.000Z","dependencies_parsed_at":"2022-07-28T18:19:49.001Z","dependency_job_id":null,"html_url":"https://github.com/terhechte/CoreValue","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terhechte%2FCoreValue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terhechte%2FCoreValue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terhechte%2FCoreValue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/terhechte%2FCoreValue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/terhechte","download_url":"https://codeload.github.com/terhechte/CoreValue/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245970354,"owners_count":20702395,"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-07-31T19:00:19.873Z","updated_at":"2025-03-28T04:30:56.282Z","avatar_url":"https://github.com/terhechte.png","language":"Swift","funding_links":[],"categories":["Libs","Swift"],"sub_categories":["Data Management"],"readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"Documents/header.png\" srcset=\"Documents/header.png 1x Documents/header@2x.png 2x\" /\u003e\u003cbr/\u003e\n\u003ca href=\"https://swift.org\"\u003e\u003cimg src=\"https://img.shields.io/badge/Swift-5-orange.svg\" /\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/Carthage/Carthage\"\u003e\u003cimg src=\"https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat\" /\u003e\u003c/a\u003e\n\u003ca href=\"https://cocoapods.org\"\u003e\u003cimg src=\"https://img.shields.io/cocoapods/v/CoreValue.svg\" /\u003e\u003c/a\u003e\n\u003cimg src=\"https://img.shields.io/cocoapods/p/CoreValue.svg\" /\u003e\n\u003ca href=\"https://travis-ci.org/terhechte/CoreValue\"\u003e\u003cimg src=\"https://travis-ci.org/terhechte/CoreValue.svg?branch=master\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\n\n## Features\n\n- Uses Swift Reflection to convert value types to NSManagedObjects\n- iOS and Mac OS X support\n- Use with `structs`\n- Works fine with `let` and `var` based properties\n- Swift 5.0 \n\n## Rationale\n\nSwift introduced versatile value types into the iOS and Cocoa development domains. They're lightweight, fast, safe, enforce immutability and much more. However, as soon as the need for CoreData in a project manifests itself, we have to go back to reference types and `@objc`. \n\nCoreValue is a lightweight wrapper framework around Core Data. It takes care of `boxing` value types into Core Data objects and `unboxing` Core Data objects into value types. It also contains simple abstractions for easy querying, updating, saving, and deleting.\n\n## Usage\n\nThe following struct supports boxing, unboxing, and keeping object state:\n\n```swift\nstruct Shop: CVManagedPersistentStruct {\n\n    // The name of the CoreData entity\n    static let EntityName = \"Shop\"\n\n    // The ObjectID of the CoreData object we saved to or loaded from\n    var objectID: NSManagedObjectID?\n    \n    // Our properties\n    let name: String\n    var age: Int32\n    var owner: Owner?\n\n    // Create a Value Type from an NSManagedObject\n    // If this looks too complex, see below for an explanation and alternatives\n    static func fromObject(_ o: NSManagedObject) throws -\u003e XShop {\n        return try curry(self.init)\n            \u003c^\u003e o \u003c|? \"objectID\"\n            \u003c^\u003e o \u003c| \"name\"\n            \u003c^\u003e o \u003c| \"age\"\n            \u003c^\u003e o \u003c|? \"owner\"\n    }\n}\n```\n\n\nThat's it. Everything else it automated from here. Here're some examples of what you can do with `Shop` then:\n\n\n```swift\n// Get all shops (`[Shop]` is required for the type checker to get your intent!)\nlet shops: [Shop] = Shop.query(self.context, predicate: nil)\n\n// Create a shop\nlet aShop = Shop(objectID: nil, name: \"Household Wares\", age: 30, owner: nil)\n\n// Store it as a managed object\naShop.save(self.context)\n\n// Change the age\naShop.age = 40\n\n// Update the managed object in the store\naShop.save(self.context)\n\n// Delete the object\naShop.delete(self.context)\n\n// Convert a managed object into a shop (see below)\nlet nsShop: Shop? = try? Shop.fromObject(aNSManagedObject)\n\n// Convert a shop into an nsmanagedobject\nlet shopObj = nsShop.mutatingToObject(self.context)\n```\n\n## Querying\n\nThere're two ways of querying objects from Core Data into values:\n\n```swift\n// With Sort Descriptors\npublic static func query(context: NSManagedObjectContext, predicate: NSPredicate?, sortDescriptors: [NSSortDescriptor]) -\u003e Array\n    \n// Without sort descriptors\npublic static func query(context: NSManagedObjectContext, predicate: NSPredicate?) -\u003e Array\n```\n\nIf no `NSPredicate` is given, all objects for the selected Entity are returned.\n\n## Usage in Detail\n\n`CVManagedPersistentStruct` is a `typealias` for the two primary protocols of CoreValue: `BoxingPersistentStruct` and `UnboxingStruct`.\n\nLet's see what they do.\n\n### BoxingPersistentStruct\n\nBoxing is the process of taking a value type and returning an `NSManagedObject`. CoreValue really loves you and that's why it does all the hard work for you via Swift's `Reflection` feature. See for yourself:\n\n```swift\nstruct Counter: BoxingStruct\n    static let EntityName = \"Counter\"\n    var count: Int\n    let name: String\n}\n```\n\nThat's it. Your value type is now CoreData compliant. Just call `aCounter.toObject(context)` and you'll get a properly encoded `NSManagedObject`!\n\nIf you're interested, have a look at the `internalToObject` function in [CoreValue.swift](/CoreValue/CoreValue.swift), which takes care of this.\n\n#### Boxing in Detail\n\nKeen observers will have noted that the structure above actually doesn't implement the `BoxingPersistentStruct` protocol, but instead something different called `BoxingStruct`, what's happening here?\n\nBy default, Value types are immutable, so even if you define a property as a var, you still can't change it from within except by declaring your function mutable. Swift also doesn't allow us to define properties in protocol extensions, so any state that we wish to assign on a value type has to be via specific properties on the value type.\n\nWhen we create or load an `NSManagedObject` from CoreData, we need a way to store the connection to the original `NSManagedObject` in the value type. Otherwise, calling `save` again (say after updating the value type) would not update the `NSManagedObject` in question, but instead *insert a new `NSManagedObject`* into the store. That's obviously not what we want.\n\nSince we cannot implicitly add any state whatsoever to a protocol, we have to do this explicitly. That's why there's a separate protocol for persistent storage:\n\n``` Swift\nstruct Counter: BoxingPersistentStruct\n    let EntityName = \"Counter\"\n\n    var objectID: NSManagedObjectID?\n\n    var count: Int\n    let name: String\n}\n```\n\nThe main difference here is the addition of `objectID`. Once this property is there, `BoxingPersistentStruct`'s bag of wonders (`.save`, `.delete`, `.mutatingToObject`) can be used.\n\nWhat's the usecase of the `BoxingStruct` protocol then, you may ask. The advantage is that `BoxingStruct` does not require your value type to be mutable, and does not extend it with any mutable functions by default, keeping it a truly immutable value type. It still can use `.toObject` to convert a value type into an `NSManagedObject`, however it can't modify this object afterwards. So it is still useful for all scenarios where you're only performing insertions (like a cache, or a log) or where any modifications are performed in bulk (delete all), or where updating will be performed on the `NSManagedObject` itself (`.valueForKey`, `.save`).\n\n#### Boxing and Sub Properties\n\nA word of advice: If you have value types in your value types, like:\n\n```swift\nstruct Employee: BoxingPersistentStruct {\n    let EntityName = \"Employee\"\n    var objectID: NSManagedObjectID?\n    let name: String\n}\n\nstruct Shop: BoxingPersistentStruct {\n    let EntityName = \"Counter\"\n    var objectID: NSManagedObjectID?\n    let employees: [Employee]\n}\n```\n\nThen ***you have to make sure*** that all value types conform to the same boxing protocol, either `BoxingPersistentStruct` or `BoxingStruct`. The type checker cannot check this and report this as an error.\n\n#### Ephemeral Objects\n\nMost protocols in CoreValue mark the `NSManagedObjectContext` as an optional, which means that you don't have to supply it. Boxing will still work as expected, only the resulting `NSManagedObject`s will be ephemeral, that is, they're not bound to a context, they can't be stored. There're few use cases for this, but it is important to note that not supplying a `NSManagedObjectContext` will not result in an error.\n\n\n### UnboxingStruct\n\nIn CoreValue, `boxed` refers to values in an `NSManagedObject` container. I.e. `NSNumber` is boxing an `Int`, `NSOrderedSet` an `Array`, and `NSManagedObject` itself is boxing a value type (i.e. `Shop`).\n\n`UnboxingStruct` can be applied to any struct or class that you intend to initialize from a `NSManagedObject`. It only has one requirement that needs to be implemented, and that's `fromObject` which takes an `NSManagedObject` and should return a value type. Here's a very simple and unsafe example:\n\n```swift\nstruct Counter: UnboxingStruct\n    var count: Int\n    let name: String\n    static func fromObject(_ object: NSManagedObject) throws -\u003e Counter {\n        return Counter(\n\t    count: object.valueForKey(\"count\")!.integerValue,\n\t    name: object.valueForKey(\"name\") as! String\n\t)\n    }\n}\n```\n\nEven though this example is not safe, we can observe several things from it. First, the implementation overhead is minimal. Second, the method can throw an error. That's because unboxing can fail in a multitude of ways (wrong value, no value, wrong entity, unknown entity, etc). If unboxing fails in any way, we throw an `NSError`. The other benefit of unboxing, that it allows us to take a shortcut (which CoreValue deviously copied from [Argo](https://github.com/thoughtbot/Argo)). Utilizing several custom operators, the unboxing process can be greatly simplified:\n\n```swift\nstruct Counter: UnboxingStruct\n    var count: Int\n    let name: String\n    static func fromObject(_ object: NSManagedObject) throws -\u003e Counter {\n        return try curry(self.init) \u003c^\u003e object \u003c| \"count\" \u003c^\u003e object \u003c| \"name\"\n    }\n}\n```\n\nThis code takes the automatic initializer, curries it and maps it over multiple incarnations of unboxing functions (`\u003c|`) until it can return a Counter (or throw an error).\n\nBut what about these weird runes? Here's an in-detail overview of what's happening here:\n\n#### Unboxing in Detail\n\n`curry(self.init)`\n\nConvert `(A, B) -\u003e T` into `A -\u003e B -\u003e C` so that it can be called step by step\n\n`\u003c^\u003e`\nMap the following operations over the `A -\u003e B -\u003e fn` that we just created\n\n`object \u003c| \"count\"`\nFirst operation: Take `object`, call `valueForKey` with the key `\"count\"` and assign this as the value for the first type of the curryed init function `A`\n\n`object \u003c| \"name\"`\nSecond operation: Take `object`, call `valueForKey` with the key `\"count\"` and assign this as the value for the second type of the curryed init function `B`\n\n#### Other Operators\n\nCustom Operators are observed as a critical Swift feature, and rightly so. Too many of those make a codebase difficult to read and understand. The following custom operators are the same as in several other Swift Frameworks (see Runes and Argo). They're basically a verbatim copy from Haskell, so while that doesn't make them less custom or even official, they're at least unofficially agreed upon.\n\n`\u003c|` is not the only operator needed to encode objects. Here's a list of all supported operators:\n\n| Operator | Description |\n|-|-|\n `\u003c^\u003e`   | Map the following operations (i.e. combine `map` operations)\n `\u003c\\|`   | Unbox a normal value (i.e. `var shop: Shop`)\n `\u003c\\|\\|` | Unbox a set/list of values (i.e. `var shops: [Shops]`)\n `\u003c\\|?`  | Unbox an optional value (i.e. `var shop: Shop?`)\n\n### CVManagedStruct\n\nSince most of the time you probably want boxing and unboxing functionality, CoreValue includes two handy typealiases, `CVManagedStruct` and `CVManagedPersistentStruct` which contain Boxing and Unboxing in one type.\n\n### `RawRepresentable` Enum support\n\nBy extending `RawRepresentable`, you can use Swift `enums` right away without having to first make sure your enum conforms to `CVManagedStruct`.\n\n```swift\nenum CarType: String {\n    case pickup\n    case sedan\n    case hatchback\n}\n\nextension CarType: Boxing, Unboxing {}\n\nstruct Car: CVManagedPersistentStruct {\n    static let EntityName = \"Car\"\n    var objectID: NSManagedObjectID?\n    var name: String\n    var type: CarType\n\n    static func fromObject(_ o: NSManagedObject) throws -\u003e Car {\n        return try curry(self.init)\n            \u003c^\u003e o \u003c|? \"objectID\"\n            \u003c^\u003e o \u003c| \"name\"\n            \u003c^\u003e o \u003c| \"type\"\n    }\n}\n```\n\n## Docs\n\nHave a look at [CoreValue.swift](/CoreValue/CoreValue.swift), it's full of docstrings.\n\nAlternatively, there's a lot of usage in the [Unit Tests](/CoreValueMacTests/CoreValueTests.swift).\n\nHere's a  more complex example of CoreValue in use:\n\n```swift\nstruct Employee: CVManagedPersistentStruct {\n\n    static let EntityName = \"Employee\"\n\n    var objectID: NSManagedObjectID?\n\n    let name: String\n    var age: Int16\n    let position: String?\n    let department: String\n    let job: String\n\n    static func fromObject(_ o: NSManagedObject) throws -\u003e Employee {\n        return try curry(self.init)\n            \u003c^\u003e o \u003c| \"objectID\"\n            \u003c^\u003e o \u003c| \"name\"\n            \u003c^\u003e o \u003c| \"age\"\n            \u003c^\u003e o \u003c|? \"position\"\n            \u003c^\u003e o \u003c| \"department\"\n            \u003c^\u003e o \u003c| \"job\"\n    }\n}\n\nstruct Shop: CVManagedPersistentStruct {\n    static let EntityName = \"Shop\"\n    \n    var objectID: NSManagedObjectID?\n\n    var name: String\n    var age: Int16\n    var employees: [Employee]\n    \n    static func fromObject(_ o: NSManagedObject) throws -\u003e Shop {\n        return try curry(self.init)\n            \u003c^\u003e o \u003c| \"objectID\"\n            \u003c^\u003e o \u003c| \"age\"\n            \u003c^\u003e o \u003c| \"name\"\n            \u003c^\u003e o \u003c|| \"employees\"\n    }\n}\n\n// One year has passed, update the age of our shops and employees by one\nlet shops: [Shop] = Shop.query(self.managedObjectContext, predicate: nil)\nfor shop in shops {\n    shop.age += 1\n    for employee in shop.employees {\n        employee.age += 1\n    }\n    shop.save()\n}\n\n```\n\n## CVManagedUniqueStruct and REST / Serialization / JSON\n\nAll the examples we've seen so far resolve around a use case where data is contained within your app. This means that the unique identifier of an `NSManagedObject` or struct is dicated by the `NSManagedObjectID` unique identifier which CoreData generates. This is fine as long as you don't plan to interact with outside data. If your data is loaded from external sources (i.e. JSON from a Rest API) then it may already have a unique identifier. `CVManagedUniqueStruct` allows you to force CoreValue / CoreData to use this external unique identifier in `NSManagedObjectID`'s stead. The implementation is easy. You just have to conform to the `BoxingUniqueStruct` protocol which requires the implementation of a `var` naming the unique id field and a function returning the current ID value:\n\n```swift\n/// Name of the Identifier in the CoreData (e.g: 'id')\nstatic var IdentifierName: String { get }\n\n/// Value of the Identifier for the current struct (e.g: 'self.id')\nfunc IdentifierValue() -\u003e IdentifierType\n```\n\nHere's a complete \u0026 simple example:\n\n```swift\nstruct Author: CVManagedUniqueStruct {\n\n    static let EntityName = \"Author\"\n\n    static var IdentifierName: String = \"id\"\n\n    func IdentifierValue() -\u003e IdentifierType { return self.id }\n\n    let id: String\n    let name: String\n\n    static func fromObject(_ o: NSManagedObject) throws -\u003e Author {\n        return try curry(self.init)\n            \u003c^\u003e o \u003c| \"id\"\n            \u003c^\u003e o \u003c| \"name\"\n    }\n}\n```\n\nPlease not that `CVManagedUniqueStruct` adds an (roughly) O(n) overhead on top of `NSManagedObjectID` based solutions due to the way object lookup is currently implemented.\n\n## State\n\nAll CoreData Datatypes are supported, with the following **exceptions**:\n- Transformable\n- Unordered Collections / `NSSet` (Currently, only ordered collections are supported)\n\nFetched properties are not supported yet.\n\n## Installation (iOS and macOS)\n\n### [CocoaPods]\n\n[CocoaPods]: http://cocoapods.org\n\nAdd the following to your [Podfile](http://guides.cocoapods.org/using/the-podfile.html):\n\n```ruby\npod 'CoreValue'\n```\n\nYou will also need to make sure you're opting into using frameworks:\n\n```ruby\nuse_frameworks!\n```\n\nThen run `pod install` with CocoaPods 1.01 or newer.\n\n### [Carthage]\n\n[Carthage]: https://github.com/Carthage/Carthage\n\nAdd the following to your Cartfile:\n\n```\ngithub \"terhechte/CoreValue\" ~\u003e 0.3.0\n```\n\nThen run `carthage update`.\n\nFollow the current instructions in [Carthage's README][carthage-installation]\nfor up to date installation instructions.\n\n[carthage-installation]: https://github.com/Carthage/Carthage#adding-frameworks-to-an-application\n\nThe `import CoreValue` directive is required in order to use CoreValue.\n\n\n### Manually\n\n1. Copy the `CoreValue.swift` and `curry.swift` file into your project.\n2. Add the `CoreData` framework to your project\n\nThere is no need for `import CoreValue` when manually installing.\n\n## Contact\n\nBenedikt Terhechte \n\n[@terhechte](http://www.twitter.com/terhechte)\n\n[Appventure.me](http://appventure.me)\n\n## Changelog\n\n### Version 0.4.0\n- Swift 5 Support thanks to [DaGerry](https://github.com/DaGerry)\n\n### Version 0.3.0\n- Swift 3 Support\n- Added CVManagedUniqueStruct thanks to [tkohout](https://github.com/tkohout)\n\n### Version 0.2.0\n- Switched Error Handling from `Unboxed` to Swift's native `throw`. Big thanks to [Adlai Holler](https://github.com/Adlai-Holler) for spearheading this!\n- Huge Performance improvements: Boxing is roughly 80% faster and Unboxing is roughly 90% faster\n- Improved support for nested collections thanks to [Roman Kříž](https://github.com/samnung).\n- RawRepresentable support (see documentation above) thanks to [tkohout](https://github.com/tkohout)\n\n### Version 0.1.6\n- Made `CVManagedPersistentStruct` public\n- Fixed issue with empty collections\n\n### Version 0.1.4\nIncluded pull request from AlexanderKaraberov which\nincludes a fix to the delete function\n\n### Version 0.1.3\nUpdated to most recent Swift 2.0 b4 changes\n\n### Version 0.1.2\nRenamed NSManagedStruct and NSPersistentManagedStruct to CVManagedStruct and CVPersistentManagedStruct as [NS is preserved prefix for Apple classes](https://github.com/terhechte/CoreValue/issues/1)\n\n### Version 0.1.1\nAdded CocoaPods support\n\n### Version 0.1.0\nInitial Release\n\n## Acknoledgements\n\nCoreValue uses ideas and code from [thoughtbot's Argo framework for JSON decoding](https://github.com/thoughtbot/Argo). Most notably their `curry` implementation. Have a look at it, it is an awesome framework.\n\n## License\n\nThe CoreValue source code is available under the MIT License.\n\n## Open Tasks\n\n- [ ] test unboxing with custom initializers (init(...))\n- [ ] change the protocol composition so that the required implementations (entityname, objectID, fromObject) form an otherwise empty protocol so it is easier to see the protocol and implement the requirements\n- [ ] add travis build\n- [ ] support aggregation\n- [ ] add support for nsset / unordered lists\n- [ ] add support for fetched properties (could be a struct a la (objects, predicate))\n- [ ] support transformable: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdNSAttributes.html\n- [ ] add jazzy for docs and update headers to have proper docs\n- [ ] document multi threading support via objectID\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fterhechte%2FCoreValue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fterhechte%2FCoreValue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fterhechte%2FCoreValue/lists"}