Why Does Realm Use Realmoptional<Int> Rather Than Int? for Optional Properties

Why does Realm use RealmOptional Int rather than Int? for optional properties?

Realm model classes automatically implement getters and setters for your persisted properties that access the underlying database data. In order to provide these getters and setters, your properties must be declared with the dynamic modifier. This modifier asks Swift to dynamically dispatch accesses to the properties via getters and setters rather than directly accessing the member at compile time. The dynamic modifier comes with a significant limitation: it is only supported for types that can be represented in Objective-C. This is because Swift's dynamic dispatch is built on top of the Objective-C runtime. It is this limitation that prevents Int? from being directly supported by Realm.

You may wonder how String?, NSData?, and NSDate? are supported given this limitation. The answer is that they have natural equivalents in Objective-C, namely nullable NSString *, nullable NSData *, and nullable NSDate *. No such equivalents exist for Swift's numeric types.

Optional Int in Realm

From the Realm docs:

String, NSDate, and NSData properties can be declared as optional or non-optional using the standard Swift syntax.

Optional numeric types are declared using RealmOptional:

class Person: Object {
// Optional string property, defaulting to nil
dynamic var name: String? = nil

// Optional int property, defaulting to nil
// RealmOptional properties should always be declared with `let`,
// as assigning to them directly will not work as desired
let age = RealmOptional<Int>()
}

let realm = try! Realm()
try! realm.write() {
var person = realm.create(Person.self, value: ["Jane", 27])
// Reading from or modifying a `RealmOptional` is done via the `value` property
person.age.value = 28
}

RealmOptional supports Int, Float, Double, Bool, and all of the sized versions of Int (Int8, Int16, Int32, Int64).

UPDATE:

The Optional Ints that were mentioned in the Tweet by Realm were just regarding a bugfix for the RealmOptional way of implementing an Optional numeric value with the sized versions of Int

According to the guys from Realm you still have to use RealmOptional if you want to have Optional numeric values in a Realm object. You cannot simply use it like other Optional types.

So dynamic var reps: Int? will not work.

Realm Swift Composite Keys for Optional Properties

You need to set RealmOptional's value member. RealmOptional properties cannot be var because Realm can't detect assignment to property types that cannot be represented by the Objective-C runtime, which is why RealmOptional, List and LinkingObjects properties must all be let.

class Item: Object {
dynamic var id = 0
let importantNumber = RealmOptional<Int>()
let importantNumber2 = RealmOptional<Int>()

func setCompoundID(id: Int) {
self.id = id
compoundKey = compoundKeyValue()
}

func setCompoundImportantNumber(importantNumber: Int) {
self.importantNumber.value = importantNumber
compoundKey = compoundKeyValue()
}

func setCompoundImportantNumber2(importantNumber2: Int) {
self.importantNumber2.value = importantNumber2
compoundKey = compoundKeyValue()
}

dynamic lazy var compoundKey: String = self.compoundKeyValue()

override static func primaryKey() -> String? {
return "compoundKey"
}

func compoundKeyValue() -> String {
return "\(id)\(importantNumber)\(importantNumber2)"
}
}

Why does Realm suggest that List T properties be declared using let ?

List<T> properties should be declared using let as Realm is unable to intercept assignment to these properties. Assigning to List<T> properties won't result in your changes being persisted to the Realm file. By declaring the property using let rather than var you enlist the Swift compiler to detect code that won't do what you intend.

Instead of assigning to List<T> properties you should mutate the existing value of the property via the methods that are part of the RangeReplaceableCollection protocol to which List<T> conforms.

For instance, to add a new dog:

person.dogs.append(lassie)

Or to replace the existing dogs:

person.dogs.replaceSubrange(0..<person.dogs.count, with: [fido, spot])

Why?

Realm model classes automatically implement getters and setters for your persisted properties that access the underlying database data. In order to provide these getters and setters, your properties must be declared with the dynamic modifier. This modifier asks Swift to dynamically dispatch accesses to the properties via getters and setters rather than directly accessing the member at compile time. The dynamic modifier comes with a significant limitation: it is only supported for types that can be represented in Objective-C. This is because Swift's dynamic dispatch is built on top of the Objective-C runtime. It is this limitation that prevents Realm from intercepting assignments to List<T> properties.

Bool and Int properties not added to Realm

To make optional int's and bool's in Realm, they must be defined as RealmOptional.

So update your model like this

