One-to-one relationship in Realm swift - ios

How can I model one-to-one relationship between objects?
For example, I have models for user_infoA, user_infoB and user_profile.
user_profile has
user_id (PK)
name
age
user_infoA has
info_a_id (PK)
user_profile
user_infoB has
info_b_id (PK)
user_profile
user_profile (P) have relationship with both user_infoA (A) and user_infoB(B). When A is deleted, also will P be deleted or not? Will P be deleted only if when related A and B are deleted?
And how can I model this with realm swift?
Many-to-one relationship needs optional property, and it makes me use force unwrapping optional. :(
[EDITED]
class RealmMyProfile: Object {
#objc dynamic var id: Int64 = 0
#objc dynamic var profile = RealmUserProfile()
}
class RealmUserProfile: Object {
#objc dynamic var userId: Int64 = 0
#objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "userId"
}
}
Exception 'The RealmMyProfile.profile property must be marked as being optional.' occurred. It should be optional.

To-one relationships (links) in Realm cannot enforce that a link is always present. So they always have to be marked as optional because there's no way to prevent nil from being stored for a link in the file format.
Therefore, we require that Realm models defined in Swift explicitly mark to-one relationships as Optional.
class RealmMyProfile: Object {
#objc dynamic var id: Int64 = 0
#objc dynamic var profile:RealmUserProfile?
}
class RealmUserProfile: Object {
#objc dynamic var userId: Int64 = 0
#objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "userId"
}
}
You can do this solution which may save you from using the unwrapping value
Realm Issue 2814
dynamic var profile_:RealmUserProfile? = nil
var profile: RealmUserProfile {
get {
return profile_ ?? RealmUserProfile()
}
set {
profile_ = newValue
}
}

Related

In realm When ever i am updating it is updating only in 0th index how to solve it?

in realm i given id = 0 and it is as primary key and it will be auto increment, but problem is while updating it is saving in the index path 0 as declare as id : Int = 0.
Where ever i update also it is only updating in 0th index only.
i want to update as per selected object.
What to do?
Program :-
class Discount: Object {
#objc dynamic var id : Int = 0
#objc dynamic var offerName : String = ""
#objc dynamic var percentage: Float = 0.00
#objc dynamic var segmentIndex : Int = 0
#objc dynamic var dateWise: Date?
override class func primaryKey() -> String? {
return "id"
}
//Incrementa ID
func IncrementaID() -> Int{
let realm = try! Realm()
if let retNext = realm.objects(Discount.self).sorted(byKeyPath: "id").last?.id {
return retNext + 1
}else{
return 1
}
}
}
Generally speaking, auto-incrementing primary keys are challenging to deal with and can cause headaches long term.
What's generally most important is ensuring primary keys are unique and using UUID strings is ideally suited for that.
class Discount: Object {
#objc dynamic var discount_id = UUID().uuidString
override static func primaryKey() -> String? {
return "discount_id"
}
}
There may be concern about ordering and often times that managed by either adding a class var to determine ordering; like a timestamp for example or if you want to preserve ordering, objects can be added to a List, which keeps the order, like an array.
To answer your specific question, the code in your question is not complete (it was partially pulled from another question). The reason is that for each object that's created, it must be written to realm first, then the next object's primary key is based on the prior object.
Here's an example.
#objcMembers class User: Object {
dynamic var uid: Int = 0
dynamic var username: String?
func getNextUid() -> Int {
let realm = try! Realm()
if let lastObject = realm.objects(User.self).sorted(byKeyPath: "uid").first {
let lastUid = lastObject.uid
let nextUid = lastUid + 1
return nextUid
}
return 1
}
override static func primaryKey() -> String? {
return "uid"
}
}
now the sequence to use this is as follows
let u0 = User()
u0.uid = u0.getNextUid()
u0.username = "User 0"
let realm = try! Realm()
try! realm.write {
realm.add(u0)
}
let u1 = User()
u1.uid = u1.getNextUid()
u1.username = "User 1"
try! realm.write {
realm.add(u1)
}
as you can see, each object needs to be written to realm in order to the next object to be queried to get the prior objects primary key.
It's a whole lot of potentially unnecessary work and code.
My advice: Stick with the UUID().uuidString for primary keys.

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"
}
}

What's best way to prevent duplicating object in Realm?

I have next error: Attempting to create an object of type 'TypeId' with an existing primary key value '96292'.
And I got crash after this.
Using String type for the primary key instead of the Int type, and use UUID for each object, then you could avoid the duplicated keys.
class AModel: Object {
#objc dynamic var id = UUID().uuidString
override static func primaryKey() -> String? {
return "id"
}
}
Alternatively, if you want to use Int, and you are sure about that there is only one object will be created in a second, you could use timestamp value to avoid the situation too:
class AModel: Object {
#objc dynamic var id = Date().timeIntervalSince1970
override static func primaryKey() -> String? {
return "id"
}
}
Agree with #Tj3n and #EpicPandaForce's opinions, updating it if it's not a new object actually.

