Realm Swift duplicates item with primary key -> Threading problem? - ios

I'm using ReamSwift to store data in my iOS application and I have troubles to figure out, why sometimes objects are duplicated in my application.
I use an UUID as primary key on every object. This property is defined in a base class and all the others are subclasses of that.
I use the update approach with a dictionary. So new objects are created and existing should be updated.
I use a central class which holds the Realm object and handles the update. This method could be called from multiple different threads.
This is my base class
import RealmSwift
open class RealmObject : Object {
static let ID_ATTRIBUTE : String = "id"
#objc dynamic var id : String = ""
override public static func primaryKey() -> String? {
return "id"
}
}
Central class managing swift.
This class holds the Realm Object
public var realm: Realm {
var tempRealm: Realm?
do {
tempRealm = try Realm(configuration: configuration)
}
catch let err {
print("------ERROR----- initializing realm\n \(err)")
}
if let tempRealm = tempRealm{
return tempRealm
}
return self.realm
}
This is the update method in the class. As a fallback if the id property is not set, it will be set as it is the primary key
func update<T: RealmObject>(_ obj : T, values : [String:Any?] ) -> T? {
var vals = values
do {
var res : T?
try realm.write {
if let idattr = vals[T.ID_ATTRIBUTE] as? String {
if(idattr.isEmpty) {
vals[T.ID_ATTRIBUTE] = UUID().uuidString
}
} else {
vals[T.ID_ATTRIBUTE] = UUID().uuidString
}
res = realm.create(T.self, value: vals, update: .modified)
}
return res
} catch {
return nil
}
}
Could calling the update method cause in any case the duplication as I set the primary key of the object? The problem is, I cannot reproduce to find the problem, i just encounter the issue from time to time and in the field from users.
One interesting thing, when a copy is deleted, also the other object will be deleted.
This is the method which deletes objects by id and type
func delete<T: RealmObject>(_ id : String, _ type : T.Type) -> Bool {
do {
let obj = get(id, T.self)
if let obj = obj {
try realm.write {
realm.delete(obj)
}
return true
} else {
return false
}
} catch {
return false
}
}

Related

How to access & get nested values from IOS Swift 'Any' type?

I am trying to read from Firestore into a Dictionary[Any] type using Struct. I can get the values loaded into variable "data" dictionary with Any type.
However I cannot loop thru it to access normal nested Dictionary variable.
I cannot get Key, values printed.
Following is my code:
class PullQuestions {
//shared instance variable
**public var data = [Any]()**
private var qdb = Firestore.firestore()
public struct questionid
{
let qid : String
var questions : [basequestion]
var answers: [baseans]
}
public struct basequestion {
let category : String
let question : String
}
public struct baseans {
let answer : String
}
class var sharedManager: PullQuestions {
struct Static {
static let instance = PullQuestions()
}
return Static.instance
}
static func getData(completion: #escaping (_ result: [Any]) -> Void) {
let rootCollection = PullQuestions.sharedManager.qdb.collection("questions")
//var data = [Any]()
rootCollection.order(by: "upvote", descending: false).getDocuments(completion: {
(querySnapshot, error) in
if error != nil {
print("Error when getting data \(String(describing: error?.localizedDescription))")
} else {
guard let topSnapshot = querySnapshot?.documents else { return }
// var questiondoc = [basequestion]()
for questioncollection in topSnapshot {
rootCollection.document(questioncollection.documentID).collection("answers").getDocuments(completion: {
(snapshot, err) in
guard let snapshot = snapshot?.documents else { return }
var answers = [baseans]()
for document in snapshot { //There should be only one Document for each answer collection
//Read thru all fields
for i in 0..<document.data().count
{
let newAns = baseans(answer: answer)
print("Answer Docs=>", (answer))
answers.append(newAns)
}
}
let qid = questioncollection.documentID
let category = questioncollection.data()["category"] as! String
let question = questioncollection.data()["question"] as! String
let newQuestions = basequestion(category: category ,question: question)
let newQuestionDict = questionid(qid: qid, questions: [newQuestions], answers: answers)
PullQuestions.sharedManager.data.append(newQuestionDict)
//Return data on completion
completion(PullQuestions.sharedManager.data)
})
}
}
})
}
}
I can print like this
print("Count =>", (PullQuestions.sharedManager.data.count))
// print(PullQuestions.sharedManager.data.first ?? "Nil")
print(PullQuestions.sharedManager.data[0])
for element in PullQuestions.sharedManager.data
{
print("Elements in data:=>", (element))
}
I could access only the key.. how do i go and get the nested values ?
First of all, consider using Swift code conventions (e.g. your structs are named with small letters, but you should start with capital), this will make your code more readable.
Returning to your question. You use an array instead of dictionary (this piece of code: public var data = [Any]()). And here you are trying to print values:
for element in PullQuestions.sharedManager.data
{
print("Elements in data:=>", (element))
}
In this context element is an Any object, thus you cannot access any underlying properties. In order to do this you have two options:
1. You should specify the type of array's objects in it's declaration like this:
public var data = [questionid]()
or you can user this:
public var data: [questionid] = []
These two are equals, use the one you prefer.
2. If for any reasons you don't want to specify the type in declaration, you can cast it in your loop. Like this:
for element in PullQuestions.sharedManager.data
{
if let element = element as? quetionid {
print("Elements in data:=>", (element))
// you can also print element.qid, element.questions, element.answers
} else {
print("Element is not questionid")
}
}
You could of course use the force cast:
let element = element as! questionid
and avoid if let syntax (or guard let if you prefer), but I wouldn't recommend this, because it (potentially) can crash your app if element will be nil or any other type.

