Docs Menu

Type Projection - Swift SDK

On this page

  • Overview
  • Declare Type Projections
  • Conform to the Type Projection Protocol
  • Use Type Projection in the Model
  • Type Projection in the Schema
  • Access Projected Types
  • Queries on Realm Objects
  • Queries on Embedded Objects
  • Dynamic APIs

New in version 10.20.0.

The Swift SDK supports type mapping between a data type supported by Realm and a type not supported by Realm. This enables developers to use "projected" types as managed properties as though they are supported types.

To use type projection with Realm:

  1. Use one of Realm's custom type protocols to map an unsupported data type to a type that Realm supports
  2. Use the projected types as @Persisted properties in the Realm object model

You can map an unsupported data type to a type that Realm supports using one of the Realm type projection protocols.

The Swift SDK provides two type projection protocols:

  • CustomPersistable
  • FailableCustomPersistable

Use CustomPersistable when there is no chance the conversion can fail.

Use FailableCustomPersistable when it is possible for the conversion to fail.

// Extend a type as a CustomPersistable if if is impossible for
// conversion between the mapped type and the persisted type to fail.
extension CLLocationCoordinate2D: CustomPersistable {
// Define the storage object that is persisted to the database.
// The `PersistedType` must be a type that Realm supports.
// In this example, the PersistedType is an embedded object.
public typealias PersistedType = Location
// Construct an instance of the mapped type from the persisted type.
// When reading from the database, this converts the persisted type to the mapped type.
public init(persistedValue: PersistedType) {
self.init(latitude: persistedValue.latitude, longitude: persistedValue.longitude)
}
// Construct an instance of the persisted type from the mapped type.
// When writing to the database, this converts the mapped type to a persistable type.
public var persistableValue: PersistedType {
Location(value: [self.latitude, self.longitude])
}
}
// Extend a type as a FailableCustomPersistable if it is possible for
// conversion between the mapped type and the persisted type to fail.
// This returns nil on read if the underlying column contains nil or
// something that can't be converted to the specified type.
extension URL: FailableCustomPersistable {
// Define the storage object that is persisted to the database.
// The `PersistedType` must be a type that Realm supports.
public typealias PersistedType = String
// Construct an instance of the mapped type from the persisted type.
// When reading from the database, this converts the persisted type to the mapped type.
// This must be a failable initilizer when the conversion may fail.
public init?(persistedValue: String) { self.init(string: persistedValue) }
// Construct an instance of the persisted type from the mapped type.
// When writing to the database, this converts the mapped type to a persistable type.
public var persistableValue: String { self.absoluteString }
}
Tip
See also:

These are protocols modeled after Swift's built-in RawRepresentable.

The PersistedType can use any of the primitive types that the Swift SDK supports. It can also be an Embedded Object.

PersistedType cannot be an optional or a collection. However you can use the mapped type as an optional or collection property in your object model.

extension URL: FailableCustomPersistable {
// The `PersistedType` cannot be an optional, so this is not a valid
// conformance to the FailableCustomPersistable protocol.
public typealias PersistedType = String?
...
}
class Club: Object {
@Persisted var id: ObjectId
@Persisted var name: String
// Although the `PersistedType` cannot be optional, you can use the
// custom-mapped type as an optional in your object model.
@Persisted var url: URL?
}

A type that conforms to one of the type projection protocols can be used with the @Persisted property declaration syntax introduced in Swift SDK version 10.10.0. It does not work with the @objc dynamic syntax.

You can use projected types for:

  • Top-level types
  • Optional versions of the type
  • The types for a collection

When using a FailableCustomPersistable as a property, define it as an optional property. When it is optional, the FailableCustomPersistable protocol maps invalid values to nil. When it is a required property, it is force-unwrapped. If you have a value that can't be converted to the projected type, reading that property throws an unwrapped fail exception.

class Club: Object {
@Persisted var id: ObjectId
@Persisted var name: String
// Since we declared the URL as a FailableCustomPersistable,
// it must be optional.
@Persisted var url: URL?
// Here, the `location` property maps to an embedded object.
// We can declare the property as required.
// If the underlying field contains nil, this becomes
// a default-constructed instance of CLLocationCoordinate
// with field values of `0`.
@Persisted var location: CLLocationCoordinate2D
}
public class Location: EmbeddedObject {
@Persisted var latitude: Double
@Persisted var longitude: Double
}

When your model contains projected types, you can create the object with values using the persisted type, or by assigning to the field properties of an initialized object using the projected types.

// Initialize objects and assign values
let club = Club(value: ["name": "American Kennel Club", "url": "https://akc.org"])
let club2 = Club()
club2.name = "Continental Kennel Club"
// When assigning the value to a type-projected property, type safety
// checks for the mapped type - not the persisted type.
club2.url = URL(string: "https://ckcusa.com/")!
club2.location = CLLocationCoordinate2D(latitude: 40.7509, longitude: 73.9777)

When you declare your type as conforming to a type projection protocol, you specify the type that should be persisted in realm. For example, if you map a custom type URL to a persisted type of String, a URL property appears as a String in the schema, and dynamic access to the property acts on strings.

The schema does not directly represent mapped types. Changing a property from its persisted type to its mapped type, or vice versa, does not require a migration.

Realm Studio screenshot showing the field types using persisted types.

When you access projected types in Realm, this access is often based on the persisted type.

When working with projected types, queries operate on the persisted type. However, you can use the mapped types interchangeably with the persisted types in arguments in most cases. The exception is queries on embedded objects.

Tip

Projected types support sorting and aggregates where the persisted type supports them.

let akcClub = realm.objects(Club.self).where {
$0.name == "American Kennel Club"
}.first!
// You can use type-safe expressions to check for equality
XCTAssert(akcClub.url == URL(string: "https://akc.org")!)
let clubs = realm.objects(Club.self)
// You can use the persisted property type in NSPredicate query expressions
let akcByUrl = clubs.filter("url == 'https://akc.org'").first!
XCTAssert(akcByUrl.name == "American Kennel Club")

You can query embedded types on the supported property types within the object using memberwise equality.

Object link properties support equality comparisons, but do not support memberwise comparisons. You can query embedded objects for memberwise equality on all primitive types. You cannot perform memberwise comparison on objects and collections.

Because the schema has no concept of custom type mappings, reading data via any of the dynamic APIs gives the underlying persisted type. Realm does support writing mapped types via a dynamic API, and converts the projected type to the persisted type.

The most common use of the dynamic APIs is migration. You can write projected types during migration, and Realm converts the projected type to the persisted type. However, reading data during a migration gives the underlying persisted type.

←  Supported Property Types - Swift SDKCollections - Swift SDK →
Give Feedback
© 2022 MongoDB, Inc.

About

  • Careers
  • Investor Relations
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2022 MongoDB, Inc.