Compound key in Realm with lazy property - swift

I found this great solution for using Realm with compound primary key in Swift: https://github.com/realm/realm-cocoa/issues/1192
public final class Card: Object {
public dynamic var id = 0 {
didSet {
compoundKey = compoundKeyValue()
}
}
public dynamic var type = "" {
didSet {
compoundKey = compoundKeyValue()
}
}
public dynamic lazy var compoundKey: String = self.compoundKeyValue()
public override static func primaryKey() -> String? {
return "compoundKey"
}
private func compoundKeyValue() -> String {
return "\(id)-\(type)"
}
}
But I discovered that Realm does not support lazy properties, in my case I receive this error:
exception NSException * name: "RLMException" - reason: "Lazy managed property 'compoundKey' is not allowed on a Realm Swift object class. Either add the property to the ignored properties list or make it non-lazy." 0x00007f8a05108060
Is it still possible to have compound key without lazy property?

The solution you found is outdated. I'll leave a note about that there. I'd suggest removing the lazy modifier and initializing compoundKey to an empty string.

Related

What is the difference between a Computed Property and a Stored Value Property in Swift?

Question:
What is the difference between the two lines in Row.swift?
Context:
Row.swift
open class Row {
// Existing code (Good):
public var cellIdentifier: String { return String(describing self) }
// The change (Bad):
public var cellIdentifier: String = String(describing: self)
DifferentRow.swift
public class DifferentRow: Row {
public override var cellIdentifier: String { return "\(super.cellIdentifier)" }
// returns the error below
Error:
Cannot override mutable property with read-only property 'cellIdentifier'
This:
public var cellIdentifier: String { return String(describing self) }
is defining a computed property. No value is stored. Every time you access cellIdentifier the closure runs and returns the String. It is read-only because only the getter has been provided.
This:
public var cellIdentifier: String = String(describing: self)
is a stored value property and it is read/write.
The error is telling you that you can't replace a property that has read/write capabilities with one that has only read capabilities.
Note: if you are initializing a property with a value, you can't access self because self doesn't represent the class/struct instance until the object is completely initialized. If you made the property a lazy var, you could use self in the initialization, because then the property would be initialized once the first time it is accessed.
You can read more about Swift properties here in the Swift Language Guide
You can’t override a “read from and write to”-property with a property which one can only read from.
You can assign a different value:
public override var cellIdentifier: String = “newValue”
or create both a set and a get implementation:
public override var cellIdentifier: String {
get { return “\(super.cellIdentifier)” }
set { super.cellIdentifier = newValue }
}
Under Computed Properties read more on this syntax.
I think the error message is quite confusing.
The problem with
public var cellIdentifier: String = String(describing: self)
is the reference to self:
When assigning cellIdentifier during definition, self is not guarateed to be fully initialized.
Therefore, it is forbiddin to call a function (like String(describing:)) and hand-in the half-initilized self
One solution could be to make cellIdentifier a lazy property:
public lazy var cellIdentifier: String = String(describing: self)
This will automatically delay the function call to the time after initialization has finished.

Swift 4 Realm Swift object

I'm trying to use realmSwift in my application in Swift 4 but i have an error.
public class Test : Object {
#objc dynamic var id: Int = 0
#objc dynamic var long: Double? = 0
convenience init(id: Int, long:Double?) {
self.init()
self.id = id
self.long = long
}
override public class func primaryKey() -> String {
return "id"
}
}
Error for line with long variable.
Property cannot be marked #objc because its type cannot be represented in Objective-C
Thx for help
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>()
}
You have provided Int value to the double, so it's throw error. Use following code for double;
#objc dynamic var value: Double = 0.0
I hope this will fix your issue.
For more info: https://realm.io/docs/swift/latest#property-attributes

Modifying Realm List tries to recreate linked Objects

I have the following (simplified) Realm Models:
final class Item: Object {
#objc dynamic var identifier: String = ""
#objc dynamic var programSet: ProgramSet?
override static func primaryKey() -> String? {
return "identifier"
}
}
final class ProgramSet: Object {
#objc dynamic var identifier: String = ""
#objc dynamic var editorialCategory: EditorialCategory?
override static func primaryKey() -> String? {
return "identifier"
}
}
final public class EditorialCategory: Object {
#objc dynamic var identifier: String = ""
override public static func primaryKey() -> String? {
return "identifier"
}
}
final class Playlist: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var name = ""
#objc dynamic var created = Date()
var items = List<Item>()
override static func primaryKey() -> String? {
return "id"
}
}
When the user adds an item to a playlist, I try to append the ItemModel to the Playlist.items List:
try realm.write {
playlistToModify.items.append(item)
}
My problem is that it's possible for Items to share the same EditorialCategory, so it may already be in the database. When I add other items via the Realm.add(_:update:) method, Realm won't try to recreate the linked objects if one with the same primary key exists.
The List.append(_:) method seems to force recreating the linked objects of an ItemModel when called. The app crashes in the following line from object_accessor.hpp:
throw std::logic_error(util::format("
Attempting to create an object of type '%1' with an existing primary key value '%2'.",
object_schema.name, ctx.print(*primary_value)));
Stacktrace:
The object_schema.name is "EditorialCategoryModel", which is linked in ProgramSetModel, which itself is linked from ItemModel.
Is there a way to avoid this error and append the item to the list without Realm attempting to re-create every linked object? Thanks!

