{"id":15066971,"url":"https://github.com/tomkuku/swiftdatastore","last_synced_at":"2026-01-03T06:06:09.955Z","repository":{"id":62221331,"uuid":"556383205","full_name":"tomkuku/SwiftDatastore","owner":"tomkuku","description":"Elegant and easy way to store data in Swift","archived":false,"fork":false,"pushed_at":"2023-01-20T18:01:11.000Z","size":153,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-25T06:49:17.221Z","etag":null,"topics":["core-data","coredata","coredata-wrapper","ios","spm","swift","swiftpackagre"],"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/tomkuku.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-10-23T18:23:11.000Z","updated_at":"2023-01-02T10:48:30.000Z","dependencies_parsed_at":"2023-02-12T04:15:17.785Z","dependency_job_id":null,"html_url":"https://github.com/tomkuku/SwiftDatastore","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomkuku%2FSwiftDatastore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomkuku%2FSwiftDatastore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomkuku%2FSwiftDatastore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomkuku%2FSwiftDatastore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomkuku","download_url":"https://codeload.github.com/tomkuku/SwiftDatastore/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243822310,"owners_count":20353498,"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":["core-data","coredata","coredata-wrapper","ios","spm","swift","swiftpackagre"],"created_at":"2024-09-25T01:14:38.974Z","updated_at":"2026-01-03T06:06:09.949Z","avatar_url":"https://github.com/tomkuku.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eSwiftDatastore\u003c/h1\u003e\n\u003ch3 align=\"center\"\u003eElegeant and easy way to store data in Swift\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://github.com/tomkuku/SwiftDatastore/actions\"\u003e\n\u003cimg alt=\"Build Status\" src=\"https://img.shields.io/github/actions/workflow/status/tomkuku/SwiftDatastore/build.yml?branch=main\u0026logo=github\" /\u003e\n\u003c/a\u003e\n\u003ca href=\"https://github.com/tomkuku/SwiftDatastore/commits\"\u003e\n\u003cimg alt=\"Last Commit\" src=\"https://img.shields.io/github/last-commit/tomkuku/SwiftDatastore?logo=git\" /\u003e\n\u003c/a\u003e\n\u003ca href=\"https://app.codecov.io/gh/tomkuku/SwiftDatastore\"\u003e\n\u003cimg alt=\"Code coverage\" src=\"https://codecov.io/github/tomkuku/SwiftDatastore/coverage.svg?branch=main\" /\u003e\n\u003c/a\u003e\n\u003cimg alt=\"Quality Gate\" src=\"https://sonarcloud.io/api/project_badges/measure?project=tomkuku_SwiftDatastore\u0026metric=alert_status\" /\u003e\n\u003c/a\u003e\n\u003c/p\u003e\n\n\u003c/br\u003e\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://github.com/tomkuku/SwiftDatastore/blob/main/LICENSE.md\"\u003e\n\u003cimg alt=\"License\" src=\"https://img.shields.io/github/license/tomkuku/SwiftDatastore?color=blue\" /\u003e\n\u003c/a\u003e\n\u003cimg alt=\"Swift Versions\" src=\"https://img.shields.io/badge/Swift-\u003e=_5.5-orange?logo=swift\u0026style=flat\" /\u003e\n\u003cimg alt=\"Platforms\" src=\"https://img.shields.io/badge/platforms-iOS_+13.0-yellowgreen?style=flat\u0026logo=apple\u0026name=platforms\u0026color=lightgrey\" /\u003e\n\u003c/p\u003e\n\u003c/br\u003e\n\u003cp align=\"center\"\u003e\n\u003cimg alt=\"CocoaPods\" src=\"https://img.shields.io/badge/CocoaPods-compatible-informational?logo=cocoapods\u0026style=flat\u0026color=informational\" /\u003e\n\u003cimg alt=\"SMP\" src=\"https://img.shields.io/badge/SPM-compatible-blue?logo=swift\u0026style=flat\u0026color=informational\" /\u003e\n\u003c/p\u003e\n\n\u003c/br\u003e\n\n### What is SwiftDatastore and why should I use it?\n* It is a wrapper to CoreData which adds additional layer of adstraction. It has been created to add opportunity to use `CoreData` in easy and safe way. In opposite to `CoreData`, `SwiftDatastore` is typed. It means that you can not have access to properties and entities by string keys.\n* `SwiftDatastore` has set of `PropertyWrappers` which allow you for getting and setting value without converting types manually every time. You decide what type you want, `SwiftDatastore` does the rest for you.\n* You don't need to create xcdatamodel. `SwiftDatastore` create model for you.\n\nJust try it 😊!\n\n\u003c/br\u003e\n\n### **Table of Content**:\n- [Installation](#installation)\n    - [Swift Package Manager](#swift-package-manager)\n    - [CocoaPods](#cocoapods)\n- [Create DatastoreObject](#create-datastoreobject)\n- [DatastoreObject Properties](#datastoreobject-properties)\n    - [Attributes](#attributes)\n        - [`NotOptional`](#notoptional)\n        - [`Optional`](#optional)\n        - [`Enum`](#enum)\n    - [Relationships](#relationships)\n        - [`ToOne`](#toone)\n        - [`ToMany`](#tomany)\n        - [`ToMany.Ordered`](#tomanyordered)\n- [Setup](#setup)\n- [SwiftDatastore’s operations](#swiftdatastores-operations)\n    - [`perform`](#perform)\n    - [`createObject`](#createobject)\n    - [`deleteObject`](#deleteobject)\n    - [`saveChanges`](#savechanges)\n    - [`reverChanges`](#revertchanges)\n    - [`fetch`](#fetch)\n    - [`fetchFirst`](#fetchfirst)\n    - [`fetchProperties`](#fetchproperties)\n    - [`count`](#count)\n    - [`convert existing object`](#convert-existing-object)\n    - [`deleteMany`](#deletemany)\n    - [`updateMany`](#updatemany)\n    - [`refresh`](#refresh)\n- [Using ViewContext](#using-viewcontext)\n- [Using FetchedObjectsController](#using-fetchedobjectscontroller)\n    - [`numberOfSections`](#numberofsections)\n    - [`numberOfObjects`](#numberofobjects)\n    - [`getObject`](#getobject)\n    - [`sectionName`](#sectionname)\n    - [`observeChanges`](#observechanges)\n- [`OrderBy`](#orderby)\n- [`Where`](#where)\n- [Observing DatastoreObject Properties](#observing-datastoreobject-properties)\n- [Testing](#Tesing)\n\n\u003c/br\u003e\n\u003c/br\u003e\n\n## Installation\n### **[Swift Package Manager](https://www.swift.org/package-manager/)**\n1. In your **Xcode's project** in navigation bar choose **File -\u003e Add Packages...**\n2. Pase https://github.com/tomkuku/SwiftDatastore.git in search field.\n3. As **Dependency Rule** choose `Branch` and type `main`.\n4. Click `Add Package`.\n5. Choose `SwiftDatastore` **Package Product** in the target which you want to use it.\n6. Click `Add Package`.\n\n### **[CocoaPods](http://cocoapods.org)**\nIn `Podfile` in the target in which you want to use  SwiftDatastore add:\n``` ruby\npod 'SwiftDatastore',\n```\n\nand then in **Terminal** run:\n``` ruby\npod install\n```\n\n\u003c/br\u003e\n\n# Create DatastoreObject\nTo create `DatastoreObject` create class which inherites after `DatastoreObject`.\n``` Swift\nclass Employee: DatastoreObject {\n}\n```\n\nIf you need to do something after the object is created, you can override the `objectDidCreate` method, which is only called after the object is created.\nThis method does nothing by default.\n``` Swift\nclass Employee: DatastoreObject {\n    @Attribute.NotOptional var id: UUID\n\n    override func objectDidCreate() {\n        id = UUID()\n    }\n}\n```\n\nIf you need to perform some operations after init use `required init(managedObject: ManagedObjectLogic)` but you need insert `super.init(managedObject: managedObject)` into it's body like on example below:\n``` Swift\nclass Employee: DatastoreObject {\n    // Properties\n\n    required init(managedObject: ManagedObjectLogic) {\n        super.init(managedObject: managedObject)\n\n        // do something here ...\n    }\n}\n```\n\n\u003c/br\u003e\n\n# DatastoreObject Properties\nEach property is `property wrapper`.\n\n## `Attributes`\n\n### `NotOptional`\nIt represents single attribute which **must not** return nil value.\nUse this Attribute when you are sure that stored value is never nil.\n\n⛔️ If this attribute returns nil value it will crash your app.\n\nYou can use it with all attribute value types. Full list of types which meet with `AttributeValueType` is below.\n\n``` Swift\nclass Employee: DatastoreObject {\n    @Attribute.NotOptional var id: UUID! // The exclamation mark at the end is not required.\n    @Attribute.NotOptional var name: String\n    @Attribute.NotOptional var dateOfBirth: Date\n    @Attribute.NotOptional var age: Int // In data model this attribute may be: Integer 16, Integer 32, Integer 64.\n}\n```\n\n👌 You can use `objectDidCreate()` method to set default value.\n\n``` Swift\nclass Employee: DatastoreObject {\n    @Attribute.NotOptional var id: UUID\n\n    override func objectDidCreate() {\n        id = UUID()\n    }\n}\n```\n\n👌 If you need to have **constant (`let`)** property of `Attribute` you can set it as `private(set)`.\n\n``` Swift\nclass Employee: DatastoreObject {\n    @Attribute.NotOptional private(set) var id: UUID\n}\n```\n\n### `Optional`\nIt represents single attribute of entity which can return or store nil value.\n\n``` Swift\nclass Employee: DatastoreObject {\n    @Attribute.Optional var secondName: String? // The question mark at the end is required.\n    @Attribute.Optional var profileImageData: Data?\n}\n```\n\n### `Enum`\nIt represents an `enum` value.\nThis `enum` must meet the `RawRepresentable` and `AttributeValueType` protocol because it's `RawValue` is saved in `SQLite database`.\n\nYou can use it with all attribute value types.\nThis type of Attribute is optional.\n\n``` Swift\nenum Position: Int16 {\n    case developer\n    case uiDesigner\n    case productOwner\n}\n\nclass Employee: DatastoreObject {\n    @Attribute.Enum var position: Position?\n}\n\n// ...\n\nemployee.position = .developer\n```\n\n\u003c/br\u003e\n\n## `Relationships`\n\n### `ToOne`\nIt represents `one-to-one` relationship beetwen `SwiftDatastoreObjects`.\n\nThere can be passed an optional and nonoptional `Object`.\n\n``` Swift\nclass Office: DatastoreObject {\n    @Relationship.ToOne(inverse: \\.$office) var owner: Employee?\n}\n\nclass Employee: DatastoreObject {\n    @Relationship.ToOne var office: Office? // inverse: Office.owner\n}\n\n// ...\n\noffice.employee = employee\n\nemployee.office = office\n```\n\n⚠️ You must pass an inverse relationship in the declaration. Due Swift \"Reference cycle\" error, you can do it only one time. \n\n👌 Add info comment about the inverse to make code more readable.\n\n### `ToMany`\nIt represents `one-to-many` relationship which is `Set\u003cObject\u003e`.\n\n``` Swift\nclass Company: DatastoreObject {\n    @Relationship.ToMany var emplyees: Set\u003cEmployee\u003e // inverse: Employee.company\n}\n\nclass Employee: DatastoreObject {\n    @Relationship.ToOne(inverse: \\.$emplyees) var company: Company\n}\n\n// ...\n\ncompany.employees = [employee1, employee2, ...]\ncompany.employess.insert(employee3)\n\nemployee.company = company\n```\n\n⚠️ This type of property must not be nil in saving moment.\n\n### `ToMany.Ordered`\nIt represents `one-to-many` relationship where objects are stored in ordered which is `Array\u003cObject\u003e`.\n\n#### Whay Array instead of OrederedSet? By default Swift doesn't have OrderedSet collection. You can use it by adding other frameworks which supply ordered collections.\n\n``` Swift\nclass Employee: DatastoreObject {\n    @Relationship.ToMany.Ordered var tasks: [Task]\n}\n\nclass Task: DatastoreObject {\n    @Relationship.ToOne var employee: Employee?\n}\n\n// ...\n\ncompany.tasks = [task1, task2, ...]\ncompany.employee = employee\n```\n\n⚠️ Please, remember that this array can not have repetitions.\n\n⚠️ This type of property must not be nil in saving moment.\n\n\u003c/br\u003e\n\n# Setup\nFirstly you must create dataModel by passing types of `DatastoreObjects` which you want to use within it.\n\n``` Swift\nlet dataModel = SwiftDatastoreModel(from: Employee.self, Company.self, Office.self)\n```\n\n⛔️ If you don't pass all required objects for relationships, you will get fatal error with information about a lack of objects.\n\nIt creates `SQLite file` with name: `\"myapp.store\"` which stores objects from  passed model.\n``` Swift\nlet datastore = try SwiftDatastore(dataModel: dataModel, storeName: \"myapp.store\")\n```\n⚠️ Creating SwiftDatastore instance may throw an exception.\n\n👌 You can create separate model and separate store for different project configurations.\n\n\u003c/br\u003e\n\n# SwiftDatastoreContext\nTo create `SwiftDatastoreContext` instance you must:\n``` Swift\nlet datastoreContext = swiftDatastore.newContext()\n```\n\nℹ️ Each Context works on copy of objects which are saved in database.\nIf you create two contexts in empty datastore and on the first context create an object, this object will be availiabe on the second context  only when you perfrom save changes on the first context.\n\n⚠️ Every operation on `Context` \u003cins\u003e**must be called on it's private queue**\u003c/ins\u003e like `privateQueueConcurrencyType of ManagedObjectContext`. Because of that, each `Context` allows to call methods only inside closure the perform method what guarantees performing operations on context's private queue. Performing methods or modify objects outside of closures of these methods may cause runs what even may *crash* your app.\n\n### Child Context\nSwiftDatastore is based on the `CoreData` framework. For this reason it apply `CoreData's parent-child concurrency mechanism`. It allows you to make changes in child context like: update, delete, insert objects. Then you save changes into parent as all. Because of that saving changes is more safly then making a lot of changes on one context.\n\nTo create `SwiftDatastoreContext` instance you must:\n``` Swift\nlet parentContext = swiftDatastore.newContext()\nlet childContext = parentContext.createNewChildContext()\n```\n\n***\n\n## SwiftDatastore's operations\n### `perform`\nCode below this method is executing immediately without waiting for code inside the closure finish executing. \n\nThis method perform block of code you pass in first closure. Becasue of some opertaions may throw an exception: \n- The `success` closure is called when performing the closure will end without any exeptions. \n- The `failure` closure is called when performing the closure will be broken by an exception. You can handle an error you will get. In this case the `success` closure isn't called.\n\n``` Swift\ndatastoreContext.perform { context in\n    // code inside the closure\n} success {\n\n} failure { error in\n\n}\n```\n\nYou can also use method with completion. This construction is intended only for safe operations like: getting and setting values of `DatastoreObjcet`'s properties because it can perform only opertaions which don't throw exceptions.\n``` Swift\ndatastoreContext.perform { context in\n    // code inside the closure\n} completion {\n\n}\n```\n\n⛔️ Never use `try!` to perform throwing methods. In a case of any exception it may crash your app!\n\n\n### `createObject`\nCreates and returns a new instance of `DatastoreObject`.\n\nYou can create a new object **only** using this method.\n\nThis method is generic so you need to pass `Type` of object you want to create.\n\nThis method may throw an exception when you try to create DatastoreObject which entity name is invalid.\n``` Swift\ndatastoreContext.perform { context in\n    let employee: Employee = try context.createObject()\n}\n```\n\n### `deleteObject`\nIt deletes a single `DatastoreObject` object from `datastore`.\n\n``` Swift\ndatastoreContext.perform { context in\n    context.deleteObject(employee)\n}\n```\n\n### `saveChanges`\nIf context is a child context it saves changes into its parent. Otherwise it saves local changes into SQL database.\n \n⚠️ You are responsible of saving changes. If you don't save changes and terminate your app, changes will disappear.\n\n``` Swift\ndatastoreContext.perform { context in\n    try viewContext.saveChnages()\n}\n```\n\n### `revertChanges`\nThis method reverts \u003cins\u003e**unsaved**\u003c/ins\u003e changes.\n\n``` Swift\ndatastoreContext.perform { context in\n    let employee = try context.createObject()\n\n    employee.name = \"Tom\"\n    employee.salary = 3000\n\n    try context.save()\n\n    employee.salary = 4000\n\n    context.revertChnages()\n\n    print(employee.salary) // output: 3000\n}\n```\n\n### `fetch`\nIt fetches objects from datastore.\n\nThis method is generic so must pass type of object you want to fetch.\n``` Swift\ndatastoreContext.perform { context in\n    let employees: [Employee] = try context.fetch(where: (\\.$age \u003e 30),\n                                                  orderBy: [.asc(\\.$name), .desc(\\.$salary)],\n                                                  offset: 10,\n                                                  limit: 20)\n}\n```\n\n### `fetchFirst`\nFetches the first object which meets conditions.\n\nYou can use this method to find e.g. max, min of value in datastore using the `orderBy` parameter.\n\nThis method is generic so you have to pass type of object you want to fetch.\n\nℹ️ Return value is always optional.\n``` Swift\ndatastoreContext.perform { context in\n    let employee: Employee? = try context.fetchFirst(where: (\\.$age \u003e 30), \n                                                     orderBy: desc(\\.$salary)])\n}\n// It returns Employee who has the highest (max) salary.\n```\n\n### `fetchProperties`\nFetches only properties which keyPaths you passed as method's paramters.\n\nReturn type is an array of `Dictionary\u003cString, Any?\u003e`.\n\nℹ️ Parameter `properties` is required and is an array of `PropertyToFetch`. `PropertyToFetch` struct ensures that entered property is stored by DatastoreObject.\n\n⚠️ You can fetch only values types like: String, Int, Data, Bool etc.\nYou can not fetch any Relationships.\n\nℹ️ If you pass empty `propertiesToFetch` array this method will do nothing and return empty array.\n\nℹ️ This method returns properties values which objects has been saved in SQLite database.\n``` Swift\nvar properties: [[String: Any?]] = []\n\ndatastoreContext.perform { context in\n    properties = try context.fetch(Employee.self,\n                                   properties: [.init(\\.$salary), .init(\\.$id)],\n                                   orderBy: [.asc(\\.$salary)])\n    let firstSalary = fetchedProperties.first?[\"salary\"] as? Float\n}\n```\n\n### `count`\nReturns the number of objects which meet conditions in datastore.\n``` Swift\ndatastoreContext.perform { context in   \n    let numberOfEmployees = try context.count(where: (\\.$age \u003e 30))\n    // It returns number of Employees where age \u003e 30.\n}\n\n```\n\n### `convert existing object`\nThis method converts object between Datastore's Contexts.\n\nYou should use this method when you need to use object on different datastore then this which created or fetched this object.\n\n⛔️ This method needs object which has been saved in SQLite database. When you try convert unsaved object this method throws a exception.\n\nExample below shows how you can convert object from ViewContext into Context and update its property.\n\n``` Swift\nvar carOnViewContext: Car = try! viewContext.fetchFirst(orderBy: [.asc(\\.$salary)])\n\ndatastoreContext.perform { context\n    let car = try context.convert(existingObject: carOnViewContext)\n\n    car.numberOfOwners += 1\n\n    try context.saveChanges()\n}\n```\n\n### `deleteMany`\nIt deletes many objects and optionally returns number of deleted objects.\n\nAs the first parameter you have to pass object's type you want to delete.\n\nParameter `where` is required.\n\nAfter calling this method, property `willBeDeleted` returns ture. If you call `saveChanges` after that it returns false.\n\n⚠️ This method deletes objects **directly** from SQLite database so it doesn't delete objects which have not saved yet. For the same reason you \u003cins\u003edon't need to\u003c/ins\u003e call `saveChanges()` after call this method.\n\n``` Swift\ndatastoreContext.perform { context\n    let numberOfDeleted = try context.deleteMany(Employee.self, where: (\\.$surname ^= \"Smith\"))\n}\n```\n\n### `updateMany`\nIt updates many objects and optionally returns number of updated objects.\n\nAs the first parameter you have to pass object's type you want to update.\n\nParameter `where` is not required.\n\nParameter `propertiesToUpdate` is required and is an array of PropertyToUpdate. `PropertyToUpdate` struct ensures that entered value is the same type as it's key.\n\nIf you pass empty `propertiesToUpdate` array this method will do nothing and return 0.\n\n⚠️ This method updates objects **directly** in SQLite database so it doesn't update objects which have not saved yet. For the same reason you \u003cins\u003edon't need to\u003c/ins\u003e call `saveChanges()` after call this method.\n\n``` Swift\ndatastoreContext.perform { context\n    let numberOfUpdated = try context.updateMany(Employee.self,\n                                                 where: \\.$surname |= \"Smith\",\n                                                 propertiesToUpdate: [.init(\\.$age,\n                                                                      .init(\\.$name, \"Jim\")])\n}\n```\n\n### `refresh`\nThis method causes refreshing any objects which have been updated and deleted on the context from changes has made. Values of these objects is revering to last state from SQLite database or from context's parent if exists. \n\n⚠️ This method doesn't apply changes made on another context. \n\n``` Swift\nvar savedChanges: SwiftCoredataSavedChanges!\n\ndatastoreContext1.perform { context in\n    savedChanges = try viewContext.saveChnages()\n} success {\n\n    datastoreContext1.perform { context in\n       context.refresh(with: savedChanges)\n    }\n}\n```\n\n***\n\n## Using ViewContext\nIt's created to cowork with UI components. \n\n⚠️ Every operation on `ViewContext` must be called on **main queue** (main thread).\n\nTo create `Datastore's ViewContext` instance you must:\n``` Swift\nlet viewContext = swiftDatastore.sharedViewContext\n```\n\nIt allows you to call operations and get objects values without using closures when it's not neccessary. But it's you are responsible to call methods on `mainQueue` using `DispatchQueue.main`.\n\nExample how to use methods:\n\n``` Swift\n// mainQueue\n\nlet employees: [Employee] = try viewContext.fetch(where: (\\.$age \u003e 30),\n                                                  orderBy: [.asc(\\.$name), .desc(\\.$salary)],\n                                                  offset: 10,\n                                                  limit: 20)\n\nnameLabel.text = employee[0].name \n```\n\nℹ️ It's highly recommended to use `offset` and `limit` to increase performance.\n\n***\n\n## Using FetchedObjectsController\n⚠️ Every operation on `ViewContext` must be called on **main queue** (main thread).\n\nConfiguration is simillar to initialization `NSFetchedResultsController`.\nYou need to pass `viewContext`, `where`, `orderBy`, `groupBy`. Then call `performFetch` method.\n\nParameter `orderBy` is required.\n\nℹ️ If you don't pass `groupBy`, you will get a **single** section with all fetched objects.\n\n``` Swift\nlet fetchedObjectsController = FetchedObjectsController\u003cEmployee\u003e(\n    context: managedObjectContext,\n    where: \\.$age \u003e 22 || \\.$age \u003c= 60,\n    orderBy: [.desc(\\.$name), .asc(\\.$age)],\n    groupBy: \\.$salary)\n\nfetchedObjectsController.performFetch()\n```\n\n### `numberOfSections`\nReturns number of fetched sections which are created by passed `groupBy` keyPath.\n``` Swift\nlet numberOfSections = fetchedObjectsController.numberOfSections\n```\n\n### `numberOfObjects`\nReturns number of fetched objects in specific section.\n\nℹ️ If you pass sections index which doesn't exist this method returns 0.\n``` Swift\nlet numberOfObjects = fetchedObjectsController.numberOfObjects(inSection: 1)\n```\n\n### `getObject`\nReturns object at specific IndexPath.\n\n⛔️ If you pass IndexPath which doesn't exist this method runs `fatalError`.\n``` Swift\nlet indexPath = IndexPath(row: 1, section: 3)\n\nlet objectAtIndexPath = fetchedObjectsController.getObject(at indexPath: indexPath)\n```\n\n### `sectionName`\nThis method returns section name for passed section index.\nBecause of gorupBy parameter may be any type, this  method returns section name as String. You can convert it to type you need.\n\n``` Swift\nlet sectionName = fetchedObjectsController.sectionName(inSection: 0)\n```\n\n### `observeChanges`\nThis method is called every time when object which you passed as FetchedObjectsController's Generic Type has changed.\n\nChange type:\n- `inserted` - when object has been inserted.\n- `updated` - when object has been updated.\n- `deleted` - when object has been deleted.\n- `moved` - when object has changed its position in fetched section.\n\nℹ️ This method informs about one change. For example: when object of type `Employee` will be inserted and than deleted this method is called twice.\n\n``` Swift\nfetchedObjectsController.observeChanges { change in\n    switch change {\n    case .inserted(employee, indexPath):\n        // do something after insert\n    case .updated(employee, indexPath):\n        // do something after update\n    case .deleted(indexPath):\n        // do something after delete\n    case .moved(employee, sourceIndexPath, destinationIndexPath):\n        // do something after move\n    }\n}\n```\n\nYou can aso Use `Combine's changesPublisher` and subscribe changes.\n\n```Swift\nfetchedObjectsController.\n    .changesPublisher\n    .sink { change\n        switch change {\n        case .inserted(employee, indexPath):\n            // do something after insert\n        case .updated(employee, indexPath):\n            // do something after update\n        case .deleted(indexPath):\n            // do something after delete\n        case .moved(employee, sourceIndexPath, destinationIndexPath):\n            // do something after move\n        }\n    }\n    .store(in: \u0026cancellable)\n```\n\n***\n\n## OrderBy\nIt's a wrapper to `CoreData's NSSortDescriptor.`\nIt's enum which contains two cases: \n- `asc` - ascending,\n- `desc` - descending.\n\nTo use it you have to pass keyPath to DatastoreObject's ManagedObjectType.\n\n``` Swift\nextension PersonManagedObject {\n    @NSManaged public var age: Int16\n    @NSManaged public var name: String\n}\n\nfinal class Person: DatastoreObject {\n    @Attribute.Optional var age: Int?\n    @Attribute.NotOptional var name: String\n}\n\nlet persons: [Person] = context.fetch(orderBy: [.asc(\\.$name), .desc(\\.$age)])\n// It returns array of Persosns where age is ascending and age is descending.\n```\n\n## Where\nIt's a wrapper to `CoreData's NSPredicate.`. You can use prepared operators:\n- `\u003e` - greater than\n- `\u003e=` - greater than or equal to\n- `\u003c` - less than\n- `\u003c=` - less than or equal to\n- `==` - equal to\n- `!=` - not equal to\n- `?=` - contains (string)\n- `^=` - begins with (string)\n- `|=` - ends with (string)\n- `\u0026\u0026` - and\n- `||` - or\n\n``` Swift\nextension PersonManagedObject {\n    @NSManaged public var age: Int16\n    @NSManaged public var name: String\n}\n\nfinal class Person: DatastoreObject {\n    @Attribute.Optional var age: Int?\n    @Attribute.NotOptional var name: String\n}\n\nlet persons: [Person] = context.fetch(where: \\.$age \u003e= 18 \u0026\u0026 (\\.$name ^= \"T\") || (\\.$name |= \"e\"))\n// It returns array of Persosns where age is great than 18 and name begins with \"T\" or ends with \"e\".\n```\n\n\u003c/br\u003e\n\n# Observing DatastoreObject Properties\nYou can observe changes of any `Attribute` and `Relationship`.\nThe closure is performed every time when a value of observed property changes no matter either the change is done on observed instance of `DatastoreObject` or on another instance but with the same `DatastoreObjectID` in the same `SwiftDatastoreContext`.\n\n⚠️ You can add only one observer per one instance of `DatastoreObject`. If you add more then one only the last one will be performed.\n\n``` Swift\nemployee.$name.observe { newValue in\n    // New value of Optional Attribute may be nil or specific value.\n}\n```\n\nYou can also use `Combine's` newValuePublisher to subscribe any newValue.\n\n``` Swift\nemployee.$position\n    .newValuePublisher\n    .sink { newValue in\n        // do something after change\n    }\n    .store(in: \u0026cancellable)\n```\n\n\u003c/br\u003e\n\n# Testing\nYou can use SwiftDatastore in your tests.\nAll what you need to do is set `storingType` to `test` as example below:\n\n``` Swift\nlet datastore = try SwiftDatastore(dataModel: dataModel, storeName: \"myapp.store.test\", storingType: .test)\n```\n\nIn that configuration `SwiftDatastore` create normal sqlite file but it will **delete it** when your test will end or when you call test again (eg. in the case when you got crash). As a result, in every test you work on totally **new data**.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomkuku%2Fswiftdatastore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomkuku%2Fswiftdatastore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomkuku%2Fswiftdatastore/lists"}