Only this pageAll pages
Powered by GitBook
1 of 16

ObjectBox Swift

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Advanced

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

The Sandbox on macOS

Installation for macOS is the same as for iOS, however, there is one small extra step.

The Sandbox on macOS

You do not need to perform any of these steps for an application running on iOS, or if your macOS application is not sandboxed.

Currently, use of ObjectBox from a sandboxed macOS application requires you to set up at least one App Group for ObjectBox to be able to open a database, in addition to whatever you would normally have done to gain access to the destination folder under the sandbox:

  1. Open your project in Xcode.

  2. Select the project icon at the very top of the Project Navigator.

  3. Select the Signing & Capabilities tab where you turned on App Sandbox.

  4. Click the + Capability button and double-click App Groups.

  5. If the App Groups list is empty, click the little "+" icon to add one.

You should now see a list entry like FGDTDLOBXDJ.$(TeamIdentifierPrefix). If you only see $(TeamIdentifierPrefix) , you still need to set up your team and code-signing under General.

That should be it: ObjectBox will pick up the App Group Identifier without any additional work.

Pick a short group identifier. There's an internal limit in macOS of 31 characters for semaphores, that enforces the complete string (FGDTDLOBXDJ.$(TeamIdentifierPrefix) in the example) to be 20 characters or less.

Customizing Code Generation

ObjectBox Swift generates code to make it as easy to use and fun for you as possible to swiftly persist objects. In rare cases you may want adjust the code generator's work.

Although you should generally not need to, you can adjust the behavior of ObjectBox's code generator by passing it additional arguments.

The general procedure is the same: Open the [OBX] Update Sourcery Generated Files build phase for your target in Xcode and edit the shell script there. Usually it looks something like:

"$PODS_ROOT/ObjectBox/generate_sources.sh" -- --output "$PROJECT_DIR/generated/EntityInfo-TARGETNAME-iOS.generated.swift"
                                           --model-json "$PROJECT_DIR/model-TARGETNAME-iOS.json"

where TARGETNAME is the name of your target.

Note the extra two dashes, which help the script distinguish arguments to be forwarded to the code generator from arguments directly to the script. You only need one double-dash at the start, even when passing multiple options to the script.

Controlling Output File Names

If you have several different targets in your project and want them to share the same model, you can customize the names and paths for the model.json and EntityInfo.generated.swift files by changing the script to use the same name for these files:

"${PODS_ROOT}/ObjectBox/generate_sources.sh" -- --output "${PROJECT_DIR}/generated/EntityInfo.generated.swift"
                                                --model-json "${PROJECT_DIR}/model.json"

In this example, EntityInfo.generated.swift and model.json without the target name in them.

Modifying the Module Name

Or if you wanted to control the Swift module name under which the generated source code should end up, you would do this by telling the script to forward the --xcode-module option to the code generator:

"${PODS_ROOT}/ObjectBox/generate_sources.sh" -- --xcode-module JennasModule

Where JennasModule would be the module name to use.

Specifying Access Control for Generated Code

If you are writing a framework and want to export your entity classes from it, you will need to make the generated code public. You do this similarly, by adding the --visibility option:

"${PODS_ROOT}/ObjectBox/generate_sources.sh" -- --visibility public

Anonymous Build Statistics

The code generator sends some anonymous usage statistics during build (on the machine you are building on, never inside your app!). If you do not wish this information to be collected, you can pass the following option to Sourcery:

"${PODS_ROOT}/ObjectBox/generate_sources.sh" -- --no-statistics

The following information is collected:

  • An anonymous random GUID identifying the installation of ObjectBox

  • The number of builds

  • A hash of the target's namespace name

  • OS version, ObjectBox version, Deployment OS, architecture and versions

  • System country and language setting

  • CI system the build is run under

The code generator will try to batch this information together so there is at most one request sent per day.

Other Options

It is not recommended to customize any other options, as they are subject to change, but should you need to do so, you can obtain a list by asking the code generator directly by typing the following into Terminal:

/path/to/ObjectBox/Sourcery.app/Contents/MacOS/Sourcery --help

Which will print a syntax description for the code generator.

Manual Installation

If you aren't using CocoaPods, ObjectBox can easily be installed manually. Just download, add the framework to your project, and run our setup script.

Download

You can get the newest ObjectBox-xcframework-<version>.zip from our GitHub Releases page:

Latest ObjectBox Swift GitHub release with XCFramework download highlighted

Unpack the ZIP-archive and put the entire folder somewhere in your project's folder (for example, if your project is in /Users/vivien/RecordsDatabase/RecordsDatabase.xcodeproj, you could put ObjectBox at /Users/vivien/RecordsDatabase/ObjectBox ).

Add the Framework to your Project

Like with any embedded framework, you link to it and copy it into your application:

  • Find the ObjectBox.framework for the platform of your application from the ObjectBox.xcframework folder.

  • Open your project's target settings by clicking the little blue project icon at the top of the Project Navigator in Xcode.

  • Select the General tab for your target and find the Frameworks, Libraries and Embedded Content section.

  • Drag the ObjectBox.framework into the list and choose "Embed & Sign" from the popup at its right.

  • macOS and Xcode 10 and earlier only:

    • Go to the Build Phases tab and add a Copy Files build phase.

    • Select Frameworks as the Destination from the popup.

    • Drag the ObjectBox.framework into it.

You're done, your project is linking to ObjectBox.

Setup Script

Then open Terminal and run

gem install xcodeproj /path/to/ObjectBox/setup.rb /path/to/MyProject.xcodeproj

where /path/to/ is again where your project is, like /Users/vivien/RecordsDatabase/ above.

The xcodeproj gem is only needed by the installation script to do its work. It's not required to build your application.

Done

You can now open your project and follow the rest of this tutorial.

The setup.rb Script

ObjectBox uses a Swift code generator to generate boilerplate code that passes object information to the library. Therefore, you can simply enjoy plain Swift objects.

If you are using the provided setup.rb script, you do not need to do any of these steps. This section is mostly of interest for people who want to know what that script does behind the scenes or who for some reason cannot use the script.

ObjectBox brings its own code generator (based on Sourcery 🧙‍♂️) to generate boilerplate code that passes information about your objects to the library. Code generation avoids inheritance trees and runtime reflection so you can write plain Swift objects that can be persisted extremly fast.

The script does the following things to ensure the code generator is run transparently whenever you build your project:

Adding the Sourcery Build Phase to Your Project

In your Xcode project, add a "New Run Script Phase" and make sure it comes above the Compile Sources phase of your project. The code generator needs to generate the code you want to compile first.

We call the Build Phase "[OBX] Update Sourcery Generated Files".

Enter the following script into this build phase:

The above example assumes you've extracted the files from the ObjectBox release on Github into a folder named ObjectBox next to your project. If you had a folder named external for all external dependencies next to a myproject folder that contains your project, that line might read:

You get the picture. In general, it is a good idea to use project-relative paths, so anyone checking out your project can run it, no matter where they keep their checkouts, or what their username.

Adding the Generated Files to Your Project

Build your project once, so it can create the generated/EntityInfo.generated.swift file for you. It will not contain much of interest, yet, but now that the generated/ directory and its contents exist, you can drag them into your Xcode project so they get compiled as well (make sure you add folders as "groups", not as "folder references").

You now have a working ObjectBox setup in your project and should be able to follow the rest of the instructions in our Getting Started section.

Get Started with ObjectBox Swift
"${PROJECT_DIR}/ObjectBox/generate_sources.sh"
"${PROJECT_DIR}/../external/ObjectBox/generate_sources.sh"
setup.rb
Adds a build phase that runs the code generator to your project.
Adds the generated source files to your project

ObjectBox Swift FAQ

The swiftiest database for iOS answers the most asked questions. Is ObjectBox based on SQLite? Is ObjectBox a CoreData alternative? What about Firebase? How is ObjectBox...

Conceptual

Is ObjectBox based on SQLite?

No, ObjectBox is a standalone NoSQL database designed and optimized for storing objects. ObjectBox clearly is a SQLite alternative as well as a CoreData alternative. There are no rows and columns. Benefits: simpler API and better performance.

Another big difference is that SQLite is an embedded database only. While ObjectBox also is an embedded database, it offers additional modes of operation like client/server and data synchronization among several machines - from small devices to servers and the cloud. ObjectBox therefore clearly is also an alternative to Firebase (for syncing data that is),

How is ObjectBox different from other solutions?

TL;DR Swift-first, speed, simple APIs

Swift first: The API is designed for Swift without an Objective-C legacy. For example there is no need for @objc, no enforced base class for entities, and no wrappers needed for optional types. To give some background here, we started with a Objective-C(++) middle layer, but had this huge turning point during development. We realized that putting Swift first required us to take rather drastic measures and remove our Objective-C(++) code completely. We wrote about that and the immediate benefits like struct support and a huge performance boost.

Speed: ObjectBox was built for high performance on mobile devices. Minimalism as a leading principle. We believe, we've built one of the most resource efficient databases that exist. Of course, you should not just rely on what we claim, but see for yourself. Our performance benchmarking code is open source.

Simple APIs: Browse through our docs using the menu on the left. We tried hard to give developers like you a simple API for powerful features. Another example are entities themselves, which are plain Swift objects without any "magic" that would break threading or cause values from being shown in the debugger, etc. With ObjectBox, you can use objects across threads without restrictions. For example, you can defer loading objects to a background thread and pass them to the UI thread to display them.

And by the way; we want your feedback: of course, ObjectBox is not perfect. We set our goals high but we also fail at times. Let us know how we can improve. If you have something specific in mind, please raise an issue on GitHub. And we're also happy if you would send us general feedback on how we are doing (takes 1-2 minutes). Thank you!

Technical

Why are you not using Codable in ObjectBox?

