Docs Menu

Docs HomeDevelop ApplicationsAtlas Device SDKs

Model Data with Device Sync - Swift SDK

On this page

  • Realm Object Models
  • JSON Schema
  • Realm Relationships
  • To-One Relationship
  • To-Many Relationship
  • Inverse Relationship
  • Embedded Object Models
  • Create Object Models and Schemas

To define a Realm object, derive a class from Object. The models that you define each map to their own collection in MongoDB Atlas. In this example, both Dog and Person would map to separate collections in MongoDB Atlas.

class Dog: Object {
@Persisted(primaryKey: true) var _id = ObjectId()
@Persisted var name = ""
@Persisted var age = 0
}
class Person: Object {
@Persisted(primaryKey: true) var _id = ObjectId()
@Persisted var name = ""
}

When you have a corresponding schema in Atlas App Services, Device Sync automatically converts the Realm Swift data types to BSON as it syncs to Atlas. When the client device syncs the data down from App Services via Device Sync, the SDK converts BSON back to Swift objects. If you query for these objects by accessing MongoDB Atlas data directly instead of using Device Sync to sync the data, you get the BSON data types. MongoDB Data Access does not automatically map to the Swift object equivalents.

Dog Schema in App Services
{
"title": "Dog",
"bsonType": "object",
"required": [
"_id"
],
"properties": {
"_id": {
"bsonType": "objectId"
},
"name": {
"bsonType": "string"
},
"age": {
"bsonType": "long"
}
}
}
Person Schema in App Services
{
"title": "Person",
"bsonType": "object",
"required": [
"_id"
],
"properties": {
"_id": {
"bsonType": "objectId"
},
"name": {
"bsonType": "string"
}
}
}

Using the objects in the example above, consider a case where a Person can have one Dog. We can add a dog property to our Person model that is an optional link to a Dog object. To-one relationships must be optional.

class Dog: Object {
@Persisted(primaryKey: true) var _id = ObjectId()
@Persisted var name = ""
@Persisted var age = 0
}
class Person: Object {
@Persisted(primaryKey: true) var _id = ObjectId()
@Persisted var name = ""
// To-one relationship
@Persisted var dog: Dog?
}

In the App Services schema, we see the new property translates to a field dog. The field is not in the required array because it is an optional property. Its type is an objectId which links to a specific Dog object in the separate Dog collection. The objectId is the primary key of the Dog model, so it's the field that links the two objects.

Person with To-One Relationship to Dog
{
"title": "Person",
"bsonType": "object",
"required": [
"_id"
],
"properties": {
"_id": {
"bsonType": "objectId"
},
"dog": {
"bsonType": "objectId"
},
"name": {
"bsonType": "string"
}
}
}

The Dog schema doesn't change. Because this is a to-one relationship, it's a one-way relationship; the Dog has no relationship back to Person.

Tip

See also:

For more information about to-one relationships, see: To-One Relationship.

Using the objects in the example above, consider a case where a Person can have many dogs. We can add a dogs property to our Person model that is a list of Dog objects. If the person has no dogs, this is an empty list. As the person gets dogs, we can create new dog objects and append them to the person's dogs list.

class Dog: Object {
@Persisted(primaryKey: true) var _id = ObjectId()
@Persisted var name = ""
@Persisted var age = 0
}
class Person: Object {
@Persisted(primaryKey: true) var _id = ObjectId()
@Persisted var name = ""
// To-many relationship - a person can have many dogs
@Persisted var dogs: List<Dog>
}

In the App Services schema, we see the new property translates to a field dogs. The type of this field is an array, the items in the array are of type objectId. This is because we defined the primary key on our Dog model as an objectId. This field is an array of the primary keys of all of the Dog objects related to the Person object.

Person with To-Many Relationship to Dog
{
"title": "Person",
"bsonType": "object",
"required": [
"_id"
],
"properties": {
"_id": {
"bsonType": "objectId"
},
"name": {
"bsonType": "string"
},
"dogs": {
"bsonType": "array",
"items": {
"bsonType": "objectId"
}
}
}
}

Tip

See also:

For more information about to-many relationships, see: To-Many Relationship.

Using the objects in the example above, consider a case where the Dog object has an inverse relationship to the Person object.

