using ThreadSafeReference can only get dynamic value - ios

I have a Store, Area table, After called api get the Store Area data, then will create Store , Area table;
Goal: For render ui, i need Store with areaName. fetch stores from db and then compose to stores with areaName, then compose to a 2d array based on areaName in background thread. then pass the 2d array to render ui in main thread
Implementation:
In the background thread read Store, using areaCode get Area, then get areaName, then store.areaName = area.areaName get the composed Store, and using ThreadSafeReference to get the reference of store
In the main thread using realm.resolve to get the store in main thread, but can only get the store.address value, can not get the store.areaName
class Store: Object {
dynamic var storeCode: String = ""
dynamic var areaCode: String = ""
dynamic var address: String = ""
dynamic var phone: String = ""
var areaName: String = ""
var userId: Int = 0
}
class Area: Object {
dynamic var areaCode: String = ""
dynamic var areaName: String = ""
}
//// in background thread//////
let realmInBack = try! Realm()
let stores = getStore(by userId: Int) // here get a list of stores from db
stores.forEach{ (store) in
let area = getArea(by areaCode: String) // get area
let areaName = area.areaName // get the areaName from area
store.areaName = areaName // assign areaName to store
let storeRef = ThreadSafeReference(to: store) // get the storeRef
}
//// in main thread /////
let realm = try! Realm()
let storeInMain = realm.resolve(storeRef)
// then use storeInMain to render ui
Could please list the ways to pass realm object through threads?
I currently know the following ways:
1. ThreadSafeReference(failed in my case)
2. transform the realm object to normal class by myself

For this simple case, you don't actually need to use a ThreadSafeReference object. You should just save a property of your object to a variable you can use to identify that specific object (preferably its primaryKey if you are using primaryKeys), then retrieve the object using its primary key from Realm on the main thread.
var storeCodeRef = "" //make sure this variable is accessible in the scope of where your code on the main thread is called from as well
let realmInBack = try! Realm()
let stores = getStore(by userId: Int) // here get a list of stores from db
stores.forEach{ (store) in
let area = getArea(by areaCode: String) // get area
let areaName = area.areaName // get the areaName from area
store.areaName = areaName // assign areaName to store
//save the unique identifier for the store
storeCodeRef = store.storeCode
}
//// in main thread /////
let realm = try! Realm()
let store = realm.object(ofType: Store.self).filter("storeCode == %#",storeCodeRef).first //assuming storeCodes are unique
If you are using a primary key, you can use
let store = realm.object(ofType: Store.self, forPrimaryKey: storeCodeRef)
Since you declare a new reference to your Realm instance on the main thread and access the Store object from that reference, you won't receive any errors.
Update based on comments:
Your Class model is flawed. All properties that you want to persist in Realm need to be declared using the dynamic keyword, otherwise they cannot be dynamically dispatched to the Obj-C runtime, which Realm uses.
class Store: Object {
dynamic var storeCode: String = ""
dynamic var areaCode: String = ""
dynamic var address: String = ""
dynamic var phone: String = ""
dynamic var areaName: String = ""
dynamic var userId: Int = 0
}
Second of all, why do you store ThreadSafeReferences to individual stores if you want to modify the actual Store objects, you can do that in a write transaction and store a ThreadSafeReference to the Results instance, so you only need to resolve a single reference and not one for each individual Store.
Below code has been tested and is working in a Realm playground.
class Store: Object {
dynamic var storeCode: String = ""
dynamic var address: String = ""
dynamic var phone: String = ""
dynamic var userId: Int = 0
dynamic var areaCode: String = ""
dynamic var areaName: String = ""
}
class Area: Object {
dynamic var areaCode: String = ""
dynamic var areaName: String = ""
}
try! realm.write {
realm.add([Store(value: ["userId":1,"storeCode":"1"]),Store(value: ["userId":1,"storeCode":"2"]),Store(value: ["userId":2,"storeCode":"1"])])
}
print(realm.objects(Store.self))
//// in some thread//////
var storesRef: ThreadSafeReference<Results<Store>>?
DispatchQueue(label: "background").sync {
autoreleasepool{
let realmInBack = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "TemporaryRealm"))
let stores = realmInBack.objects(Store.self).filter("userId == 1")
print("Stores for user 1:",stores)
try! realmInBack.write {
let area = Area(value: ["areaCode":"a","areaName":"AreaA"])
realmInBack.add(area)
stores.forEach{ (store) in
store.areaCode = area.areaCode
}
}
print("Modified stores:",stores)
storesRef = ThreadSafeReference(to: stores)
}
}
//// in main thread /////
DispatchQueue.main.async {
let mainRealm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "TemporaryRealm"))
guard let storesRef = storesRef, let storeInMain = mainRealm.resolve(storesRef) else {
print("Couldn't resolve stores reference"); return
}
print("Stores from ThreadSafeReference:",storeInMain)
}