ObjectBox uses a code generator, which does more than serializing objects into a binary format. It manages an internal data model and lets you e.g. rename a Swift property without breaking already stored objects. All of this happens at compile time, and gets checked into version control, so each user's and developers' databases are compatible. Codable happens at runtime, on each user's machine. Simply said, ObjectBox could not do what it does with Codable.

Troubleshooting

Error message "Missing argument for parameter 'model'..." when creating a new Store

TL;DR: build the project first.

ObjectBox's code generator generates a convenience initializer for you that doesn't need the model parameter because it sets up your model for you. It does this after parsing all your entities, which happens at build time. Thus, make sure to build your project at least once after declaring your entities. Otherwise Xcode's CodeSense auto-completion will not propose the right initializer in its list, and will display this erroneous error message.

Error Message "Could not open env for DB (1)" in Sandboxed macOS App

A sandboxed macOS application must have at least one App Group set up in its target settings under Signing and Capabilities. See The Sandbox on macOS for detailed instructions. This error occurs if no app group can be found.

Error message "ObjectBox does not contain bitcode" when linking

ObjectBox currently does not contain Bitcode, as it generally is not needed and only increases file sizes. You can still build for device and app store distribution by turning off Bitcode for your application as well. As the error message goes on to explain, you do this using the target's Enable Bitcode setting under Build Settings in Xcode ( ENABLE_BITCODE).

If you have a strong need to obtain a version of ObjectBox with Bitcode, please let us know.

Transactions

ObjectBox for Swift is a fully transactional NoSQL database and ACID compliant. Learn here how database transactions work. Also, how does multiversion concurrency work?

What are ObjectBox Transactions?

A database transaction groups several operations into a single unit of work that either executes completely or not at all. If you are looking for a more detailed introduction to transactions in general, check out this Wikipedia article on database transactions.

You may not notice it, but almost all interactions with ObjectBox involve transactions. For example, if you call put a write transaction is used. Also if you get an object or query for objects, a read transaction is used. All of this is done under the hood and transparent to you. It may be fine to completely ignore transactions in your app without running into any problems. With more complex apps however, it’s usually worth learning transaction basics to make your app more consistent and efficient.

Explicit transactions

All ObjectBox operations run in implicit transactions – unless an explicit transaction is in progress. In the latter case, multiple operations share the (explicit) transaction. In other words, with explicit transactions you control the transaction boundary. Doing so can greatly improve efficiency and consistency in your app.

The class Store offers the following methods to perform explicit transactions:

  • runInTransaction: Runs the given closure inside a transaction (comes in result-forwarding and/or throwing variants).

  • runInReadOnlyTransaction: Runs the given closure inside a read(-only) transaction. Unlike write transactions, multiple read transactions can run at the same time.

The advantage of explicit transactions over the bulk put operations is that you can perform any number of operations and use objects of multiple boxes. In addition, you get a consistent (transactional) view on your data while the transaction is in progress.

Example for a write transaction:

try store.runInTransaction {
   try allUsers.forEach { user in
     if (modify(user)) {
       try box.put(user)
     } else {
       try box.remove(user)
     }
   }
}

Transaction Costs

Understanding transactions is essential to master database performance. If you just remember one sentence on this topic, it should be this one: a write transaction has its price.

Committing a transaction involves syncing data to the physical storage, which is a relatively expensive operation for databases. Only when the file system confirms that all data has been stored in a durable manner (not just memory cached), the transaction can be considered successful. This file sync required by a transaction may take a couple of milliseconds. Keep this in mind and try to group several operations (e.g.putcalls) in one transaction.

Consider this example:

try allUsers.forEach { user in
   modify(user) // modifies properties of given user
   try box.put(user)
}

Do you see what’s wrong with that code? There is an implicit transaction for each user which is very inefficient especially for a high number of objects. It is much more efficient to use of of the put overloads to store all users at once:

try allUsers.forEach { user in
   modify(user) // modifies properties of given user
}
try box.put(allUsers)

Much better! If you have 1,000 users, the latter example uses a single transaction to store all users. The first code example uses 1,000 (!) implicit transactions causing a massive slow down.

Read Transactions

In ObjectBox, read transactions are cheap. In contrast to write transactions, there is no commit and thus no expensive sync to the file system. Operations like get , count , and queries run inside an implicit read transaction if they are not called when already inside an explicit transaction (read or write). Note that it is illegal to put when inside a read transaction: an exception will be thrown.

While read transactions are much cheaper than write transactions, there is still some overhead to starting a read transaction. Thus, for a high number of reads (e.g. hundreds, in a loop), you can improve performance by grouping those reads in a single read transaction (see explicit transactions below).

Multiversion concurrency

ObjectBox gives developers Multiversion concurrency control (MVCC) semantics. This allows multiple concurrent readers (read transactions) which can execute immediately without blocking or waiting. This is guaranteed by storing multiple versions of (committed) data. Even if a write transaction is in progress, a read transaction can read the last consistent state immediately. Write transactions are executed sequentially to ensure a consistent state. Thus, it is advised to keep write transactions short to avoid blocking other pending write transactions. For example, it is usually a bad idea to do networking or complex calculations while inside a write transaction. Instead, do any expensive operation and prepare objects before entering a write transaction.

Note that you do not have to worry about making write transactions sequential yourself. If multiple threads want to write at the same time (e.g. via put or runInTransaction), one of the threads will be selected to go first, while the other threads have to wait. It works just like a lock.

Locking inside a Write Transaction

Avoid locking (even implicitly by e.g. calling DispatchQueue.syncor explicitly using NSLock or objc_sync_enter) when inside a write transaction when possible. Because write transactions run exclusively, they effectively acquire a write lock internally. As with all locks, you need to pay close attention when multiple locks are involved. Always obtain locks in the same order to avoid deadlocks. If you acquire a lock “X” inside a transaction, you must ensure that your code does not start another write transaction while having the lock “X”.

Enums and Custom Types

ObjectBox Swift supports enums and custom types: here's how.

Custom types allow entities to have properties of any type. This is based on mapping custom classes to built-in types. ObjectBox recognizes the following built-in (Swift) types:

Int8, UInt8
Int16, UInt16
Int32, UInt32
Int64, Int, UInt64, UInt
Float, Double
Data, [UInt8]
String
Date

Enums

ObjectBox has built-in support for RawRepresentable enums. All that is needed is an annotation to tell ObjectBox to convert an enum:

enum TestEnum: Int {
    case unknown = 0
    case first = 100
    case second = 200
    case third = 300
}

class EnumEntity: Entity {
    var id: Id = 0
    // objectbox: convert = { "default": ".unknown" }
    var custom: TestEnum
}

Specify a default value to use when the value in the database is missing or invalid as the default argument to the // objectbox: convert annotation.

A database can have a missing value even for non-optional fields if you add a property to your entity and then open a database that was written by an older version of your app that did not have that field yet. A database may contain an invalid value if an older version of your app opens a file created by a newer version of your app that has added a new case to this enum.

When persisting enums, there are a couple of best practices:

  • Always assign explicit rawValues to each enum case: Implicitly-assigned rawValues are unstable, and can easily change the next time you edit your enum definitions.

  • Prepare for the unknown: Define an unknown enum value and specify it as the default. It can serve to handle missing or unknown values. This will allow you to gracefully handle cases like an old enum value getting removed without having to constantly unwrap optionals.

You can leave away the "default" annotation if your property is an optional. In that case nil will be the automatic default.

Enums in queries

QueryBuilder is unaware of enums. You have to use the enum's rawValue for queries.

So for the EnumEntity example above you would get users with the custom property of second with the query condition box.query { User.role == TestEnum.second.rawValue }.

Custom Property Converters

To add support for a custom type, you can map properties to one of the built-in types using an // objectbox: convert annotation. You also need to provide a class to serve as a property converter.

For example, you could define a color in your entity using a custom Color class and map it to a String.

Here is an example mapping an enum to an Int manually:

class RoleConverter {
    static func convert(_ enumerated: Role) -> Int {
        return enumerated.rawValue
    }
    static func convert(_ num: Int?) -> Role {
        guard let num = num else { return Role.default }
        return TestEnum(rawValue: num) ?? Role.default
    }
}

enum Role: Int {
    case default = 0
    case author = 1
    case admin = 2
}

class User: Entity, CustomDebugStringConvertible {
    var id: Id = 0
    // objectbox: convert = { "dbType": "Int", "converter": "RoleConverter" }
    var role = Role.default
}

Things to look out for

Be sure to correctly handle nil and invalid values: If you add a field to your entity later on, old records in a database will not have a value for this field. Your converter will be handed a nil value for those instead. Or if a user opens a database created with a newer version of your app that supports additional values for an enum with an older version that doesn't know about these, you will have to supply a fallback value from your converter (In the above example, those are the two Role.default returns in convert(_: Int?)).

You must not interact with the database (such as using Box or Store) inside the converter. The converter methods are called within a transaction, so for example getting or putting entities into a box will fail.

If you implement your database-to-class converter method to not take an optional, ObjectBox will supply an appropriate 0 value for missing values, Same as it would for the underlying type without a converter.

Note: Make sure the converter is thread safe, because it might be called concurrently on multiple entities.

List/Array types

At the moment it is not possible to use arrays with converters, apart from [UInt8], which is treated like Data. However, you could convert a List of Strings to a JSON array resulting in a single string for the database.

Alternately, you can replace arrays with relations and create a new entity for the array elements. So, for example instead of

class Document {
    var id: Id = 0
    var userNames: [String] // ObjectBox doesn't know how to store this
}

You would have two entities:

class Document {
    var id: Id = 0
    var users: ToMany<User>
}

class User {
    var id: Id = 0
    var document: ToOne<Document> = nil
    var name: String
}

Custom types in queries

QueryBuilder is unaware of custom types. You have to use the raw database value for queries.

So for the User example above you would get users with the role of admin with the query condition box.query { User.role == Role.admin.rawValue } or box.query { User.role == 2 }.

Install ObjectBox Swift

