ObjectBox Swift for iOS and macOS

We’re happy to share our first Swift version of the ObjectBox database for iOS and macOS! We want to give you a Swift “native” API without any ObjectiveC legacy shining through, which is why we decided to put Swift, with its unique language features, first. We really want your feedback on this to improve swiftly, for example on our query API (more details below):

let joes = box.query{ Person.name.contains("Joe") } 1 let joes = box . query { Person . name . contains ( "Joe" ) }

The ObjectBox Swift Approach

To enable your entity model to be lightweight Swift objects, ObjectBox does not force your entities to inherit from any base class. Accordingly, there’s also no requirement to inherit from NSObject (of course you still can; the choice is yours).

Instead of inheriting from a base class, ObjectBox uses code generation. This minimizes overhead and avoids expensive dynamic look-ups at run time. ObjectBox uses Sourcery to generate boilerplate code for you. Don’t worry: This is done in the background and invisible for you. The generated code is the glue between our library and your app code. Also it provides some meta properties you will see in action with queries below.

Code: Entities and Store Initialization

Let’s say you have ObjectBox installed (check also our GitHub repository) and you want to persist a Person entity. Once the ObjectBox code generator did its job, you have access to a convenience initializer to setup the object store:

class Person: Entity { var id: Id<Person> = 0 var firstName: String var lastName: String // ... } let store = Store(directoryPath: aUserPathInTheSandbox) 1 2 3 4 5 6 7 8 class Person : Entity { var id : Id < Person > = 0 var firstName : String var lastName : String // ... } let store = Store ( directoryPath : aUserPathInTheSandbox )

What we did here:

Made Person conform to the ObjectBox.Entity protocol to mark it as persistable (optional), add an entity ID property, id: Id<Person> , initialize an ObjectBox.Store

An instance of Store is meant to be kept alive during the lifetime of your app; just pass it along to where it is needed. The Store is also responsible for offering transaction management. Check the Store API docs for details.

ObjectBox is meant literally: you put objects in boxes, that’s all you worry about. So you grab an instance of Box for your entity type and use it to store and retrieve entities. Box instances can be kept and passed around (also among threads). Getting a Box is trivial:

let personBox: Box<Person> = store.box(for: Person.self) 1 let personBox : Box < Person > = store . box ( for : Person . self )

To create a new object and read it later, you use Box.put(_:) and Box.get(_:) :

let steve = Person(firstName: "Steve", lastName: "Jobs") let steveId: Id<Person> = personBox.put(steve) if let foundPerson = personBox.get(steveId) { assert(foundPerson.firstName == steve.firstName) } 1 2 3 4 5 6 let steve = Person ( firstName : "Steve" , lastName : "Jobs" ) let steveId : Id < Person > = personBox . put ( steve ) if let foundPerson = personBox . get ( steveId ) { assert ( foundPerson . firstName == steve . firstName ) }

To update and persist changes to an object, simply put the object into the box again:

if let person = personBox.get(steveId) { person.firstName = "Steven" personBox.put(person) } 1 2 3 4 if let person = personBox . get ( steveId ) { person . firstName = "Steven" personBox . put ( person ) }

Why does that not create a new record, you ask? – Because when Box.get(_:) returns an entity, it has its id set. This makes Box perform an update for the entity.

Finally, you can delete entities by calling Box.remove() :

personBox.remove(steve) // or: personBox.remove(steveId) assert(personBox.get(steveId) == nil) 1 2 personBox . remove ( steve ) // or: personBox.remove(steveId) assert ( personBox . get ( steveId ) == nil )

That’s all for the basics. The Box API offers method overloads to put and remove collections of entities, too, among other niceties. Explore our sample apps to see things in action.

Queries: Getting Data

Beyond simple “fetch all” commands, you may want to filter entities by their properties. ObjectBox offers a very Swifty query API:

let query: Query<Person> = personBox.query { Person.firstName.startsWith("Steve") } let allSteves: [Person] = query.find() 1 2 3 4 let query : Query < Person > = personBox . query { Person . firstName . startsWith ( "Steve" ) } let allSteves : [ Person ] = query . find ( )

So, what is happening inside the block Box.query(_:) ? Why does that even compile? Where does Person.firstName come from? Code generation is the answer. It creates static “meta” properties for your entities. That’s how you can reference an entity’s property “itself”; not its value. To illustrate this with an example, here’s what the code generator created for our Person entity class:

extension Person { static var id: Property<Person, Id<Person>> { /* ... */ } static var firstName: Property<Person, String> { /* ... */ } static var lastName: Property<Person, String> { /* ... */ } } 1 2 3 4 5 extension Person { static var id: Property<Person, Id<Person>> { /* ... */ } static var firstName: Property<Person, String> { /* ... */ } static var lastName: Property<Person, String> { /* ... */ } }

This generated approach has advantages over string based ones: Xcode helps you with auto-completion and prevents typos. If a property name was wrong, you will get an compile error immediately instead of errors at run-time.

You can form more complex queries by using comparison and boolean expression operators:

let query = personBox.query { (Person.firstName == "Steve" || Person.firstName == "Steven") && Person.age > 30 && Person.income.isBetween(1000000, and: 99999999999) } 1 2 3 4 5 let query = personBox . query { ( Person . firstName == "Steve" || Person . firstName == "Steven" ) && Person . age > 30 && Person . income . isBetween ( 1000000 , and : 99999999999 ) }

You can re-use query objects for optimal performance. Just call Query.find() again to execute the query and obtain recent data from the store.

For a more in-depth documentation of the many features ObjectBox queries offer, have a look at the API docs of Query and the query documentation.

Threading “rules”

ObjectBox does not impose threading limitations on you:

The API is thread safe

Entities are lightweight Swift objects: you can pass them across threads

And, yes, ObjectBox offers transactions and ACID semantics

What’s Missing in the Alpha?

⚠️ ObjectBox Swift Alpha is a developer preview. It is not ready for production use yet. Consider the following limitations:



No binary compatibility of your on-disk data with future versions.

No model migrations: once you persist an entity with 2 properties, you cannot simply add a 3rd property. You have to reset the store (e.g. delete the database files) to start from scratch.

Incomplete functionality: features like additional relation types, indexes, data observers, object browser, etc. are absent in this alpha. This functionality is available at lower layers and has not yet been exposed to Swift.

Why We Released this Preview

Because your feedback is paramount to us. We want ObjectBox not only to be the fastest, but also to be the most Swift-friendly persistence solution. By releasing it early we can still make adjustments based on your input.

Thus, this preview is really all about you: what do you love? What’s amiss? Where do you struggle in everyday app development?

We’re looking forward to receiving your comments and requests:

Take this short questionaire (takes only 1 or 2 minutes)

Add GitHub issues and

Upvote issues you consider important

Thank you!

***

ObjectBox is a super fast database and synchronization solution, built uniquely for Mobile and IoT devices. We bring edge computing to small devices, allowing data to be stored and processed from sensor to server for reliable, fast and secure data management. ObjectBox is smaller than 1MB, so it is the ideal solution across hardware from Mobile Apps, to IoT Devices and IoT Gateways. We are the first high-performance NoSQL, ACID-compliant on-device edge database. All of our products are built with developers in mind, so they are easy to use and take minimal code to implement.