How can I use a lazy var in Swift and Realm? - ios

I have a lazy var in Swift like:
import Realm
class DataUser: RLMObject {
#objc dynamic lazy var id: String = self.myId()
#objc dynamic var firstTime: Int = 0
#objc dynamic var position: Int = 0
private func myId() -> String {
return “\(firstTime)\(position)”
}
I’m getting this message:
** Terminating app due to uncaught exception 'RLMException', reason: 'Lazy managed property 'id' is not allowed on a Realm Swift object
class. Either add the property to the ignored properties list or make
it non-lazy.'
What is the correct way to use a lazy-variable in Swift and Realm?
Thanks!

I believe you can use ignoreProperties() method for creating lazy var
Swift code:
public override static func ignoredProperties() -> [String] {
return ["data"]
}

You can directly use dynamic lazy var id: String = self.myId() also, and I don't think that your implementation is wrong. But Realm seems to not handling properly as your id is lazy. you can see bug report about the same also.

Related

Why does adding a convenience init to a Realm object declaration mess with private values?

I have created a Realm object that needs to store an enum value. To do that I use a method outlined in this question which involves declaring a private property of type String, and then declaring another property of type Enum that sets/reads the private property using getters and setters.
For ease of reference here is the code for that:
#objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
override static func primaryKey() -> String? {
return "id"
}
}
I have noticed though that if I add a convenience init to the class declaration (to make it a bit easier to initialise the object) the revisionType properties on the objects I end up with adopt the default value declared in the class, and NOT the revision type value passed to the class using the convenience init.
Here is the class declaration with a convenience init
#objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
convenience init(title: String, revisionType: RevisionType) {
self.init()
self.title = title
self.revisionType = revisionType
}
override static func primaryKey() -> String? {
return "id"
}
}
And - to make things even more perplexing - if I simply remove the word 'private' from the revisionTypeRaw property, everything works fine!
I am confused. 1) Why does adding a convenience init have this effect? 2) Why does making the property 'public' resolve the issue?
I have created a demo Xcode project to illustrate the issue and can share it if anyone needs it.
Update:
I found the problem. It has nothing to do with the convenience init. I am using #objcMembers at the top of the class as per the Realm docs: https://realm.io/docs/swift/latest/#property-attributes
If you remove this and place #objc in front of the private keyword, everything works as would be expected. I guess the question then is: what explains this behaviour?
This is a good question but I think the issue is elsewhere in the code. Let's test it.
I created a TestClass that has a Realm managed publicly visible var, name, as well as a non-managed public var visibleVar which is backed by a Realm managed private var, privateVar. I also included a convenience init per the question. The important part is the privateVar is being set to the string "placeholder" so we need to see if that is overwritten.
class TestClass: Object {
#objc dynamic var name = ""
#objc private dynamic var privateVar = "placeholder"
var visibleVar: String {
get {
return self.privateVar
}
set {
self.privateVar = newValue
}
}
convenience init(aName: String, aString: String) {
self.init()
self.name = aName
self.visibleVar = aString
}
}
We then create two instances and save them in Realm
let a = TestClass(aName: "some name", aString: "some string")
let b = TestClass(aName: "another name", aString: "another string")
realm.add(a)
realm.add(b)
Then a button action to load the two objects from Realm and print them.
let testResults = realm.objects(TestClass.self)
for test in testResults {
print(test.name, test.visibleVar)
}
and the output:
some name some string
another name another string
So in this case, the default value of "placeholder" is being overwritten correctly when the instances are being created.
Edit:
A bit more info.
By defining your entire class with #objMembers, it exposes your propertites to Objective-C, but then private hides them again. So that property is not exposed to ObjC. To reverse that hiding, you have to say #objc explicitly. So, better practice is to define the managed Realm properties per line with #objc dynamic.

Realm Swift - save a reference to another object

I thought this would be pretty straightforward after reading here and here but I'm a bit stuck.
I have a 'favouriteWorkout' object that looks like this :
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference = WorkoutSessionObject()
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
What I'm trying to do here is reference a WorkoutSessionObject in Realm that links from a WorkoutName when a workout is saved as a favourite.
My WorkoutSessionObject has a primary key of workoutID which is a UUID string. It looks like this :
class WorkoutSessionObject: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutType = ""
let exercises = List<WorkoutExercise>()
#objc dynamic var totalExerciseCount = 0
#objc dynamic var rounds = 0
#objc dynamic var favourite : Bool = false
override class func primaryKey() -> String? {
return "workoutID"
}
}
I've then tried to save using this :
let favouriteWorkout = FavouriteObject()
favouriteWorkout.favouriteWorkoutName = favouriteName
favouriteWorkout.workoutReference = (realm.object(ofType: WorkoutSessionObject.self, forPrimaryKey: self.workoutID))!
do {
try realm.write {
realm.add(favouriteWorkout)
}
} catch {
print ("Error adding favourite")
}
but i get a crash when I run of :
'RLMException', reason: 'The FavouriteObject.workoutReference property must be marked as being optional.
However, when I then try to make it optional (by adding ?) it says
"Cannot use optional chaining on non-optional value of type 'WorkoutSessionObject"!
Summary
I want to save a reference of the workoutID of a WorkoutSessionObject in my FavouriteObject which is an actual link to the WorkoutSessionObject (so the properties can be accessed from favourites)
Update
using the answers below I've now sorted the problem of the workout reference. This is now showing in Realm as the proper format () under "workoutReference". However, I'm now getting "nil" in "workoutReference" when trying to save. I know the workoutID is coming through correctly as I am printing it in the console.
You need to change the declaration of workoutReference. First of all, you need to make it Optional by writing ? after the type. Secondly, you shouldn't assign a default value to it, it needs to be Optional for a reason. The linked docs clearly state that
to-one relationships must be optional
, and workoutReference is clearly a to-one relationship.
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
#objc dynamic var workoutReference:WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}
In property-cheatsheet you can see that a non-optional Object-property is not allowed, so you have to change it like the following:
class FavouriteObject: Object {
#objc dynamic var favouriteWorkoutName = ""
// here you have to make the property optional
#objc dynamic var workoutReference: WorkoutSessionObject?
override class func primaryKey() -> String? {
return "favouriteWorkoutName"
}
}