ObjectBox is a NoSQL Swift database uniquely optimized for high-performance on smartphones. Learn how to set up ObjectBox Swift and persist objects in your iOS or macOS application.

Having problems installing ObjectBox using this guide? Please, let us know where we lost you. Thanks for your help!

ObjectBox Swift is available as a:

  • CocoaPods pod

  • Swift Package

Alternatively, a manual setup in Xcode is possible.

CocoaPods

If you are new to CocoaPods, check out their website for an introduction and installation instructions.

To add the ObjectBox Swift dependency, add the following line to your Podfile:

pod 'ObjectBox'

Then install the pod and run the ObjectBox setup script:

pod install --repo-update
Pods/ObjectBox/setup.rb

The setup.rb script will configure your Xcode project to run ObjectBox generator on every build by adding a build phase (called [OBX] Update Sourcery Generated Files) for every target with an executable.

Disable the User Script Sandboxing option to run the ObjectBox generator in recent Xcode versions:

Search for "sandbox" in your projects build settings and set User Script Sandboxing to No.

The ObjectBox code generator needs to run in the project directory and will generate files there. If this is executed in a sandbox, the build phase will fail.

Then, open your Xcode workspace (.xcworkspace) instead of the Xcode project (.xcodeproj).

Now, you are all set to define your first ObjectBox entities! To continue check the Getting Started guide or the example project.

Troubleshoot setup issues

If installing the pod or configuring the project failed, try to update the xcodeproj and cocoapods gem:

gem update xcodeproj && gem update cocoapods && pod repo update

On Apple Silicon (M1), ensure you have the latest gems for CocoaPods:

gem update ffi ethon

Update to a new version

After a new version of ObjectBox Swift is released, update the ObjectBox pod and run the setup script again:

pod repo update
pod update ObjectBox
Pods/ObjectBox/setup.rb

Swift Package

The Swift Package is currently in preview, we welcome your feedback!

Instructions depend on whether you want to set up an Xcode project or a Swift Package Manager manifest.

Set up an Xcode project

In Xcode, add a package dependency and search for the package URL:

https://github.com/objectbox/objectbox-swift-spm

For the Dependency Rule, we recommend to use "Up to Next Major Version" and version 4.3.0-beta.2. To find the latest version to use view the tags of the objectbox-swift-spm repository.

Finally, when asked, add the ObjectBox.xcframework to your app target. Or to use ObjectBox Sync (requires access to the Sync feature), add the ObjectBox-Sync.xcframework instead.

Now, you are all set to define your first ObjectBox entities. To continue check the Getting Started guide or the example project.

Set up a Swift Package Manager manifest

In your Swift.package file, add the ObjectBox Swift Package repository to the dependencies block:

.package(url: "https://github.com/objectbox/objectbox-swift-spm.git", from: "4.3.0-beta.2"),

Add the ObjectBox.xcframework to the dependencies of the desired target in targets:

.product(name: "ObjectBox.xcframework", package: "objectbox-swift-spm")

Or to use ObjectBox Sync (requires access to the Sync feature), add the ObjectBox-Sync.xcframework instead:

.product(name: "ObjectBox-Sync.xcframework", package: "objectbox-swift-spm")

Your Swift.package file should then contain sections similar to this:

dependencies: [
    .package(url: "https://github.com/objectbox/objectbox-swift-spm.git", from: "<version>"),
],
targets: [
  .executableTarget(
    name: "YourApp",
    dependencies: [
        .product(name: "ObjectBox.xcframework", package: "objectbox-swift-spm")
    ],
]

Now, you are all set to define your first ObjectBox entities! To continue check the Getting Started guide or the example project.

Recommended Xcode project settings

Disable Bitcode

Some versions of ObjectBox do not include Bitcode and thus you may need to adjust "Build Settings" in Xcode accordingly. In that tab, ensure "All" is active and search for "bitcode". You will find the "Enable Bitcode" setting which you set to "No".

Otherwise, for some build targets, you will get a build error like this: '.../ObjectBox.framework/ObjectBox' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target.

ObjectBox Swift Database Docs

ObjectBox is a NoSQL Swift object database for iOS providing high-performance on mobile devices. It is an easy-to-use Core Data alternative.

Welcome to the official documentation for ObjectBox Swift. Here's what you need for your first steps:

Your opinion matters to us! To make ObjectBox better for our users, we have set up an . Please do fill this in (it only takes 2 minutes). Every response is highly appreciated. To rate this documentation, you can use the "Was this page helpful?" smiley at the end of each page.

Otherwise, feel free to open an or send us your comments to contact[at]objectbox.io - Thank you! - and if you like what you see, we also appreciate a shoutout :)

ObjectBox Change Log

For the latest changes see the .

Old releases

1.9.2 - 2023-11-14

  • Built with Xcode 15.0.1 and Swift 5.9.

  • Update to .

  • Include debug symbols for the ObjectBox Swift library.

1.9.1 - 2023-11-21

  • Built with Xcode 15.0.1 and Swift 5.9.

  • Fix incorrect flags getting generated in the model JSON for to-one properties.

  • setup.rb: Changed configured build phase for ObjectBox generator to always run to remove warning due to no build phase inputs being configured (as the script can not know which source code files contain entities).

  • Require at least iOS 12.0 and macOS 10.15.

1.9.0 - 2023-09-19

  • Built with Xcode 14.3.1 and Swift 5.8.1 (Xcode 14.3.1 or higher recommended)

  • Update to

    • Queries: all expected results are now returned when using a less-than or less-or-equal condition for a String property with index type VALUE. Reported via

    • Queries: when combining multiple conditions with OR and adding a condition on a related entity ("link condition") the combined conditions are now properly applied. Reported via

1.8.1 - 2023-01-30

Highly recommended bugfix release; please update.

  • Build with Xcode 14.2 and Swift 5.7.2 (Xcode 14.2 or higher recommended)

  • Fixes "Could not put (-30786)", which may occur in some corner cases on some OSes (ARM based).

1.8.0 - 2022-12-13

  • Build with Xcode 14.1 and Swift 5.7.1 (Xcode 14.1 or higher recommended)

  • Many internal improvements (see versions 0.16 to 0.18)

  • Note: the targeted macOS version was increased from 10.10 to 10.13. Alternatively, you can use version 1.8.1-rc for a podspec targeting 10.11 (still linked for 10.13).

  • (Data) Sync only: protocol updates improving efficiency. Reach out via your existing contact to check if any actions are required for your setup.

1.7.0 - 2022-02-22

  • Unique properties can now be annotated to replace objects "on conflict" (e.g. useful for a secondary key).

  • Attaching to previously opened stores (using just the directory path)

  • DateNano type (a date stored with nanosecond precision; default is milliseconds)

  • Build with Xcode 13.2.1 and Swift 5.5 Note: requires Xcode 13.1 or later

  • Many internal improvements (see 0.15 and 0.15.2)

  • Prepared new "flex" and "id-companion" annotations (at this point for compatibility only)

1.6.0 - 2021-05-13

  • Experimental Swift Package Manager (SPM) support; added 2021-06-02

  • Build with Xcode 12.5 and Swift 5.4

  • Updated Sync protocol to V3

  • Internal improvements

1.5.0 - 2021-05-03

  • Added support for Apple Silicon (e.g. M1 ARM64-based CPU)

  • Switched packaging to XCFramework (note: this will help us with SwiftPM in the future, but currently breaks Carthage consumers)

  • Several updates to the Sync API

  • Several internal improvements

  • Built with Swift 5.3.2

1.4.1 - 2020-11-10

  • Several internal improvements under the hood; an indirect consequence is that Foundation is not implicitly imported anymore (you may have to add Foundation imports after upgrading)

  • New sync annotation

  • Build target for macOS set to 10.10 (was undefined before)

  • Built with Swift 5.3

1.4.0 - 2020-09-08

See also:

  • ToMany improvements

    • applyToDb() now puts new objects automatically; you do not have to put them individually anymore

    • New properties to probe the state: resolved and canInteractWithDb

    • A throwing resolveFromDb() to explicitly fetch the target objects allowing to catch errors before starting to interact with the target objects

    • getUncachedFromDb() and getUncachedIdsFromDb() for getting plain arrays from the database

    • Fixes: e.g. resolving now happens transactionally (improves performance too), removeAll() is now supported

  • ToOne: putting an object with a ToOne, now avoids to put already persisted related ToOne target object. Note: While this is a fix, it may be a breaking change if you relied on that behavior before.

  • Box: added a bulk get() for given IDs and renamed dictionaryWithEntities() to getAsDictionary()

  • Read-only stores

  • Added a version check to ensure that the library matches the generator

  • Reduced framework size by omitting outdated 32 bit versions

1.3.1 - 2020-06-29

  • Fixes for ToMany

  • Several internal improvements, e.g. query links are resolved faster

1.3.0 - 2020-05-11

  • Built with and for Swift 5.2

  • Fix sporadic "errno 12" while opening a DB on iOS devices

  • Lower deployment target to iOS 9.3 (seems to be still used by older iPads)

  • Fix for PropertyQuery (e.g. single line construction without holding on to Query)

  • API clean ups

    • Remove deprecated Query.all(); use find() instead

    • Box functions like visit(), for() and forEach() are now read-only by default. New optional writable flags changes this to use a write transaction instead.

1.2.0 - 2019-12-17

  • Added support for optional unsigned property types

  • Better type support for queries; e.g. unsigned and optional properties, Bool properties

  • Property queries compute sums and averages more precisely (improved algorithms and wider types)

  • Some Query API clean up, e.g. setting query parameters does not throw anymore, findUnique() always returns an optional result, etc.

1.1.1 - 2019-11-23

Fix for the 1.1 "realpath" error during build on machines without Homebrew coreutils.