Nested Object Update issue in Realm

Please check the following model class of Team and Member. In a team, there is members which can hold a list of Member. A JSON object is used to create and update teams in service methods. A member can be updated individually. In Member object, there is a property lastSeenAt which is updated according to that member's activity.
class Team: Object {
dynamic var channelId: String? = nil
dynamic var title: String? = nil
let members = List<Member>()
override class func primaryKey() -> String? {
return "channelId"
}
}
class Member: Object {
dynamic var _id: String?
dynamic var lastSeenAt: Date?
override class func primaryKey() -> String? {
return "_id"
}
}
Service Methods For updating teams and members
func createAndUpdateTeams(_ teams: [[String:Any]]?) {
DispatchQueue(label: "queueNameToCreate", attributes: []).async {
let realm = try! Realm()
try! realm.write({ () in
for team in teams! {
realm.create(Team.self, value: team, update: true)
}
})
}
}
func updateMember(_ member: [String:Any]?) {
DispatchQueue(label: "queueNameToUpdateMember", attributes: []).async {
let realm = try! Realm()
try! realm.write({
realm.create(MemberRealm.self, value: member, update: true)
})
}
}
To get the real-time update on teams, I have added a notification block on Results<Team> as follows:
teams = realm.objects(Team.self);
notificationToken = teams?._addNotificationBlock({ [weak self](changes) in
switch( changes ) {
case .initial:
self?.tableView?.reloadData()
case .update(_,let deletions,let insertions,let modifications):
print("Modified: deletions: \(deletions.count),\n insertions: \(insertions.count),\n modifications:\(modifications.count)")
self?.tableView?.reloadData()
case .error:
break
}
})
Problem
When I called updateMember(_:) to update Member object, the notification block gets called. Though there is no update in the Team, the TableView load itself with same data. As a effect the screen fluctuates for each updateMember(_:) call.
Github Sample Project to generate the issue
https://github.com/milankamilya/RealmUpdateTest
What you see is the intended behaviour of Realm. Notification blocks are called by design even if only a single object is updated in a one-to-many relationship of one of the objects that is in the scope of your completion block. Since your Team class has a one-to-many relationship to Member, when setting up a notification block on all Team objects, the notification block will be and should be called each time a Member object gets updated if it is in stored in one of your Team objects members property.
See this GitHub issue explained the issue in more details.
One workaround you could use is to define members as LinkingObjects instead of a List. I haven't tried this myself, but according to the answers on linked GitHub issue, the notification block doesn't get called when an object changes that is part of LinkingObjects in contrast to a one-to-many relationship using List.
To use LinkingObjects as members, you need to change your model classes the following way:
class Team: Object {
dynamic var channelId: String? = nil
dynamic var title: String? = nil
let members = LinkingObjects(fromType: Member.self, property: "team") //finds all Member objects, where Member.team = self (this team)
override class func primaryKey() -> String? {
return "channelId"
}
}
class Member: Object {
dynamic var _id: String?
dynamic var team:Team?
dynamic var lastSeenAt: Date?
override class func primaryKey() -> String? {
return "_id"
}
}

Realm Reserved Words

I'm having an issue with Realm Swift. I have an object which is suppose to store information about the user created character. However certain properties are not saving. If I switch the name of the object just by one letter it saves and reads back correctly. The first example refuses to save anything but the default value for the race property, but the second example saves any value to the racea property with no issue. What is causing this?
Example 1
class Character: Object {
//MARK: Properties
dynamic var id: Int = 1
dynamic var name: String = "John Appleseed"
dynamic var level: Int = 1
dynamic var exp: Int = 0
dynamic var race: Int = 0
dynamic var career: Int = 0
dynamic var currentHealth: Int = 100
dynamic var inventory: Inventory? = Inventory()
//MARK: Realm
override static func primaryKey() -> String? {
return "id"
}
}
Example 2
class Character: Object {
//MARK: Properties
dynamic var id: Int = 1
dynamic var name: String = "John Appleseed"
dynamic var level: Int = 1
dynamic var exp: Int = 0
dynamic var racea: Int = 0
dynamic var career: Int = 0
dynamic var currentHealth: Int = 100
dynamic var inventory: Inventory? = Inventory()
//MARK: Realm
override static func primaryKey() -> String? {
return "id"
}
}
Extension
extension Character {
func getRace() -> String {
return Fitventure.species[race]
}
}
If the getRace() function was causing the issue, then it sounds like to me that this might be an unfortunate collision between Swift and the Objective-C runtime reflection features that Realm uses to work.
Realm dynamically generates its own accessors for each of its properties in order to explicitly manage how data is saved and retrieved from disk. As a result, it's not possible to override the accessors of Realm properties, and doing so will create strange behavior.
Best practice is what you've already discovered: when creating another method that transforms the Realm property somehow, you need to make sure the function doesn't have a name that might have been implicitly generated by the Objective-C runtime.

Resources