Realm ignoredProperties not working for compound primary key

I am trying to create compound primary key from two keys. Using lazy for compoundKey will raise an exception - either remove lazy or add to ignore property list
So when I try to add ignore property list I am getting following exception - Terminating app due to uncaught exception 'RLMException', reason: 'Primary key property 'compoundKey' does not exist on object 'Collection'
Removing lazy and setting the empty string will add empty key and hence single row which will treat all primary key value as empty.
This is my code
class Collection : Object {
#objc dynamic var count: Int = 0
#objc dynamic var nextURL: String?
#objc dynamic var previousURL: String?
func setCompoundNextURL(nextURL: String) {
self.nextURL = nextURL
compoundKey = compoundKeyValue()
}
func setCompoundTourPreviousURL(previousURL: String) {
self.previousURL = previousURL
compoundKey = compoundKeyValue()
}
public dynamic lazy var compoundKey: String = self.compoundKeyValue()
override static func primaryKey() -> String? {
return "compoundKey"
}
override static func ignoredProperties() -> [String] {
return ["compoundKey"]
}
func compoundKeyValue() -> String {
return "\(nextURL ?? "")\(previousURL ?? "")"
}
}
Please help. I am not able to figure where I went wrong.
You cannot tell Realm to use an ignored property as a primary key. An ignored property isn't persisted to the Realm. The primary key property must be persisted to the Realm. Additionally, the primary key property's value cannot be changed after the object is created. For this reason I'd suggest computing the value inside a convenience initializer and assigning it to the property at that time.

Swift mutating func in Singleton

In Swift I have this Singleton
struct Networking {
static let shared = Networking()
private var observed: Set<String> = []
}
I have to manipulate observed and I need to create useful method to insert and remove member in Set.
mutating func addObserver(for member: String) {
//other code
observed.insert(member)
}
mutating func removeObserver(for member: String) {
//other code
observed.remove(member)
}
The problem is when I try to call this methods like this
Networking.shared.addObserver(for: "x")
because I'm getting this error
cannot use mutating on immutable value: “shared” is a “let” constant
This error is pretty clear. shared is let and obviously it cannot be modified. But to modify the var I need to declare method as mutating. It's a vicious circle.
Any ideas?
Thanks.
If you want your Networking object to act as a singleton, why not make it a class instead of a struct?
class Networking {
static let shared = Networking()
private var observed: Set<String> = []
func addObserver(for member: String) {
//other code
observed.insert(member)
}
func removeObserver(for member: String) {
//other code
observed.remove(member)
}
}
Networking.shared.addObserver(for: "x")
This simplifies the code and solves your issue.
Basically your syntax is wrong, Networking() creates a new instance of the class.
To use the struct as singleton you have to write
Networking.shared.addObserver(for: "x")
Then declare shared as mutable
static var shared = Networking()
There is also another way of doing it
class Networking {
static let shared = Networking()
var observed: Set<String> = [] {
didSet {
print("set has changed")
}
}
}
Value Type
Since Set is a struct (a value type), the didSet block will be executed every time you add or remove and element to Set.
Networking.shared.observed.insert("a")
set has changed
Networking.shared.observed.insert("b")
set has changed
Networking.shared.observed.remove("a")
set has changed

Using Realm Swift with initializers throws constant errors

Xcode 7.1 and Swift 2.1 with latest Realm Swift 0.96.2
I created a model class for Realm but it is constantly throwing errors about inits. I understand initializers to an extent for subclasses but I can't wrap my head around this and why it fails. Here's the class I made:
import UIKit
import RealmSwift
class Boxes: Object {
dynamic var precessor: String = "B";
dynamic var id: Int = 0;
dynamic var boxNumber: String {
return "\(precessor) \(id)"; //computed property
}
dynamic var boxDescription: String? = "";
dynamic var brand: String? = "";
dynamic let dateCreated: NSDate
dynamic var dateUpdated: NSDate?
dynamic var photo: UIImage?
dynamic var tags: NSArray? = [];
override static func primaryKey() -> String? {
return "id"; //sets primary key of the model
}
init(precessor: String, id: Int, description: String, brand: String, dateCreated: NSDate, dateUpdated: NSDate) {
self.precessor = precessor;
self.boxDescription = description;
self.brand = brand;
self.dateUpdated = dateUpdated;
self.dateCreated = dateCreated;
super.init();
}
}
This won't build when I try and it tells me:
'required' initializer 'init()' must be provided by subclass of 'Object'
And that I need this line added:
required init() {
fatalError("init() has not been implemented")
}
That satiates the compiler enough to let me build the project. However when I run the project, it always errors out and give me the fatalError line in the output. I know it's doing this as a last resort initializer but I can't figure out why.
Is this related to a super initializer I'm missing somewhere? I'm relatively new to swift but I can get my initializers to work if I don't subclass my class with Object
You are required to implement init() but Xcode doesn't know how to implement if for you so it puts in fatalError("init() has not been implemented") to remind you to implement it.
You probably just want to call super. So:
required init() {
super.init()
}

Resources