1.1 - 2019-11-18

  • Experimental Carthage support (in addition to CocoaPods)

  • If a convert annotation on an enum has no explicit database type given, its RawType is used.

  • Various performance optimizations

  • Usability improvements, e.g.setup.rb now asks which project to use if there is more than one, and creates a non-empty *.generated.swift file to help with code completion.

  • Added put() with variable argument list, put(inout) for mutable structs, and putAndReturnIDs(). Upgrade note: put() itself no longer returns IDs.

  • Added variants of Box methods for ContiguousArray class, which are faster than the methods using standard arrays.

  • Anonymous build can be turned off using --no-statistics

1.0.1 - 2019-10-01

  • Fix bug that prevented code generator from working in projects that use SwiftPM

1.0 - 2019-09-24

  • Relations in queries

  • Observer callbacks for data changes

  • Many-to-many relations

  • Asynchronous put/remove

  • Edit relations through their backlinks

  • API clean up; e.g. renames, simple IDs, simplifications, etc.

  • If you've been using pre-releases, you can find migration instructions in the .

0.9.1 - 2019-08-13

iOS hotfix

  • Fixed "Storage error code 78" for iOS

0.9.0 - 2019-07-22

Open Source Release

  • Source code for the Swift binding is now available

  • Reduced write/update time by about 25%

  • Rewrote the remaining Objective-C classes in Swift

  • Fix an issue reading an old entity written before a new, non-optional property was added to it

  • Improvements for people using SwiftLint in their projects

  • Added findIds(), visit()/forEach(). Added remove() for queries.

  • Improved support for enums and other user-defined types

  • Data and [UInt8] are now supported as property types.

  • Binaries: Swift 5 ABI only

0.8.0 - 2019-05-14

Struct Support and Performance Improvements

  • Immutable , you are no longer restricted to classes

  • We no longer throw NSError-based errors, they're all enum ObjectBoxError cases now

  • Strings that were created as NSStrings previously are now created as Swift Strings, which should reduce the number of unnecessary UTF-16/UTF-8 roundtrip conversions when Swift 5.1 arrives

  • New projects are now set up with separate generated source files for each target by setup.rb.

  • Binaries: Swift 5 ABI only

0.7.0 - 2019-04-02

Swift 5 and Build Improvements

  • Binaries: Swift 5 ABI only

  • ObjectBox setup script can now also be

  • ObjectBox can now be

0.6.0 - 2018-12-19

Model Migration

  • Your data model is now when you make changes

  • Properties can be

  • You can require property fields to be

  • You can specify a than the instance variable's

  • Use of has been simplified

  • You do not need to annotate back-links in relations anymore in clear cases

  • Binaries: Swift 4 ABI only.

  • Alpha users:

    • Use annotations // objectbox: instead of // sourcery:

    • Delete any database files you created before (one time only, starting with the beta we have model migrations)

0.5.5 - 2018-11-29

Alpha 6

  • Remove code-generator limitations regarding order of properties vs. relations

  • Support sandboxed macOS applications ()

  • Add "transient" annotation for skipping properties

  • Binaries: Swift 4 ABI only.

0.5.4 - 2018-11-27

Alpha 5

  • Code generation fixes for optionals

  • Expanded example app to demo optionals

  • Fixes for Date-decoding into entities

  • Binaries: Swift 4 ABI only

0.5.3 - 2018-11-26

Cleanup

  • Just small things. Also an elephant.

0.5.2 - 2018-11-22

iOS Sandboxing.

  • Fixed issues that could occur when deploying to device.

  • Added auto-registration of your entities

0.5.1 - 2018-11-19

Alpha 2

  • Fix an issue related to "duplicate index ID" error message.

0.5.0 - 2018-11-16

Alpha 1

  • Initial public release for comment.

Meta Model, IDs and UIDs

Unlike relational databases like SQLite, ObjectBox swift database does not require you to create a database schema. That does not mean ObjectBox is schema-less. Learn here how it is done.

While ObjectBox does not require you to create a database schema, the database is not schema-less. For efficiency reasons, ObjectBox manages a meta model of the data stored. This meta model is actually ObjectBox’s equivalent of a schema. It includes known object types including all properties, indexes, etc. A key difference to relational schemas is that ObjectBox manages its meta model - mostly - automatically. In some cases it needs your help. That’s why we will look at some details.

IDs

In the ObjectBox meta model, everything has an ID and an UID. IDs are used internally in ObjectBox to reference entities, properties, and indexes. For example, you have an entity “User” with the properties “id” and “name”. In the meta model the entity (type) could have the ID 42, and the properties the IDs 1 and 2. Property IDs must only be unique within their entity.

Note: do not confuse object IDs with meta model IDs: object IDs are the values of th Id orEntityId<MyEntity> property (see Object IDs in basics). In contrast, all objects are instances of the entity type associated with a single meta model ID.

ObjectBox assigns meta model IDs sequentially (1, 2, 3, 4, …) and keeps track of the last used ID to prevent ID collisions.

UIDs

As a rule of thumb, for each meta model ID there’s a corresponding UID. They complement IDs and are often used in combination (e.g. in the JSON file). While IDs are assigned sequentially, UIDs are a random long value. The job of UIDs is detecting and resolving concurrent modifications of the meta model.

A UID is unique across entities, properties, indexes, etc. Thus unlike IDs, a UID already used for an entity may not be used for a property. As a precaution to avoid side effects, ObjectBox keeps track of “retired” UIDs to ensure previously used but now abandoned UIDs are not used for new artifacts.

JSON for consistent IDs

ObjectBox stores a part of its meta model in a JSON file. This file should be available to every developer and thus checked into a source version control system (e.g. git). The main purpose of this JSON file is to ensure consistent IDs and UIDs in the meta model across devices.

This JSON file is stored in the file model-TargetName.json. For example, look at the file from the ObjectBox example project:

{
  "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
  "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
  "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
  "entities": [
    {
      "id": "1:712683617673955584",
      "lastPropertyId": "2:5025387500910526208",
      "name": "Author",
      "properties": [
        {
          "flags": 1,
          "id": "1:6336800942024279296",
          "name": "id",
          "type": 6
        },
        {
          "id": "2:5025387500910526208",
          "name": "name",
          "type": 9
        }
      ],
      "relations": []
    },
    {
      "id": "2:5608901830082711040",
      "lastPropertyId": "6:6001769173142034944",
      "name": "Note",
      "properties": [
        {
          "flags": 1,
          "id": "1:7180411752564202752",
          "name": "id",
          "type": 6
        },
        {
          "id": "2:249105953415333376",
          "name": "title",
          "type": 9
        },
        {
          "id": "3:5661281725891017216",
          "name": "text",
          "type": 9
        },
        {
          "id": "4:8342334437465755392",
          "name": "creationDate",
          "type": 10
        },
        {
          "id": "5:8881960381068888832",
          "name": "modificationDate",
          "type": 10
        },
        {
          "flags": 520,
          "id": "6:6001769173142034944",
          "indexId": "1:6069708401898380544",
          "name": "author",
          "relationTarget": "Author",
          "type": 11
        }
      ],
      "relations": []
    }
  ],
  "lastEntityId": "2:5608901830082711040",
  "lastIndexId": "1:6069708401898380544",
  "lastRelationId": "0:0",
  "lastSequenceId": "0:0",
  "modelVersion": 5,
  "modelVersionParserMinimum": 4,
  "retiredEntityUids": [],
  "retiredIndexUids": [],
  "retiredPropertyUids": [],
  "retiredRelationUids": [],
  "version": 1
}

As you can see, the id attributes combine the ID and UID using a colon. This protects against faulty merges. When applying the meta model to the database, ObjectBox will check for consistent IDs and UIDs.

Meta Model Synchronization

At build time, ObjectBox gathers meta model information from the entities (classes that conform to the Entity protocol or are annotated objectbox: entity) and the JSON file. The complete meta model information is written into the generated/EntityInfo-TargetName.generated.swift file.

Then, at runtime, the meta model assembled in EntityInfo-TargetName.generated.swift is synchronized with the meta model inside the ObjectBox database (file). UIDs are the primary keys to synchronize the meta model with the database. The synchronization involves a couple of consistency checks that may fail when you try to apply illegal meta data.

Stable Renames using UIDs

At some point you may want to rename an entity class or just a property. Without further information, ObjectBox will remove the old entity/property and add a new one with the new name. This is actually a valid scenario by itself: removing one property and adding another. To tell ObjectBox it should do a rename instead, you need to supply the property's previous UID.

Add an // objectbox: uid annotation without any value to the entity or property you want to rename and trigger a project build. The build will fail with a message containing the UID you need to apply to the // objectbox: uid annotation.

Also check out this how-to guide for hands-on information on renaming and resetting.

Resolving Meta Model Conflicts

In the section on UIDs, we already hinted at the possibility of meta model conflicts. This is caused by developers changing the meta model concurrently, typically by adding entities or properties. The knowledge acquired in the previous paragraphs helps us to resolve the conflicts.

The Nuke Option