Add values from array into Realm database

I have my Device class defined as such:
class Device: Object {
dynamic var asset_tag = ""
}
I have an array like this ["43", "24", "23", "64"]
and I would like to loop through the array and add each one into the asset_tag attribute of the Device entity in Realm.
To create an array in Realm you use List. According to Realm's docs, List is the container type in Realm used to define to-many relationships. It goes on to say, "Properties of List type defined on Object subclasses must be declared as let and cannot be dynamic." This means you need to define an entirely separate object to create a list in Realm, there are no native types that will allow you to do something like
let assetTagList = List<String>().
You need to create an AssetTags object and make a list of it in your Device object as follows:
class AssetTags: Object {
dynamic var stringValue = ""
}
class Device: Object {
var asset_tags: [String] {
get {
return _assetTags.map { $0.stringValue }
}
set {
_assetTags.removeAll()
_assetTags.append(objectsIn: newValue.map({ AssetTags(value: [$0]) }))
}
}
let _assetTags = List<AssetTags>()
override static func ignoredProperties() -> [String] {
return ["asset_tags"]
}
}
Now you can do this to add asset tags to Device.
//adding asset tags
let realm = try! Realm()
try! realm.write {
let device = Device()
device.asset_tags = ["1", "2"]
realm.add(device)
}
//looking at all device objects and asking for list of asset tags
for thisDevice in realm.objects(Device.self) {
print("\(thisDevice.asset_tags)")
}
SECOND EDIT
This is not the way I would do it but I think this is what you might find easier to understand.
class AssetTags: Object {
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
dynamic var tagValue = ""
}
class Device: Object {
let assetTags = List<AssetTags>()
}
So now Device has a list of asset tags. I added a primary id for Asset Tags because you indicated you might want to add more properties so an id should help make each one unique. It is optional though, so you can remove it if you do not need it.
To add assetTags objects to a Device object in a for-loop style (note: device in this code exampl is the device object you want to save the asset tags to):
var assetTagArray: [String] = ["1", "2"]
let realm = try! Realm()
for assetTag in assetTagArray {
let newAssetTag = AssetTags()
newAssetTag.tagValue = assetTag
newAssetTag.id = (realm.objects(AssetTags.self).max(ofProperty: "id") as Int? ?? 0) + 1
do {
try realm.write {
device?.assetTags.append(newAssetTag)
}
} catch let error as NSError {
print(error.localizedDescription)
return
}
}

Perform Realm migration from one List to another updating property value

I'm trying to count how many cards are equal inside my List and update the new quantity property with the count number
eg:
newObject!["list"] = [CardObject1, CardObject2, CardObject2,
CardObject2, CardObject3, CardObject3]
Assign to temporary list
var tempList = List()
CardObject1 (Update quantity property to 1)
CardObject2 (Update quantity property to 3)
CardObject3 (Update quantity property to 2)
tempList = [CardObject1, CardObject2, CardObject3]
Assign back to newObject!["list"] the updated/migrated list
newObject!["list"] = newList
Crash at newList.index(of: card)
* Terminating app due to uncaught exception 'RLMException', reason: 'Object type 'CardDTO' does not match RLMArray type 'DynamicObject'.'
* First throw call stack:
Info:
DeckDTO.swift
class DeckDTO: Object {
dynamic var id = NSUUID().uuidString
dynamic var name = ""
var list = List<CardDTO>()
override class func primaryKey() -> String {
return "id"
}
}
CardDTO.swift
class CardDTO: Object, Mappable {
// Other properties
dynamic var id = NSUUID().uuidString
dynamic var quantity: Int = 1
// Other properties
required convenience public init?(map: Map) {
self.init()
mapping(map: map)
}
func mapping(map: Map) {
//Map all my properties
}
override class func primaryKey() -> String {
return "id"
}
}
What I'm trying
if oldSchemaVersion < 2 {
migration.enumerateObjects(ofType: CardDTO.className()) { oldObject, newObject in
newObject!["quantity"] = 1
}
migration.enumerateObjects(ofType: DeckDTO.className()) { oldObject, newObject in
var newList = List<DynamicObject>()
let oldList = newObject!["list"] as! List<DynamicObject>
for card in oldList {
if let i = newList.index(of: card), i >= 0 {
newList[i] = (newList[i] as! CardDTO).quantity += 1 //some how do quantity++
print("increment quantity")
} else {
newList.append(card)
}
}
newObject!["list"] = newList
}
}
Realm migration blocks (and their dynamic API) aren't really well-suited for your particular use case. Neither index(of:) nor append() can be used properly with dynamic objects.
My recommendation for approaching this problem is to simply set the quantity properties to 1 in the migration block as you are doing, and then set a boolean flag that indicates that you need to perform the deck update. Then, before you do anything else (perhaps in application(_: didFinishLaunchingWithOptions:)), open the Realm and check for that flag. If that flag is set you can then open the Realm and perform the migration using the normal Realm API.
Here is some example code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Get the configuration and set the migration block on it
var config = Realm.Configuration.defaultConfiguration
config.schemaVersion = 2
var needsMigrationToV2 = false
config.migrationBlock = { migration, oldSchemaVersion in
if oldSchemaVersion < 2 {
migration.enumerateObjects(ofType: CardDTO.className()) { oldObject, newObject in
newObject!["quantity"] = 1
}
needsMigrationToV2 = true
}
}
let realm = try! Realm(configuration: config)
// Run the rest of the migration using the typed API
if needsMigrationToV2 {
let allDecks = realm.objects(DeckDTO.self)
try! realm.write {
for deck in allDecks {
var uniqueCards : [CardDTO] = []
for card in deck.list {
if uniqueCards.contains(card) {
card.quantity += 1
} else {
uniqueCards.append(card)
}
}
deck.list.removeAll()
deck.list.append(objectsIn: uniqueCards)
}
}
}
return true
}
One more thing to note is that List<T> properties should be declared as let, not var, since reassigning to a List<T> property is an error.