Related

How to create model like firebase structure using RealmSwift in iOS swift?

I have been trying to create model like firebase database structure. I can able to create normal string, bool and int value but not able to do array and dictionary.
Here is my firebase structure screenshot:
Here i am trying to add groupMembers and to in model like firebase structure.
Here is my Model i tried to create with array and dictionary:
import Foundation
import RealmSwift
class RealmMessages: Object {
#objc dynamic var messageText : String?
#objc dynamic var sender: String?
let chatCreatedDateTimee = List<timeStampValue>()
#objc dynamic var chatId: String?
#objc dynamic var from: String?
#objc dynamic var groupMemberss : [String: String]!
let groupMemebersCount = RealmOptional<Int>()
#objc dynamic var task: Bool = false
#objc dynamic var to: Array = [String]()
}
class timeStampValue: Object {
let timestamp = RealmOptional<Int>()
}
Here is my contoller code: Trying to add value into realm database.
var dic : [String : String] = [:]
var cont = ["one", "two", "three"]
var oneVal = ["909090": "SELF", "808080": "Other"]
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
print("realm location:::\(String(describing: Realm.Configuration.defaultConfiguration.fileURL))")
let myMessage = RealmMessages()
myMessage.messageText = "Diva"
myMessage.sender = "yIvq1mQxjfZpjs1ybRTTlDOmUKV2"
let timevalue = timeStampValue()
timevalue.timestamp.value = 123123131
myMessage.chatId = "+918000080000"
myMessage.from = "+918000080000"
myMessage.groupMemberss = oneVal
myMessage.to = cont
try! realm.write {
realm.add(myMessage)
}
}
How to get groupMemberss and to structure in realm database like firebase. And how to create array and dictionary in realm
There are a number of solutions but here's two.
Assuming the data has been read in and the data from the groupMembers snapshot is sitting in a dictionary var that looks like this
let groupMembersDict = [
"919": "participant",
"111": "observer",
"222": "participant"
]
To store that in Realm, you can work with primitives and store each key and value in a separate List (think: Array) or you can leverage a managed Realm object and store those in a List.
If you want to keep the data within an object; here's what it would look like.
class GroupData: Object {
#objc dynamic var num = ""
#objc dynamic var type = ""
convenience init(withNum: String, andType: String) {
self.init()
self.num = withNum
self.type = andType
}
}
Here's the main object showing both options; either option 1: store the key value pairs in two arrays, or option 2: use the above GroupData object to store the key value pairs together
class Messages: Object {
#objc dynamic var messageText = ""
//option 1: two lists of primative strings that can be accessed like an array.
// downside is managing two lists
let groupNum = List<String>()
let groupType = List<String>()
//option 2: a list of members using another Realm object
let groupNumType = List<GroupData>()
}
And some code to create two messages, one of each type
let msg0 = Messages()
msg0.messageText = "My message"
for member in groupMembersDict {
msg0.groupNum.append( member.key )
msg0.groupType.append( member.value )
}
let msg1 = Messages()
msg1.messageText = "This message"
for member in groupMembersDict {
let aGroup = GroupData(withNum: member.key, andType: member.value)
msg1.groupNumType.append(aGroup)
}
store them in realm
realm.add(msg0)
realm.add(msg1)
read them both in an display the message from option 2. Option 1 would be just iterating over the arrays to print the group data
let messageResults = realm.objects(Messages.self)
for msg in messageResults {
print(msg.messageText)
for group in msg.groupNumType {
print(group.num, group.type)
}
}
Keep in mind that all managed properties must be primitives: NSString, NSDate, NSData, NSNumber or List, Results, RLMLinkingObjects, or subclasses of RLMObject like the GroupData shown above.