During initial development, it may be an option to just delete the meta model and all databases. This will cause a fresh start for the meta model, e.g. all UIDs will be regenerated. Follow these steps:

  • Delete the JSON file for the given target(s) (model-TargetName.json)

  • Build the project to generate a new JSON file from scratch

  • Commit the recreated JSON file to your VCS (e.g. git)

  • Delete all previously created ObjectBox databases (e.g. for iOS, delete the app from your home screen, or delete the file from your application's container in the simulator or on macOS)

While this is a simple approach, it has its obvious disadvantages and is not an option once an app has been published.

Manual conflict resolution

Usually, it is preferred to edit the JSON file to resolve conflicts and fix the meta model. This involves the following steps:

  • Ensure IDs are unique: in the JSON file the id attribute has values in the format “ID:UID”. If you have duplicate IDs after a VCS merge, you should assign a new ID (keep the UID part!) to one of the two. Typically, the new ID would be “last used ID + 1”.

  • Update last ID values: for entities, update the attribute “lastEntityId”; for properties, update the attribute “lastPropertyId” of the enclosing entity

  • Check for other ID references: do a text search for the UID and check if the ID part is correct for all UID occurrences

To illustrate this with an example, let's assume the last assigned entity ID was 41. Thus the next entity ID will be 42. Now, the developers Alice and Bob add a new entity without knowing of each other.

Alice adds a new entity “Ant” which is assigned the entity ID 42.

At the same time, Bob adds the entity “Bear” which is also assigned the ID 42.

After both developers committed their code, the ID 42 does not uniquely identify an entity type (“Ant” or “Bear”?). Furthermore, in Alice’s ObjectBox the entity ID 42 is already wired to “Ant” while Bob’s ObjectBox maps 42 to “Bear”.

UIDs make this situation resolvable. Let’s say the UID is 12345 for “Ant” and 9876 for “Bear”. Now, when Bob pulls Alice’s changes, he is able to resolve the conflict: He manually assigns the entity ID 43 to “Bear” and updates the lastEntityId attribute accordingly to “43:9876” (ID:UID). After Bob commits his changes, both developers are able to continue with their ObjectBox files.

Data Model Updates

The ObjectBox swift database manages the data model mostly automatically for you. Enjoy the ease of automatic schema migrations and learn here what is left to take care of.

ObjectBox - Data Model Updates

ObjectBox manages its data model (schema) mostly automatically. The data model is defined by the entity classes you define. When you add or remove entities or properties of your entities, ObjectBox takes care of those changes without any further action from you.

For other changes like renaming or changing the type, ObjectBox needs extra information to make things unambiguous. This works using unique IDs (UIDs) and an // objectbox: uid annotation, as we will see below.

UIDs

ObjectBox keeps track of entities and properties by assigning them unique IDs (UIDs). All those UIDs are stored in a file “model.json”, which you should add to your version control system (e.g. git). If you are interested, we have in-depth documentation on UIDs and concepts. But let’s continue with how to rename entities or properties.

In short: To make UID-related changes, put an // objectbox: uid annotation on the entity or property and build the project to get further instructions.

Renaming Entities and Properties

So why do we need that UID annotation? If you simply rename an entity class, ObjectBox only sees that the old entity is gone and a new entity is available. This can be interpreted in two ways:

  • The old entity is removed and a new entity should be added, the old data is discarded. This is the default behavior of ObjectBox.

  • The entity was renamed, the old data should be re-used.

So to tell ObjectBox to do a rename instead of discarding your old entity and data, you need to make sure it knows that this is the same entity and not a new one. You do that by attaching the internal UID to the entity.

The same is true for properties.

Now let’s walk through how to do that. The process works the same if you want to rename a property:

How-to and Example

Step 1: Add an empty // objectbox: uid annotation to the entity/property you want to rename:

// objectbox: entity
// objectbox: uid
class MyName { ... }

Step 2: Build the project. The build will fail with an error message that gives you the current UID of the entity/property:

error: No UID given for entity MyName. You can do the following:
	[Rename] Apply the current UID using // objectbox: uid = 17664
	[Change/Reset] Apply a new UID using // objectbox: uid = 18688

Step 3: Apply the UID from the [Rename] section of the error message to your entity/property:

// objectbox: entity
// objectbox: uid = 17664
class MyName { ... }

Step 4: The last thing to do is the actual rename on the language level (Java, Kotlin, etc.):

// objectbox: entity
// objectbox: uid = 17664
class MyNewName { ... }

You can now use your renamed entity/property as expected and all existing data will still be there.

Note: Instead of the above you can also find the UID of the entity/property in the ObjectBox model.json file yourself and add it together with the // objectbox: uid annotation before renaming your entity/property.

Changing Property Types

When you want to change the type of a property, you must tell ObjectBox to create a new property internally. This is because ObjectBox cannot migrate your data, so simply changing the type may lead to data loss. You can do this in two ways:

  • Just rename the property, so it is treated as a new property (this only works if the property has no // objectbox: uid annotation already):

// old:
var year: String
// new:
var yearInt: Int
  • Tell ObjectBox to use a new UID for the property. Let’s walk through how to do that:

How-to and Example

Step 1: Add the // objectbox: uid annotation to the property where you want to change the type:

// objectbox: uid
var year: String

Step 2: Build the project. The build will fail with an error message that gives you a newly created UID value:

error: No UID given for property year of entity MyName. You can do the following:
	[Rename] Apply the current UID using // objectbox: uid = 15616
	[Change/Reset] Apply a new UID using // objectbox: uid = 18688

Step 3: Apply the UID from the [Change/Reset] section to your property:

// objectbox: uid = 18688
var year: Int

You can now use the property in your entity as if it were a new one.

Anonymous Feedback Form
issue on GitHub
GitHub releases
ObjectBox C API 0.21.0
ObjectBox C API 0.19.0
objectbox-dart#318
objectbox-dart#546
C API
C API
announcement post covering object relations
statistics
Readme on Github
structs can now be used
used without CocoaPods
used in frameworks
migrated transparently for you
indexed
unique
different name for use in the model
ObjectBox without CocoaPods
see macOS Sandbox setup
Install ObjectBox Swift
Get Started with ObjectBox Swift

Get Started with ObjectBox Swift

Learn how to use ObjectBox NoSQL DB to persist data with swift for an Offline-first iOS app experience. ObjectBox performs well on all CRUD operations and is fully ACID compliant.

Add ObjectBox to your project

See if you haven't already. This guide assumes ObjectBox was added using CocoaPods.

Define Entity Classes

ObjectBox uses code generation to read and write your objects instead of forcing you to inherit from a base object. To mark an object as an entity, you have two options:

  • Conform to the protocol or

  • add // objectbox: entity before the type as an annotation for the code generator.

Then supply an ID property and parameter-less initializer init(), and you're good to go:

That is all you need to get going.

You initialize an Id with 0 for all entities. This tells ObjectBox your object isn't persisted, yet. When you persist objects, the ID will be changed to the actual value. And once an entity has a non-zero ID, persisting changes will update the existing record instead of creating a new one.

So the ID is managed by ObjectBox. Conversely, you do not want to modify it yourself.

Generate ObjectBox code

Next, some binding code needs to be generated based on the model defined in the previous step. This step is different, depending on if a CocoaPods or Swift Package setup is used.

CocoaPods

Build your project to generate the classes and Store initializer required to use ObjectBox, for example using Product > Build in Xcode.

Next, .

Swift Package

The Swift Package integrates ObjectBox generator as a command plugin.

The generator requires write permissions in your project directory to generate the model file and Swift binding code for your entities.

To run the generator from Xcode

  1. right-click your project in the Project navigator and click ObjectBoxGeneratorCommand,

  2. select the target that contains your ObjectBox entity classes,

  3. when asked, allow the command to change files.

  4. Once the command has finished, add the generated source file (generated/EntityInfo-<target-name>.generated.swift) to your project.

To run the generator from the command line

Use this command:

Review and keep generated files

Among other files ObjectBox generates a JSON model file, by default to model-<target-name>.json where <target-name> is the name of an Xcode target, e.g. NotesExample-iOS.

The JSON file is not visible by default in the Xcode Project navigator, you have to add it to the project or view it in Finder.

This JSON file changes when you change your entity classes (or sometimes with a new version of ObjectBox).

Keep this JSON file, commit the changes to version control!

This file keeps track of unique IDs assigned to your entities and properties. This ensures that an older version of your database can be smoothly upgraded if your entities or properties change.

The model file also enables you to keep data or to when two of your developers make changes at the same time.

Create a Store

To create or access a database on disk, use the ObjectBox.Store class. The Store behaves much like a database connection: you keep the instance around to maintain an open connection to the data in a folder on disk. Usually for the lifetime of your app.

Getting "Missing argument for parameter 'model'..." here? Make sure to first. Background: The ObjectBox code generator creates a second, convenience Store() initializer without the model parameter.

Of course, you would usually save your database in one of the standard system directories, like .applicationSupportDirectory, .documentsDirectory or .cachesDirectory:

Basic Box operations

Since ObjectBox is all about sticking objects in boxes, you interact with objects using aBoxinterface. For each object class, there is a matching Box instance.

To manage your entities, you retrieve the ObjectBox.Box<T> instance for its class from yourStore. Boxes are lightweight and can be discarded, but then you have to pass the Store around. You will almost always prefer to pass long-lived Box instances around instead, for example in segues between UIViewControllers.

Wherever you have access to a Box, you can use it to persist objects and fetch objects from disk. Boxes are thread safe. Here are some of the basic operations:

  • put: Persist an object, which may overwrite an existing object with the same ID. In other words, use putto insert or update objects. When put succeeds, an ID will be assigned to the entity. There are put variants that support writing multiple objects at once, which is more efficient than writing each with its own call to put.

  • get: When you have an object's ID, you can get to the object very efficiently using get. To get all objects of a type, use all.

  • remove: Deletes a previously persisted object from its box. There are method overloads to remove multiple entities, and removeAll to delete all objects and empty the box.

  • count: The number of objects stored in this box.

  • query: Lets you provide a query expression to retrieve objects by certain criteria. See for details.

Check for a list of operations.

Put and Get Example

Once you have a box, it is simple to persist an entity. To illustrate the change of IDs, we added assertions to the code; you don't need these, of course.

Put with structs

Structs basically work the same way as classes. However, since structs are value types, ObjectBox can only adjust their ID if you pass them by reference. So if you have a mutable struct, remember to pass it by reference:

Alternately, you can manually update the struct's ID field by using putAndReturnID() or putAndReturnIDs() to get the ID a struct was written as:

Given immutable structs are such a common occurrence, ObjectBox generates a convenience method for you to help with updating the ID on an immutable struct, put(struct:), which will automatically create a new copy of your struct with the ID filled out:

Note that, after writing exampleEntity, you must use newEntity from then on. If you called put(struct:) or put() on exampleEntity again, ObjectBox would not know that this object has already been saved, and would write a second copy of it to the database.

But what if you know you already saved an immutable entity, and you already made a copy because you changed one of its fields, and don't need another copy? Then you can call put(), just like above:

So beyond having to use a special call the first time you write out an entity, everything is the same as with classes.

For ObjectBox to work with structs, there needs to be an init() method on your struct that accepts all its persisted properties. In the above example, the default initializer provided for the struct by Swift does just fine, but if you have properties that are not persisted, or you are defining your own initializer, you may also have to provide that initializer.

Note that for structs you will usually not want to specify a default value of 0 for the ID like you do for classes. If you do, the default initializer will omit the "id:" parameter and there would be no way to initialize the ID without writing your own initializer.

Transactions

Transactions in ObjectBox let you group together several operations and ensure that either all of them complete, or none does, always leaving your data relations in a consistent state. If you do not run your own transaction, ObjectBox will implicitly create one for every call:

  • A put runs an implicit transaction.

  • Prefer the put variant that takes an array of entities (like put([person1, person2])) whenever possible to increase performance.

For a high number of interactions inside loops, consider wrapping the loop in explicit transactions. Store exposes methods like runInTransaction for this purpose.

For details, check the and for details.

import ObjectBox

// objectbox: entity
class Person {
    var id: Id = 0
    var firstName: String = ""
    var lastName: String = ""
    
    init() {} // Used by ObjectBox
    
    init(id: Id = 0, firstName: String, lastName: String) {
        self.id = id
        self.firstName = firstName
        self.lastName = lastName
    }
}
swift package plugin --allow-writing-to-package-directory objectbox-generator
let store = try Store(directoryPath: "/Users/jenna/Documents/mydatabase/")
let databaseName = "notes"
let appSupport = try FileManager.default.url(for: .applicationSupportDirectory,
                                             in: .userDomainMask,
                                             appropriateFor: nil,
                                             create: true)
                                             .appendingPathComponent(Bundle.main.bundleIdentifier!)
let directory = appSupport.appendingPathComponent(databaseName)
try? FileManager.default.createDirectory(at: directory,
                                         withIntermediateDirectories: true,
                                         attributes: nil)

let store = try Store(directoryPath: directory.path)
let exampleEntityBox = store.box(for: ExampleEntity.self)
// Similarly:
let personBox = store.box(for: Person.self)
let noteBox: Box<Note> = store.box()
// let's start with an empty box
assert(try exampleEntityBox.isEmpty())

let exampleEntity = ExampleEntity()
assert(exampleEntity.id.value == 0)

let newID = try exampleEntityBox.put(exampleEntity)
// Change of ID with the `put`:
assert(exampleEntity.id.value != 0)
assert(exampleEntity.id == newID)

// Check the Box contents did indeed change:
assert(try exampleEntityBox.count() == 1)
assert(try exampleEntityBox.all().first?.id == newID)

// Getting to a specific object
guard let foundEntity = try exampleEntityBox.get(newID) 
    else { fatalError("Object should be in the box") }
assert(exampleEntity.id == foundEntity.id)

// Cleaning up
try exampleEntityBox.removeAll()
assert(try exampleEntityBox.count() == 0)
// objectbox: entity
struct Author {
    var id: Id // Do not initialize the ID to 0 for structs.
    var name: String
    var age: Int
}

let exampleEntityBox = store.box(for: Author.self)
var exampleEntity = Author(id: 0, name: "Nnedi Okorafor", age: 45)

// Put the struct and update the ID:
try exampleEntityBox.put(&exampleEntity)
assert(exampleEntity.id != 0)
exampleEntity.id = exampleEntityBox.putAndReturnID(exampleEntity)
// objectbox: entity
struct Author {
    let id: Id // Do not initialize the ID to 0 for structs.
    let name: String
    let age: Int
}

// Let's start with an empty box:
let exampleEntityBox = store.box(for: Author.self)
let exampleEntity = Author(id: 0, name: "Nnedi Okorafor", age: 45)

// Put the immutable struct:
let newEntity = try exampleEntityBox.put(struct: exampleEntity)
assert(newEntity.id != 0)
// Modify object:
let futureExampleEntity = Author(id: newEntity.id, name: newEntity:name,
                                        age: newEntity.age + 1)

// Write out changes:
try exampleEntityBox.put(futureExampleEntity)
// Bad/slow, each Put runs inside a new transaction
for i in (1...1000) {
    try box.put(AnEntity(number: i))
}

// Possible, but still inefficient
try store.runInTransaction {
    for i in (1...1000) {
        try box.put(AnEntity(number: i))
    }
}

// Much better
let allEntities: [AnEntity] = (1...1000).map(AnEntity.init(number:))
try box.put(allEntities)
Install ObjectBox
Entity
review and keep the generated files
when renaming entities or properties
resolve conflicts
generate ObjectBox code
the page on queries
the API docs for Box
Transaction guide
the API docs for Store

Entity Annotations in ObjectBox

Persisting objects with Entity Annotations in your iOS Swift application is easy with ObjectBox.

Database Persistence with Entity Annotations

ObjectBox DB persists Swift objects. For a clear distinction, we sometimes call those persistable objects "entities". To let ObjectBox know which classes are entities you can either make them implement the ObjectBox.Entity protocol, or add annotations to them. Then ObjectBox can take care of your entities.

Here is an example:

// objectbox: entity
class User {
    var id: Id = 0

    var name: String

    // objectbox: transient
    private var tempUsageCount: Int // not persisted

    // ...
}

The Entity annotation identifies the Swift class User as a persistable entity. This will trigger ObjectBox to generate persistence code tailored for this class, even if it does not conform to the Entity protocol..

Note: It’s often good practice to model entities as “dumb” data classes with just properties.

Object IDs: id

In ObjectBox, every object has an ID of type Id to efficiently get or reference objects. Usually ObjectBox will just find that property in your entity based on its type and "do the right thing", but if you have several properties of the same Id type, or your ID uses the types UInt64 or Int64, you can use an annotation to mark a particular property as the ID of your entity:

class User: Entity {

    // objectbox: id
    var thisIsMyId: UInt64 = 0

    // ...
}

While we recommend to define your ID as type Id or EntityId<MyEntity>(where MyEntity would be replaced with the name of your class), you can also annotate a property of type UInt64 or Int64 as an ID.

ObjectBox will usually manage ID values for you, but should you absolutely need to, you can tell ObjectBox that you want to assign IDs manually by yourself by adding an "assignable" parameter to the id annotation:

// objectbox: id = { "assignable": true }

If you do that, make sure that you assign a unique ID to each new object. If you assign the same ID to two objects of the same class, writing one to the database will overwrite the other. In general, it is best to let ObjectBox assign IDs. You are free to provide an additional indexed property to e.g. store a GUID for an object in addition to the ID used by ObjectBox, and to use queries to retrieve objects based on that GUID.

Ways to Define IDs

To specify Entity IDs, you need to have a property of type Id . The code generator will look for one of these configurations, in the following order

  1. A property that has a // objectbox: id comment annotation on the line above the property definition.

  2. A property named var id: Id.

  3. Any other property of the Id type, if there is only one.

You usually don't need more than one property of the Id type. Use ObjectBox's support for object relations if you need to connect entities with each other. In any case, should you need to store a reference to another object for an unusual purpose, simply use annotations to ensure ObjectBox uses the right property as the entity's ID.

Here's an example of using annotations in action:

// objectbox: entity
class ExampleEntity {
    var anotherID: Id = 0

    // objectbox: id
    var theEntityIdentifier: Id = 0 // <- this will be used
    
    required init() {
        // nothing to do here
    }
}

ObjectBox supports several types for IDs. Apart from Id, you can also use the more type-safe EntityId<ExampleEntity> struct (where ExampleEntity is the class of your entity), which will let you ensure that you don't accidentally pass an ID of the wrong type into Box API.

In addition, you can also use UInt64 or Int64 for your IDs, but for ObjectBox to know to use those, you must annotate them.

Using Different Names in the Database than in Swift

class User: Entity {
    // objectbox: name = "USERNAME"
    var name: String
    ...
}

The name annotation lets you define a name on the database level for a property. This allows you to rename the Swift field without affecting the property name on the database level. This is mostly useful in cross-platform code, where different platforms may have different conventions for property names and you need to exchange database files between them.

Note: To rename properties and even entities you should use Uid annotations instead.

Transient Properties

class User: Entity {
    // objectbox: transient
    var tempUsageCount: Int
    ...
}

The // objectbox: transient annotation can be used to mark properties that should not be persisted, like the temporary counter above. As far as ObjectBox is concerned, transient properties simply do not exist. static properties are always ignored by ObjectBox and do not need to be marked as transient.

Property Indexes

Annotate a property with // objectbox: index to create a database index for the corresponding database column. This can greatly improve performance when querying for that property.

// objectbox: index
var name: String

Index is currently not supported for Data, Float and Double

An index stores additional information in the database to make lookups "faster", or more correctly, "more scalable". With an index, you will get results fast no matter if you store ten, one thousand, or one million objects in the database. Index based database lookups perform in O(log n).

As an analogy we could look at how you store objects in an Array<>. For example you could store persons using an Array<Person>. Now, you want to search for all persons with a specific name so you would iterate through the list and check for the name property of each object. This is an O(N) operation and thus does not scale well with an increasing number of objects.

To make this more scalable you can introduce a second data structure Dictionary<String, Person> with the name as a key. This will give you a superfast lookup time (typically O(1)). The downside of this is that it needs more resources (here: RAM) and slows down add/remove operations on the list a bit. These principles can be transferred to database indexes, just that the primary resource consumed is disk space.

Index types (String)

Because String properties typically take more space than scalar values, ObjectBox uses a hash for indexing strings by default. For any other type, the property value is used for all index look-ups.

You can instruct ObjectBox to use a value-based index for a String property by specifying an index type:

// objectbox: index = value 
var name: String

Keep in mind that for String, depending on the length of your values, a value-based index may require more storage space than the default hash-based index.

ObjectBox supports these index types:

  • Not specified Uses best index based on property type (hash for String, value for others).

  • value Uses property values to build index. For String, this may require more storage than a hash-based index.

  • hash Uses 32-bit hashes of property values to build the index. Occasional collisions may occur which should not have any performance impact in practice. Usually a better choice than hash64, as it requires less storage.

  • hash64 Uses 64-bit hashes of property values to build the index. Requires more storage than hash and thus should not be the first choice in most cases.

Limits of hash-based indexes: Hashes work great for equality checks, but not for "starts with" type conditions. If you frequently use those, you should use value-based indexes instead.

Vector Index for Nearest Neighbor Search

To enable nearest neighbor search, a special index type for vector properties is available:

Unique constraints

Annotate a property with unique to enforce that values are unique before an entity is put:

// objectbox: unique
var name: String

A put() operation will abort and throw an ObjectBoxError.uniqueViolation error:

do {
    try box.put(User("Sam Flynn"))
} catch ObjectBoxError.uniqueViolation(let message) {
    // A user with that name already exists.
}

Unique constraints are based on an index. You can further configure the index by adding an index annotation.

Converting Enums and Custom Types

You can add an // objectbox: convert annotation to properties with types that ObjectBox does not know to allow converting it into a recognized type. See Enums and Custom Types for more.

Relations

Creating to-one and to-many relations between objects is possible as well, and may require use of the // objectbox: backlink annotation, see the Relations documentation for details.

Triggering generation

You usually should not need to do anything special to trigger code generation once your entity classes are properly annotated. The setup.rb script should have automatically configured your project to run the code generator when you compile your project, for example using Product > Build in Xcode.

Should you have unusual needs or encountering issues, see Customizing Code Generation for a description of how things usually work.

Queries

The ObjectBox Swift database supports building Queries using a query builder. Learn all about Query syntax and how to get to entities and their property values by filtering for specific criteria.

Building Queries that Return Entities

Beyond simple "fetch all" commands, like personBox.all(), you may want to filter entities by their properties. ObjectBox's query syntax should be familiar to every Swift developer, because queries are written in Swift:

let query: Query<Person> = try personBox.query {
    Person.name.startsWith("Andrea")
}.build()
let allAndreas: [Person] = try query.find()

You simply write your class name, the property name, and then an operator, or a method name (like startsWith() here). While most of these calls look like the regular Swift calls you're used to, they are actually provided by ObjectBox's code generator, so not all methods you would e.g. find on a String are available for queries. See below for available operations.

Store query objects for re-use to increase performance.

You can call Query.find(), Query.count(), and all the other operations that execute a query multiple times. In performance-critical situations, building a new query object thousands of times can easily become costly.

Query Syntax

Combine Conditions with logical Operators and Parentheses

Query conditions can be combined by the use of the logical operators && and ||.

let query: Query<Person> = try personBox.query {
    Person.firstName.startsWith("Ste") 
        && Person.lastName.startsWith("Jo")
}.build()
let maybeSteveJobs = try query.find()

Since these are overloads of native Swift operators, their standard operator precedence rules apply (&& > ||). You can use parentheses to group conditions and affect the truth condition of the whole expression.

That's fancy talk for: group condition parts by wrapping them in parens.

let query: Query<Person> = try personBox.query {
    (Person.firstName.contains("Santa") || Person.age > 100)
        && Person.lastName.isEqual(to: "Claus", caseSensitive: true) 
}.build()
let oldClauses = try query.find()

QueryCondition Operators

ObjectBox also provides operators for the most common operations. Please note that the property comes first:

  • Collection containment: ∈and ∉ ; for example, disallowing teens entry to your disco: Person.age ∉ (10..<18)

  • Equality: == and !=, as in Person.firstName == "Steve"

  • Comparison: < and >, as in CocoaPod.rating > 4.5

Those operators are available for optional types, which currently lack the long form.

There are no custom operators for conditions like Property<T, String>.startsWith(), as their names are already much more familiar to Swift developers.

Query Operations

Once you have a query set up properly, you can execute it as often as you want to get the latest results of its evaluation. These are the most common operations:

  • Query.find() will return all results that match

  • Query.count() will return the count of all results that match

  • Query.findUnique() will return an unique match from the result set; returns nil if no result was found and throws if the result is not unique

Please refer to the Query API docs for a comprehensive list of permitted operations.

Sorting

In addition to specifying conditions you can order the returned results using the ordered() method:

let query = try userBox.query { User.firstName == "Joe" }
                .ordered(by: User.lastName) // in ascending order, ignoring case
                .build()

You can also pass flags to ordered(by:,flags:) to sort in descending order, to sort case sensitively or to treat null values specially . For example, to sort the above results in descending order and case sensitively instead:

let query = try userBox.query { User.firstName == "Joe" }
                .ordered(by: User.lastName, flags: [.descending, .caseSensitive])
                .build()

Order conditions can also be chained. Check the method documentation for details.

Modifying Conditions Later

You can modify conditions after creation of the query. That can be useful if your base query stays the same but one small part changes between find() executions and you don't want to create a new Query object every time.

To do that, you use the query's setParameter() methods. Refer to the Query API docs for a comprenhesive list of permitted setParameter() variants.

setParameter Changes the Condition Value

There are many useful variants of the Query.setParameter() and Query.setParameters() method. (Note the plural s.) The first version will set the comparison value of a condition that matches to a new value. The second version does the same for comparisons with two values, hence the plural s.

For example:

  • query.setParameter(Person.age, to: 18) for a query you built using personBox.query { Person.age == 21 }.build() will effectively change the condition to Person.age == 18

  • query.setParameters(Person.age, to: 10, and: 18) for a query you built using personBox.query { Person.age.isBetween(0, and: 99) }.build() will effectively change the condition to Person.age.isBetween(10, and: 18)

You will get fatal errors if you try to call the plural-s-Version when the original condition only has one comparison value, and vice versa. Also note that this is an usage error and thus does not throw.

Use PropertyAlias to Disambiguate Conditions

One problem of the property-based approach above is that you have no control what happens when there are two or more conditions on the same property:

let query = try personBox.query {
    Person.age > 10 
        && Person.age < 50
}.build()
query.setParameter(Person.age, to: 25) // did this affect "<" or ">" ?

To resolve this ambiguity, you can register a short name (called an alias) for a query condition. That's a simple string to give the condition a name.

We use a variant of the definition operator (.=) for this:

let query = try personBox.query {
    "MinAge" .= Person.age > 10 
        && "MaxAge" .= Person.age < 50
}.build()
query.setParameter("MinAge", to: 25)

There is, of course, also a plural s-variant for conditions with two values. The same warning as above applies: make sure not to mix up the plural and singular versions.

A downside of the string-based alias approach is that you lose type information: you have to make sure not to pass a Double to a setter for a condition that operates on String, for example.

Building Queries that Operate on Properties

In addition to filtering entities, you can also build queries for their properties. These are aggregate functions like max, min, average, count, but also various find methods. These are available on the PropertyQuery type, that you can get from a regular query like this:

let query: Query<Person> = try personBox.query { Person.firstName.startsWith("S") }.build()
let agePropertyQuery: PropertyQuery<Person, Int> = query.property(Person.age)

The PropertyQuery will respect all conditions of its original Query. So in the example above, only entities with a firstName that starts with "S" will be regarded. Use the empty block variant personBox.query() if you want to operate on all entities.

Please refer to the PropertyQuery API docs for an exhaustive list of operations.

Currently, property queries do not honor the ordered(by:,flags:) setting. If you need a fixed order for your proerty query results, you must sort them manually after retrieving them.

Aggregate Functions

A simple aggregate function is to request the maximum, minimum, or average value of a given property.

let query: Query<Person> = try personBox.query().build()
let agePropertyQuery: PropertyQuery<Person, Int> = query.property(Person.age)
let maxAge: Int = try agePropertyQuery.max()
let minAge: Int = try agePropertyQuery.min()
let averageAge: Double = try agePropertyQuery.average()

Number-based operations are only available for number-based properties and not for strings, for example. Also note that the average of a property is always a floating point number, even if the property is an Int (what would you do with a non-fractional average anyway?).

Fetching all Property Values

In addition to aggregate functions, you can also use "find" methods. These will return any or all of the values of entities matching the query:

let query: Query<Person> = try personBox.query { Person.firstName == "Steve" }.build()
let steveLastNamePQ: PropertyQuery<Person, String> = query.property(Person.lastName)
let allOfStevesLastNames: [String] = try steveLastNamePQ.findStrings()
// if allOfStevesLastNames.contains("Jobs") { ... }

While the possibilities are not literally endless, they are plentiful if you combine query conditions with property-based requests.

Distinct and Unique Results

The property query can also only return distinct values, not producing duplicates. That means if you have 5 people in your database with ages [25, 30, 30, 40, 40], you will get [25, 30, 40] as a distinct result set.

For example:

let names: [String] = try personBox.query().build()
    .property(Person.firstName)
    .distinct(caseSensitiveCompare: false)
    .findStrings()

If only a single value is expected to be returned the query can be configured to throw if that is not the case:

do {
    let singleNameResult: String = try personBox.query { Person.age > 999 }.build()
        .property(Person.firstName)
        .findUniqueString()
} catch ObjectBoxError.uniqueViolation(let message) {
    print("Found more than one item! \(message)")
}
guard let singleNameResult = singleNameResult else {
    fatalError("No result found at all.")
}

You can combine distinct() with findUnique() .

Building Queries Traversing Relations

If your entity contains a relation to other entities, you can apply additional criteria to the related entities by using the link(_ property:, conditions:) method on your query.

E.g. if you had an entity Order that has a property ToOne<Order, Customer> pointing to the customer that placed the order, you could select all orders belonging to a particular customer from today's orders using

let sallysOrders = try orderBox.query{
        Order.date.isAfter(thisMorning)
    }.link(Order.customer) {
        Customer.name == "Sally Sparrow"
    }.build().find()

This is equivalent to what other databases call a join.

How do queries work?

Let's assume we have this interesting Entity:

class Person: Entity {
    var id: Id = 0
    var firstName: String
    var lastName: String
    var age: Int

    init(firstName: String, lastName: String, age: Int) {
        self.firstName = firstName
        self.lastName = lastName
        self.age = age
    }

    // ... 
}

For this entity, the code generator will create something like this:

extension Person {
    static var id: Property<Person, Id, Void> { /* ... */ }
    static var firstName: Property<Person, String, Void> { /* ... */ }
    static var lastName: Property<Person, String, Void> { /* ... */ }
    static var age: Property<Person, Int, Void> { /* ... */ }
}

That's your entity property metadata. The code generator will create these static properties for you with the same name as a stored object's properties. This is what you use for queries.

The associated types of the generic Property<Entity, Value, ReferencedType> itself encode so much interesting information already to make the query interface very intuitive, for example:

  • Property<E, String, R> exposes methods that make sense for strings, like contains

  • Property<E, Int, R> exposes methods that make sense for numbers, including comparisons

  • Property<E, Date, R> exposes methods for dates, like isBetween(_:and:)

All of these factory methods create a QueryCondition, the type used for queries, and a lot of them have intuitive operator variants. Please refer to the API docs for a full list of operators and methods.

Relations

Entity relations are declared using wrapper types. Learn how to form to-one and to-many relations with ObjectBox here.

Objects may reference other objects, for example using a simple reference or a list of objects. In database terms, we call those references relations. The object defining the relation we call the source object, the referenced object we call target object. So the relation has a direction.

If there is one target object, we call the relation to-one. And if there can be multiple target objects, we call it to-many. Relations are lazily initialized: the actual target objects are fetched from the database when they are first accessed. Once the target objects are fetched, they are cached for further accesses.

To-One Relations

To-One Relation

You define a to-one relation using the ToOne class, a smart proxy to the target object. It gets and caches the target object transparently.

For example, an order is typically made by one customer. Thus, we could model the Order class to have a to-one relation to the Customer like this:

class Customer: Entity {
    var id: Id = 0
    // ...
}

class Order: Entity {
    var id: Id = 0
    var customer: ToOne<Customer> = nil
    // ...
}

Given these entities and their to-one relation, you can create a relation and persist it:

let store = ...
// Illustrate that initially, nothing did exist
assert(try store.box(for: Customer.self).isEmpty())
assert(try store.box(for: Order.self).isEmpty())

let customer = Customer()
let order = Order()
order.customer.target = customer
let orderId = try store.box(for: Order.self).put(order) // puts order and customer

// Verify the `put` was called for the relation target as well
assert(try store.box(for: Customer.self).count() == 1)
assert(try store.box(for: Order.self).count() == 1)

You can persist whole trees of object relations at once: If the customer object does not yet exist in the database, the ToOne will put() it. If it already exists, the ToOne will only create the relation (but not put() it).

For a comprehensive documentation of its features, visit the ToOne API docs.

Removing Relations

You have to set the target of a relation to nil and persist the changed object(s) via Box.put()to remove a relation permanently. Which one you reset doesn't matter, though. You have these options:

anOrder.customer.target = nil
// ... or ...
anOrder.customer.targetId = nil
// ... are both equivalent to:
anOrder.customer = nil
// ... whis is a short version of:
anOrder.customer = ToOne<Customer>(target: nil)

Removing a relation never removes participating objects.

ToOne is a Lazy Relation Proxy

The target object of a relation is not eagerly loaded from the store; it is loaded lazily. Until you request the ToOne.target, it will not be read into main memory.

To-Many Relations

To define a to-many relation, you can use a property of type ToMany. Like the ToOne class, the ToMany class helps you to keep track of changes and to apply them to the database.

Note that to-many relations are resolved lazily on first access, and then cached in the source entity inside the ToMany object. So subsequent calls to any method, like the count of the ToMany, do not query the database, even if the relation was changed elsewhere. To avoid the cache and trigger a fresh reload from the database, call reset() on the ToMany.

There is a slight difference if you require a one-to-many (1:N) or many-to-many (N:M) relation. A 1:N relation is like the example above where a customer can have multiple orders, but an order is only associated with a single customer. An example for an N:M relation are students and teachers: students can have classes by several teachers but a teacher can also instruct several students.

One-to-Many Relations (1:N)

One-To-Many Relation

For every ToOne relation that you have, you can define a backlink. Backlinks are using the same relation information, but in the reverse direction. Thus, a backlink of a ToOne will result in a list of potentially multiple objects: all entities pointing to the same entity. Example: Two Order objects point to the same Customer using a ToOne. The backlink is a ToMany from the Customer referencing its two Order objects.

Let's extend the example from above to get the backlinks from Customer to Order:

class Customer: Entity {
    var id: Id = 0
    // objectbox: backlink = "customer"
    var orders: ToMany<Order> = nil
    // ...
}

class Order: Entity {
    var id: Id = 0
    var customer: ToOne<Customer> = nil
    // ...
}

Note you tell ObjectBox about your backlink using an // objectbox: backlink = "name" annotation (where name is the name of the ToOne property that makes up the other end of the relation).

Once the backlink is set up, you can traverse the relation in both directions:

// Store two new orders for a new customer
let customer = Customer()
let order1 = Order(customer: customer)
let order2 = Order(customer: customer)
try store.box(for: Order.self).put([order1, order2])

// ID of customer was also set by put()
assert(customer.id != 0)

// Backlink: customer has two orders
assert(try store.box(for: Customer.self).get(customer.id).orders.count() == 2)

In database terminology, you create a (bi-directional) one-to-many (1:N) relationship by defining a ToOne with a backlink ToMany.

Collection Nature of ToMany

ToMany conforms to Swift's RandomAccessCollection protocol. Thus, you can use it just like an array or similar collections and pass it around. And of course, you can create an Array if need be:

let orders = Array(customer.orders)

See also: ToMany API docs

Modifying One-to-Many Relations

Apart from being a RandomAccessCollection, a ToMany relation is also a RangeReplaceableCollection. That means you can use append(_:) etc. on it just like on an Array to modify it. We've also added a replace(_:) method as a convenience to replace all entities referenced by the relation.

Once you've performed all the modifications you want, call applyToDb() on the ToMany to actually cause them to be written to the database. Note that ToMany applies change tracking and thus only writes updated relations to the database.

When you change its contents, ToMany will simply set the ToOne relation in the removed entities to nil, and will make added entities' ToOne point at the object containing the ToMany backlink. Note that, starting from version 1.4, you can add new (not yet persisted) objects, which applyToDb() will put automatically :

let newOrder = Order(summary: "Shoes")
// try orderBox.put(newOrder) // ObjectBox Swift 1.4+ does not need this
aCustomer.orders.replace([newOrder, oldOrder])
try aCustomer.orders.applyToDb()

Also, modifying a ToMany backlink modifies the ToOne of the referenced entities (in this example, newOrder and oldOrder) and will put() those objects to actually write out the changed relation. To remove all references to an entity, you may pass an empty array to replace():

// You cannot set `aCustomer.orders = nil`, so:
aCustomer.orders.replace([])
try aCustomer.orders.applyToDb()

Removing a relation never removes the referenced objects from the database.

Many-to-Many (N:M)

Many-to-Many (N:M)

To define a many-to-many relation you simply add a property using the ToMany class. Assuming a students and teachers example, this is how a simple Student class that has a to-many relation to Teacher entities can look like:

class Teacher: Entity {
    var id: Id = 0
    
    ...
}

class Student: Entity {
    var id: Id = 0
    var ToMany<Teacher> teachers = nil
    
    ...
}

Adding the teachers of a student works exactly like with an array, or a one-to-many relation:

let teacher1 = Teacher()
let teacher2 = Teacher()

let student1 = Student()
let student2 = Student()

// try store.box(for: Teacher.self).put([teacher1, teacher2])
try store.box(for: Student.self).put([student1, student2])

student1.teachers.append(teacher1)
student1.teachers.append(teacher2)

student2.teachers.append(teacher2)

try student1.teachers.applyToDb()
try student2.teachers.applyToDb()

To get the teachers of a student we just access the list:

var student1 = try boxStore.box(for: Student.self).get(student1.id)
for (let teacher in student1.teachers) {
    ...
}

And if a student drops out of a class, we can remove a teacher:

student1.teachers.remove(at: 0)
try student1.teachers.applyToDb()

Removing a relation never removes the referenced objects from the database.

Access Many-To-Many in the reverse direction

Following the above example, you might want an easy way to find out what students a teacher has. Instead of having to perform a query, you can just add a to-many relation to the teacher and annotate it with the // objectbox: backlink annotation:

class Teacher: Entity {
    var id: Id = 0
    // objectbox: backlink = "teachers"
    var students: ToMany<Student> = nil
    ...
}

class Student: Entity {
    var id: Id = 0
    var teachers: ToMany<Teacher> = nil
    
    ...
}

This will tell ObjectBox that there is only one relation, teachers, and that students is just a reverse-lookup of this relation. In any other respect, a many-to-many backlink can be used just like its forward counterpart.

Relations in Queries

You can traverse relations in queries.

Example: Modelling Tree Relations

You can model a tree relation with a to-one and a to-many relation pointing to itself:

class TreeNode: Entity {
    var id: Id = 0
    
    var parent: ToOne<TreeNode> = nil
    
    // objectbox: backlink = "parent"
    var children: ToMany<TreeNode> = nil
}

The generated entity lets you navigate its parent and children:

let parent = entity.parent.target
let children = Array(entity.children)
one-to-many relations
On-Device Vector Search | ObjectBox Docs
Logo