How to set attribute of member object of a realm model?

I have a realm object called DiscoverUserInfo:
class DiscoverUserInfo: Object , Mappable{
dynamic var UserObject:User?
dynamic var ConnectionStatus:Int = -1
var PreviousMeetings = List<Meeting>()
required convenience init?(map: Map) {
self.init()
}
override class func primaryKey() -> String? { return "UserObject.UserId" }
}
Now for this, I want to set a primary key which is UserId of UserObject.
But when I run this code, I get this error:
Terminating app due to uncaught exception 'RLMException', reason:
'Primary key property 'UserObject.UserId' does not exist on object
'DiscoverUserInfo''
You cannot set a primary key using a property of a dynamic variable. You'll have to do something like this:
class DiscoverUserInfo: Object , Mappable{
dynamic var UserObject: User?
dynamic var id = ""
dynamic var ConnectionStatus:Int = -1
var PreviousMeetings = List<Meeting>()
required convenience init?(map: Map) {
self.init()
}
override class func primaryKey() -> String? {
return "id"
}
}
and then set the id to the associated UserObject's UserId each time you create a new DiscoverUserInfo object.
This is related to the issue of having no native support for compound primary keys in Realm. However, we expect to see this feature down the road.

How to save a struct to realm in swift?

It is easy to use Realm with classes by inheriting from Object. But how would I save a struct containing several fields to realm in Swift? E.g.
struct DataModel {
var id = 0
var test = "test"
}
I know the documentation is clear about supported types. But maybe there is nice workaround or - even better - someone from realm could write about future plans about structs.
I' suggest you to use protocols, to achive what you want.
1) Create your Struct
struct Character {
public let identifier: Int
public let name: String
public let realName: String
}
2) Create your Realm Object
final class CharacterObject: Object {
dynamic var identifier = 0
dynamic var name = ""
dynamic var realName = ""
override static func primaryKey() -> String? {
return "identifier"
}
}
3) Use protocols to transform our struct to Realm Object
public protocol Persistable {
associatedtype ManagedObject: RealmSwift.Object
init(managedObject: ManagedObject)
func managedObject() -> ManagedObject
}
4) Make your struct persistable
extension Character: Persistable {
public init(managedObject: CharacterObject) {
identifier = managedObject.identifier
name = managedObject.name
realName = managedObject.realName
}
public func managedObject() -> CharacterObject {
let character = CharacterObject()
character.identifier = identifier
character.name = name
character.realName = realName
return character
}
}
With these tools in place, we are ready to implement the insertion methods of our persistence layer.
5) Exemple to write datas
public final class WriteTransaction {
private let realm: Realm
internal init(realm: Realm) {
self.realm = realm
}
public func add<T: Persistable>(_ value: T, update: Bool) {
realm.add(value.managedObject(), update: update)
}
}
// Implement the Container
public final class Container {
private let realm: Realm
public convenience init() throws {
try self.init(realm: Realm())
}
internal init(realm: Realm) {
self.realm = realm
}
public func write(_ block: (WriteTransaction) throws -> Void)
throws {
let transaction = WriteTransaction(realm: realm)
try realm.write {
try block(transaction)
}
}
}
5) Use the magic!
let character = Character(
identifier: 1000,
name: "Spiderman",
realName: "Peter Parker"
)
let container = try! Container()
try! container.write { transaction in
transaction.add(character)
}
Amazing source : Using Realm with Value Types & My Article
To save a struct in Realm, means copying the data into a Realm Object. The reason why Realm Objects are classes and not structs is because they are not inert values, but auto-updating objects that represent the persisted data in Realm. This has practical benefits, such as the fact that a Realm Object's data is lazy loaded.
You can take advantage of Realm's approach by responding to the change notifications from a Realm instance. For example if your UITableView data source is based off an array property on a Realm Object, as long as you have an instance of that object, you are guaranteed that after the notification it represents the correct values. Used properly this can simplify your code versus having multiple copies of values as structs.
Swift 4 shortest answer
Save structs as Data in Realm
struct MyStruct : Codable { // Variables here }
class MyRealObject : Object {
#objc private dynamic var structData:Data? = nil
var myStruct : MyStruct? {
get {
if let data = structData {
return try? JSONDecoder().decode(MyStruct.self, from: data)
}
return nil
}
set {
structData = try? JSONEncoder().encode(newValue)
}
}
}
Use the magic
let realm = try! Realm()
try! realm.write {
let myReal = MyRealObject()
myReal.myStruct = MyStruct(....)
realm.add(myReal)
}
You can do what suggests Ludovic, or you can automate that process and get rid of that boilerplate code for each of your structs by using Unrealm.