class Dog: Object {
@Persisted(primaryKey: true) var _id = ObjectId()
@Persisted var name = ""
@Persisted var age = 0
// The backlink to the `Person` who has this `Dog`.
@Persisted(originProperty: "dogs") var person: LinkingObjects<Person>
}
class Person: Object {
@Persisted(primaryKey: true) var _id = ObjectId()
@Persisted var name = ""
// To-many relationship - a person can have many dogs
@Persisted var dogs: List<Dog>
}

In the App Services schema, we see that the person property that represents the inverse relationship to a Person from our Dog model is not present. You can't directly set the value of an inverse relationship, and that relationship does not exist in Atlas. However, Realm derives and updates those relationships for you in the client application based on your Realm object model.

Dog with Inverse Relationship to Person
{
"title": "Dog",
"bsonType": "object",
"required": [
"_id"
],
"properties": {
"_id": {
"bsonType": "objectId"
},
"name": {
"bsonType": "string"
},
"age": {
"bsonType": "long"
}
}
}

Tip

See also:

For more information about inverse relationships, see: Inverse Relationship.

When you define an embedded object with the Realm Swift SDK, you derive a class from EmbeddedObject. You can reference an embedded object type from parent object types in the same way as you would define a relationship:

class Person: Object {
@Persisted(primaryKey: true) var id = 0
@Persisted var name = ""
// To-many relationship - a person can have many dogs
@Persisted var dogs: List<Dog>
// Inverse relationship - a person can be a member of many clubs
@Persisted(originProperty: "members") var clubs: LinkingObjects<DogClub>
// Embed a single object.
// Embedded object properties must be marked optional.
@Persisted var address: Address?
convenience init(name: String, address: Address) {
self.init()
self.name = name
self.address = address
}
}
class DogClub: Object {
@Persisted var name = ""
@Persisted var members: List<Person>
// DogClub has an array of regional office addresses.
// These are embedded objects.
@Persisted var regionalOfficeAddresses: List<Address>
convenience init(name: String, addresses: [Address]) {
self.init()
self.name = name
self.regionalOfficeAddresses.append(objectsIn: addresses)
}
}
class Address: EmbeddedObject {
@Persisted var street: String?
@Persisted var city: String?
@Persisted var country: String?
@Persisted var postalCode: String?
}

Embedded objects map to embedded documents in the parent type's schema. This behavior differs from regular Realm objects, which map to their own MongoDB collection.

{
"title": "Person",
"bsonType": "object",
"required": ["id"],
"properties": {
"id": { "bsonType": "int" },
"name": { "bsonType": "string" },
"dogs": {
"bsonType": "array",
"items": {
"bsonType": "objectId"
}
},
"address": {
"title": "Address",
"bsonType": "object",
"properties": {
"street": { "bsonType": "string" },
"city": { "bsonType": "string" },
"country": { "bsonType": "string" },
"postalCode": { "bsonType": "string" }
}
}
}
}
{
"title": "DogClub",
"bsonType": "object",
"required": ["_id", "name", "addresses"],
"properties": {
"_id": "objectId",
"name": { "bsonType": "string" },
"members": {
"bsonType": "array",
"items": {
"bsonType": "objectId"
}
},
"addresses": {
"bsonType": "array",
"items": {
"title": "Address",
"bsonType": "object",
"properties": {
"street": { "bsonType": "string" },
"city": { "bsonType": "string" },
"country": { "bsonType": "string" },
"postalCode": { "bsonType": "string" }
}
}
}
}
}

To map between SDK object models and BSON data in Atlas, you must have a schema in App Services that matches your SDK object model. There are a few ways you can generate matching schema and object models:

  • Create object models in the client application, and generate App Services schemas from them.

  • Create schemas in App Services, and generate Realm object models from them.

If you are developing a new client application, you likely want to iterate on the data model in the client application. Enable Development Mode in App Services to have the backend infer and update the schema based on your client object models. Development Mode does not work with breaking schema changes, so you must remove the existing schema from the server when you make breaking changes to the SDK data model.

If you are developing a client application that works with data that already exists in Atlas, you can generate a schema from that data. Then, you can generate SDK object models from the server-side schema.

← Change an Object Model - Swift SDK