Preserving Realm property value when updating object

I'm working on a Swift 3 Realm app and I have this Realm model class:
class User: Object {
dynamic var userName = ""
dynamic var userURL = ""
dynamic var userThumbnailURL = ""
dynamic var isRegistered = false
var userID = RealmOptional<Int>()
override class func primaryKey() -> String? {
return "userID"
}
}
Then I'm adding values as follows. I'm getting them from my server and saving them in the local database:
Query 1
let user = User()
user.userName = fields["userName"] as! String
user.userURL = fields["userURL"] as! String
user.userThumbnailURL = fields["userThumbnailURL"] as! String
user.userID.value = fields["userID"] as? Int
try! uiRealm.write {
uiRealm.add(user, update: true)
}
Then when the user finishes the registration I'm updating the specific user in the local Realm database as being a registered user (isRegistered = true). This value is only saved in the local Realm database:
uiRealm.beginWrite()
let updatingUser = uiRealm.objects(User).filter("userID = %d", self.userId)
let user = updatingUser.first
book?.isRegistered = true
try! uiRealm.commitWrite()
But my problem is that when the new server response is received and I re-run Query 1 the isRegistered property becomes false. How can I avoid this?
The short answer is that partial updates of an object cannot be performed by passing an instance of an Object subclass to Realm.add(_:update:). The reason for this is that there's no way to represent don't update this property for a property on your Object subclass. You'll instead want to pass a dictionary to Realm.create(_:value:update:), where the absence of a given key represents don't update this property.

Realm crash when try to deleting children Objects of type Object

I Have one class UserObject(Object) that has one property of SessionObject(Object), and the SessionObject has properties of other Realm Objects(TestObject, NewObject).
When I delete the children Objects and after that the parent Object successfully, the app crashes with Bad Access: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
class UserObject: Object{
dynamic var uuid = ""
dynamic var username = ""
dynamic var session: SessionObject?
class SessionObject: Object{
dynamic var tokenType = ""
dynamic var refreshToken = ""
dynamic var test: TestObject?
dynamic var new: NewObject?
}
class TestObject: Object{
dynamic var test = ""
}
class NewObject: Object{
dynamic var test = ""
}
func deleteUser(){
guard let realm = self.realmInstance else{
return
}
guard let user = Array(realm.objects(UserObject.self)).first else{
return
}
do{
try realm.write {
if let session = user.session{
if let test = session.test{
realm.delete(test)
}
if let new = session.new{
realm.delete(new)
}
realm.delete(session)
}
realm.delete(user)
}
}catch{
}
}
You're running in to ARC naming conventions, which apply to dynamic properties on Swift classes. Currently Realm does not generate accessor methods which conform to what ARC expects for specially-named methods, which results in objects being double-deleted when you have a property name starting with new, copy or mutableCopy.

RealmSwift - How can I store data in list

I'm new in programming and I would like to know how I can store data in a List with RealmSwift.
Considering the following model:
import RealmSwift
class ScanResults: Object{
dynamic var id = 0
dynamic var resource = ""
dynamic var scanDate = ""
let ScanResultsDetail = List<ScanResultsDetails>()
}
class ScanResultsDetails: Object{
dynamic var scanner = ""
dynamic var result = ""
}
This is an example how I store new ScanResults:
let newResults = ScanResults()
newResults.id = newResults.IncrementaID()
newResults.resource = "Test"
newResults.scanDate = "19.01.2016"
do{
try uiRealm.write({ () -> Void in
uiRealm.add(newResults)
})
}
catch{
}
My Question is now, how can I store data in the list? I can't figure it out.. Can you give me an example?
I don't see that you append any object to ScanResultsDetail in your example
Here is quick example based on swift source code (docs):
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
}
class Person: Object {
dynamic var name = ""
let dogs = List<Dog>()
}
let realm = try! Realm() // Create realm pointing to default file
// Link objects
let person = Person()
person.name = "Tim"
person.dogs.append(mydog)
try! realm.write {
realm.add(person)
}

Insert object in RLMArray property on already persisted object?