@objcMembers class CategoryDoc: Object: Codeable {
dynamic var name: String?

let isActive = RealmOptional<Int>()
let v = RealmOptional<Bool>()

RealmSwift and Codable when using optional types

Vin Gazoli gave me the missing key.

First, RealmOptional needs to be declared as a let, so in the init you need to set the value by using myObject.myVariable.value = newValue. Then, everywhere you use it you must use it as obj.variable.value as well. RealmOptional does not conform to codable though, so you must write an extension. You can find it below as well as a link to where I received it.

ex of object:

class Attendee: Object,Codable {

@objc dynamic var id = 0
@objc dynamic var attendeeId = 0
@objc dynamic var lanId = ""
@objc dynamic var firstName = ""
@objc dynamic var lastName = ""
@objc dynamic var email = ""
@objc dynamic var employeeId = ""
@objc dynamic var badgeId = ""
@objc dynamic var department = ""
@objc dynamic var picture:String? = nil
let scanIn = RealmOptional<Double>()
let scanOut = RealmOptional<Double>()

override static func primaryKey () -> String? {
return "id"
}

private enum CodingKeys: String, CodingKey {
case id, attendeeId, lanId, firstName, lastName, email, employeeId, badgeId, department, picture, scanIn, scanOut
}

required convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey:.id)
attendeeId = try container.decodeIfPresent(Int.self, forKey:.attendeeId) ?? 0
lanId = try container.decode(String.self, forKey:.lanId)
firstName = try container.decode(String.self, forKey:.firstName)
lastName = try container.decode(String.self, forKey:.lastName)
email = try container.decode(String.self, forKey:.email)
employeeId = try container.decode(String.self, forKey:.employeeId)
badgeId = try container.decode(String.self, forKey:.badgeId)
department = try container.decode(String.self, forKey:.department)
picture = try container.decodeIfPresent(String.self, forKey:.picture)
self.scanIn.value = try container.decodeIfPresent(Double.self, forKey:.scanIn) ?? 0
self.scanOut.value = try container.decodeIfPresent(Double.self, forKey:.scanOut) ?? 0
}

The below code is REQUIRED to make the above object function. Retrieved from this link in conjunction with h1m5's fix in the comments of that page. The below is for Double. The link has other primitives.

func assertTypeIsEncodable<T>(_ type: T.Type, in wrappingType: Any.Type) {
guard T.self is Encodable.Type else {
if T.self == Encodable.self || T.self == Codable.self {
preconditionFailure("\(wrappingType) does not conform to Encodable because Encodable does not conform to itself. You must use a concrete type to encode or decode.")
} else {
preconditionFailure("\(wrappingType) does not conform to Encodable because \(T.self) does not conform to Encodable.")
}
}
}

func assertTypeIsDecodable<T>(_ type: T.Type, in wrappingType: Any.Type) {
guard T.self is Decodable.Type else {
if T.self == Decodable.self || T.self == Codable.self {
preconditionFailure("\(wrappingType) does not conform to Decodable because Decodable does not conform to itself. You must use a concrete type to encode or decode.")
} else {
preconditionFailure("\(wrappingType) does not conform to Decodable because \(T.self) does not conform to Decodable.")
}
}
}

extension RealmOptional : Encodable where Value : Encodable {
public func encode(to encoder: Encoder) throws {
assertTypeIsEncodable(Value.self, in: type(of: self))

var container = encoder.singleValueContainer()
if let v = self.value {
try (v as Encodable).encode(to: encoder) // swiftlint:disable:this force_cast
} else {
try container.encodeNil()
}
}
}

extension RealmOptional : Decodable where Value : Decodable {
public convenience init(from decoder: Decoder) throws {
// Initialize self here so we can get type(of: self).
self.init()
assertTypeIsDecodable(Value.self, in: type(of: self))

let container = try decoder.singleValueContainer()
if !container.decodeNil() {
let metaType = (Value.self as Decodable.Type) // swiftlint:disable:this force_cast
let element = try metaType.init(from: decoder)
self.value = (element as! Value) // swiftlint:disable:this force_cast
}
}
}

Optional Int property contains nil instead of zero during migration

It was a bug. See https://github.com/realm/realm-cocoa/pull/3643. Fixed in 0.103.2.

Optional properties of scalar types were getting the wrong Objective-C
type code, leading to the dynamic accessors taking the non-optional
code path. This resulted in nil values being returned as 0.

Exception throw for null data with optional Int type in Realm

The screenshot from the debugger shows that elevationFt is an empty string, which is not a number or null, so it is not a valid value for an optional int property.

Swift 4 Realm Swift object

As the docs clearly state, numeric types cannot simply be marked Optional, because Optional numeric types cannot be represented in Objective-C. You need to use RealmOptional to store Optional numeric types in Realm classes.

public class Test : Object {
@objc dynamic var id: Int = 0
let long = RealmOptional<Double>()
}


Related Topics



Leave a reply



Submit