Type Projection - Swift SDK
On this page
New in version 10.20.0.
Overview
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.
Declare Type Projections
To use type projection with Realm:
- Use one of Realm's custom type protocols to map an unsupported data type to a type that Realm supports
- Use the projected types as @Persisted properties in the Realm object model
Conform to the Type Projection Protocol
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 } }
These are protocols modeled after Swift's built-in RawRepresentable.
Supported PersistedTypes
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 { var id: ObjectId var name: String // Although the `PersistedType` cannot be optional, you can use the // custom-mapped type as an optional in your object model. var url: URL? }
Use Type Projection in the Model
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 { var id: ObjectId var name: String // Since we declared the URL as a FailableCustomPersistable, // it must be optional. 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`. var location: CLLocationCoordinate2D } public class Location: EmbeddedObject { var latitude: Double 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)
Type Projection in the Schema
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.

Access Projected Types
When you access projected types in Realm, this access is often based on the persisted type.
Queries on Realm Objects
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.
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")
Queries on Embedded Objects
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.
Dynamic APIs
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.