I have a fairly simple and straightforward issue I'm trying to solve with Realm. I have objects which have an array property on them (threads). When I fetch all the threads via our API, they're all persisted into Realm since the parent objects are individually saved and thus all child objects (messages & users) within the array property are properly persisted as well. But during the lifecycle of the app I need to add new messages into that array property. Here's what I'm attempting to do:
func addPubNubMessageToThread(notification: NSNotification) {
if let info = notification.userInfo as? Dictionary<String, AnyObject> {
var embeddedMessage = Message(json: (info["data"] as? NSDictionary)!)
let threadId = (info["thread"]! as String)
// Persist the message to Realm for future use
var respectiveThread = Thread(forPrimaryKey: threadId)
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
respectiveThread.conversation.insertObject(embeddedMessage, atIndex: UInt(0)) // Always fails here in XCode with the error below
realm.addOrUpdateObject(respectiveThread)
realm.commitWriteTransaction()
}
}
But each time I get the following error:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Setting unique property '_id' with existing value '540729b543dd5d1868a42b5d''
For more context, here are my Realm models:
class Message: RLMObject {
dynamic var _id = ""
dynamic var type = ""
dynamic var text = ""
dynamic var author = User()
dynamic var created = NSDate()
dynamic var lastUpdated = NSDate()
}
class Thread: RLMObject {
dynamic var _id = ""
dynamic var name = ""
dynamic var conversation = RLMArray(objectClassName: Message.className())
dynamic var participants = RLMArray(objectClassName: User.className())
dynamic var created = NSDate()
dynamic var lastUpdated = NSDate()
}
class User: RLMObject {
dynamic var _id = ""
dynamic var name = ""
dynamic var firstName = ""
dynamic var lastName = ""
dynamic var email = ""
dynamic var phone = ""
dynamic var username = ""
dynamic var avatar = NSData()
dynamic var created = NSDate()
dynamic var lastUpdated = NSDate()
}
Each message has a property called author, and the _id it's complaining about is the _id of the author (or user object) of the message. The error message is hard to decipher. I think it's saying that I'm trying to create a new user object with a primary key that already exists. If that's the issue, what should I do instead to add new Realm objects to an array property on an already persisted object?
Edit
I am setting the primary key for each model like so:
override class func primaryKey() -> String {
return "_id"
}
And _id is a GUID generated by MongoDB...so it's globally unique.
The problem is that when calling
insertObject
your object and all child objects are created in the Realm instead of updated if they already exist. If you first explicitly update your object (which applies to all child objects as well), then this should avoid the issue:
var persistedMessage = realm.addOrUpdateObject(embeddedMessage)
respectiveThread.conversation.insertObject(persistedMessage, atIndex: UInt(0))
Your problem is probably in your the Message(json:) initializer. When you process the JSON for the message, I'm guessing the author ID is passed (and perhaps the rest of the author data). In your initializer you are probably instantiating a new User, rather than getting an instance of the existing User in the Realm. I recreated your model objects, and then created an existing user, thread and message. I was able to deserialize a message from JSON and add the new message to the existing conversation array, as long as my deserialization logic grabbed an existing user if it was provided. Here is my example initializer:
init(json : NSDictionary) {
self._id = json["_id"] as String
self.type = json["type"] as? String ?? ""
self.text = json["text"] as? String ?? ""
self.created = json["created"] as? NSDate ?? NSDate()
self.lastUpdated = json["lastUpdated"] as? NSDate ?? NSDate()
if let author = json["author"] as? NSDictionary {
if let authorId = author["_id"] as? String {
self.author = User(forPrimaryKey: authorId)
}
}
super.init()
}
In this case, my example JSON:
{
"_id": "2",
"text": "new message",
"author": {"_id": "1"}
}
And finally, the code that reads the JSON and adds to an already saved thread:
var error: NSError?
var jsonDictionary : NSDictionary!
if let dictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.allZeros, error: &error) as? NSDictionary {
jsonDictionary = dictionary
} else {
NSLog("\(error)")
return
}
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
let newMessage = Message(json: jsonDictionary)
self.thread.conversation.addObject(newMessage)
realm.addOrUpdateObject(self.thread)
realm.commitWriteTransaction()

Resources