Getting Started Using ObjectBox in Your Project

OT
Last updated 20 days ago

Learn how to use the ObjectBox Framework to persist and read objects.

To see ObjectBox in action, you need to perform

  1. Install ObjectBox if you haven't already.

  2. Define your Entities, i.e. the object types you want to persist;

  3. Include

Define your Entities

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:

  1. Simply conform to the Entity protocol, or

  2. add // sourcery: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:

import ObjectBox
class ExampleEntity: Entity {
var id: Id<ExampleEntity> = 0
required init() {
// nothing to do here, ObjectBox calls this
}
}

That is all you need to get going. Build your project and Sourcery will generate the boilerplate code to persist and read objects of this type.

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 mess with it yourself.

Ways to Define IDs

To specify Entity IDs, you need to have a property name of type Id<EntityName> , where "EntityName" is the name of your entity, of course. The code generator will look for one of these configurations, in the following order

  1. Use any property name and add the // sourcery:entityId comment annotation to the line above the property definition. That way, the code generator knows which property to pick.

  2. Let the code generator use the suggested default: var id: Id<EntityName>.

  3. Let the code generator use any other property it finds of the Id<EntityName> type.

Why would you want to have multiple properties of the Id<EntityName> type, you ask? Well, maybe because you want to store some kind of reference to another object of the same entity type but don't want to use our amazing support for object relations. In any case, we got you covered!

Here's an example using annotations in action:

// sourcery:Entity
class ExampleEntity {
var anotherID: Id<ExampleEntity> = 0
// sourcery:entityId
var theEntityIdentifier: Id<ExampleEntity> = 0 // <- this will be used
required init() {
// nothing to do here
}
}

Initialize a Store

You use ObjectBox.Store to define the location of the database on disk. The Store behaves much like a database connection: you keep the instance around for the lifetime of your app to maintain an open connection to the data on disk.

let store = Store(directoryPath: "/path/to/the/store")

When you have a store, you register your entities once. This will make the structure of your entities known to the store, similar to registering a database schema iteratively.

store.register(entity: ExampleEntity.self)
// And others, if you have any, e.g.:
store.register(entity: Person.self)
store.register(entity: Note.self)

Working with Object Boxes

Bet you wondered where our name comes from :)

Well, you interact with objects using aBoxinterface. For each object class, there is a matching Box instance.

From a Store, you vend ObjectBox.Box<T> instances to manage your entities. A Box<ExampleEntity> instance manages persistence of ExampleEntity objects for you. 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.

let exampleEntityBox: Box<ExampleEntity> = store.box(for: ExampleEntity.self)
// Similarly:
let personBox: Box<Person> = store.box(for: Person.self)
let noteBox: Box<Note> = store.box(for: Note.self)

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. The various put variants support putting multiple objects at once, which is more efficient.

  • 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 delet all objects and empty the box.

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

  • query: Returns a query builder so you can specify which objects you want to fetch. See the page on queries for details.

Check the API docs for Box for a list of operations.

Put and Get Example

With a Box prepared, it's simple to persist an entity. To illustrate the change of IDs, we added assertions to the code; you don't need these, of course!

// In the beginning, there was emptiness ...
assert(exampleEntityBox.all().isEmpty)
let exampleEntity = ExampleEntity()
assert(exampleEntity.id.value == 0)
let newID = 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(exampleEntityBox.count == 1)
assert(exampleEntityBox.all().first?.id == newID)
// Getting to a specific object
guard let foundEntity = exampleEntityBox.get(newID)
else { fatalError("Object should be in the box") }
assert(exampleEntity.id == foundEntity.id)
// Cleaning up
exampleEntityBox.removeAll()
assert(exampleEntityBox.count == 0)

Transactions

ObjectBox offers powerful transactions. In many cases, the default transaction handling is sufficient:

  • 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.

// Possible, but inefficient
store.runInTransaction {
for i in (1...1000) {
box.put(AnEntity(number: i))
}
}
// Much better
let allEntities: [AnEntity] = (1...1000).map(AnEntity.init(number:))
box.put(allEntities)

For details, check the Transaction guide and the API docs for Store for details.