Write Generic Swift Method to load data from property list

I have a method that loads an array of dictionaries from a propertylist. Then I change those arrays of dictionaries to array of a defined custom type;
I want to write that method in generic form so I call that method with the type I expect, then the method loads it and returns an array of my custom type rather than dictionaries
func loadPropertyList(fileName: String) -> [[String:AnyObject]]?
{
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "plist")
{
if let plistXML = NSFileManager.defaultManager().contentsAtPath(path)
{
do {
if let temp = try NSPropertyListSerialization.propertyListWithData(plistXML, options: .Immutable, format: nil) as? [[String:AnyObject]]
{
return temp
}
}catch{}
}
}
return nil
}
//
func loadList<T>(fileName: String) -> [T]?{//**Here the answer I am expecting**}
I am assuming your function to read from a Plist works and that you don't want to subclass NSObject.
Since Swift reflecting does not support setting values this is not possible without some implementation for each Type you want this to work for.
It can however be done in a pretty elegant way.
struct PlistUtils { // encapsulate everything
static func loadPropertyList(fileName: String) -> [[String:AnyObject]]? {
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "plist") {
if let plistXML = NSFileManager.defaultManager().contentsAtPath(path) {
do {
if let temp = try NSPropertyListSerialization.propertyListWithData(plistXML, options: .Immutable, format: nil) as? [[String:AnyObject]] {
return temp
}
} catch {
return nil
}
}
}
return nil
}
}
This protocol will be used in a generic fashion to get the Type name and read the corresponding Plist.
protocol PListConstructible {
static func read() -> [Self]
}
This protocol will be used to implement Key Value setters.
protocol KeyValueSettable {
static func set(fromKeyValueStore values:[String:AnyObject]) -> Self
}
This is the combination of both to generate an array of objects. This does require that the Plist is named after the Type.
extension PListConstructible where Self : KeyValueSettable {
static func read() -> [Self] {
let name = String(reflecting: self)
var instances : [Self] = []
if let data = PlistUtils.loadPropertyList(name) {
for entry in data {
instances.append(Self.set(fromKeyValueStore: entry))
}
}
return instances
}
}
This is some Type.
struct Some : PListConstructible {
var alpha : Int = 0
var beta : String = ""
}
All you have to do is implement the Key Value setter and it will now be able to be read from a Plist.
extension Some : KeyValueSettable {
static func set(fromKeyValueStore values: [String : AnyObject]) -> Some {
var some = Some()
some.alpha = (values["alpha"] as? Int) ?? some.alpha
some.beta = (values["beta"] as? String) ?? some.beta
return some
}
}
This is how you use it.
Some.read()

Realm object as member is nil after saving

I'm facing an issue where a Realm object has another Realm object as member which is always nil after adding to the database.
class MedPack: Object {
dynamic var uuid = NSUUID().UUIDString
dynamic var medicine: Medicine?
convenience init(medicine: Medicine) {
self.init()
self.medicine = medicine
}
override static func primaryKey() -> String? {
return "uuid"
}
}
The reference to the object Medicine is always nil after adding.
class Medicine: Object {
var uuid = NSUUID().UUIDString
var name: String?
override static func primaryKey() -> String? {
return "uuid"
}
}
Creation of object
let medPack = MedPack(medicine: med)
Adding to database
static let sharedInstance = DBHelper()
var realmDb: Realm!
private init() {
realmDb = try! Realm()
}
func store(object: Object) {
try! self.realmDb.write {
self.realmDb.add(object)
}
}
After comparing this code to one of the Realm sample projects, it would appear that simply setting an Object as a child of another does not implicitly write it to the database as well.
Instead, you may need to refactor your code slightly, but make sure you explicitly add your Medicine object to Realm in a write transaction, before you set its relation to MedPack and then write MedPack to the database.

Resources