Why does the Realm Set object work at random? - ios

The function to use a set of Realm objects is always random.
Primary keys must not be changed and they must be unique.
So I added another variable for compare.
And I override isEqual(:) function.
See below my code.
class Model: Object {
#objc dynamic var key = ""
#objc dynamic var id = ""
override static func primaryKey() -> String? {
return "key"
}
override func isEqual(_ object: Any?) -> Bool {
if let object = object as? Model {
return id == object.id
} else {
return false
}
}
}
let model1 = Model()
model1.key = UUID().uuidString
model1.id = "hi"
let model2 = Model()
model2.key = UUID().uuidString
model2.id = "hi"
let model1Array = [model1]
let model2Array = [model2]
let set1 = Set(model1Array)
let set2 = Set(model2Array)
let result = set1.intersection(set2)
print(result) // []
let result = set1.intersection(set2)
print(result) // [Model { key = 9E814B97-D0CC-4550-BF7B-19645C1DB746; id = hi; }]
let result = set1.intersection(set2)
print(result) // []
let result = set1.intersection(set2)
print(result) // []
let result = set1.intersection(set2)
print(result) // [Model { key = 8A399388-1FA2-4699-8258-5DA5DFCEC203; id = hi; }]
Every time I run, the values come out randomly.
What did I do wrong?

For Set to work correctly, your objects need to have a correct implementation of Hashable. The Realm Object already implements Hashable, and presumably, the == implementation calls isEqual.
However, the hash should be consistent with isEqual as well, but you haven't overridden hash yet. You should implement hash such that two equal objects (as determined by isEqual) have equal hashes.
One way is to do it like this:
override var hash: Int {
return id.hash
}

Related

Realm model contains property but not found it at runtime

I am getting error as below
Invalid property name , reason: 'Property 'IsRecordDeleted' not found in object of type 'MyCustomModel'
Where as my Model is as Under
#objcMembers public class MyCustomModel : Object {
dynamic var Id : String = ""
dynamic var ProductId : String? = ""
dynamic var IsRecordDeleted : Bool? = false
dynamic var ProductBarcode : String? = ""
override public class func primaryKey() -> String? {
return "Id"
}
}
and I am making query like this :
let mSavedItems = mDbHelper.realmObj.objects(MyCustomModel.self).filter("IsRecordDeleted = false")
What could be problem here. I do not know why my app is crashing with the same error. But If I change the value like
let mSavedItems = mDbHelper.realmObj.objects(MyCustomModel.self).filter("ProductId = 0")
The app gets run, but crashed on when I use IsRecordDeleted in predicate.
Please tell me what could be problem
You can try
let mSavedItems = mDbHelper.realmObj.objects(MyCustomModel.self)
let filtered = mSavedItems.filter { $0.IsRecordDeleted == false }
and
let mSavedItems = mDbHelper.realmObj.objects(MyCustomModel.self)
let filtered = mSavedItems.filter { $0.ProductId == "0" }
For both
let mSavedItems = mDbHelper.realmObj.objects(MyCustomModel.self)
let filtered = mSavedItems.filter {
$0.IsRecordDeleted == false
&& $0.ProductId == "0"
}
//
let resultPredicate = NSPredicate(format: "ProductId == '0' AND IsRecordDeleted == false")
let filtered = mSavedItems.filter(resultPredicate)
I think you'll find that simply switching to filter blocks instead of string predicates might stop the crash, but will not produce the expected results.
This is because IsRecordDeleted never gets saved to the database. It is not a type that can be represented in Objective-C, therefore it cannot be dynamic, so Realm ignores it.
Take as an example the following class:
#objcMembers class MyObject: Object {
dynamic var id = ""
dynamic var testBool: Bool? = false
override static func primaryKey() -> String {
return "id"
}
}
And say we initialize them like this:
let obj1 = MyObject()
obj1.id = "1"
obj1.testBool = true
let obj2 = MyObject()
obj2.id = "2"
obj2.testBool = false
let realm = try? Realm()
try? realm?.write {
realm?.add(obj1, update: true)
realm?.add(obj2, update: true)
}
If we query Realm for these objects using realm.objects(MyObject.self), you'll get something like this
Results<MyObject> <0x7fe410c0ad90> (
[0] MyObject {
id = 1;
},
[1] MyObject {
id = 2;
}
)
And you'll see that in the database, there indeed is no property named testBool, which was our optional Bool.
You can easily see that the optional Bool may cause problems if you write it out like this instead:
class MyObject: Object {
#objc dynamic var id = ""
#objc dynamic var testBool: Bool? = false // This line will not compile.
override static func primaryKey() -> String {
return "id"
}
}
I'm curious why the IsRecordDeleted needs to be optional in the first place, since it seems to have a value from the get-go. If it doesn't, then something like this will work as expected:
#objcMembers public class MyCustomModel: Object {
dynamic var Id: String = ""
dynamic var ProductId: String? = ""
dynamic var IsRecordDeleted: Bool = false
dynamic var ProductBarcode: String? = ""
override public class func primaryKey() -> String? {
return "Id"
}
}
and you can query via string like you were trying to do in the first place.
If it has to be optional, then Realm provides a RealmOptional for this exact case, that you can look into here.

Realm Typecast issue Swift

I have to declare realm string property for to save the value get from API, but the issue is, I don't know which type of data will come from the server.
Sometimes I am getting String value and sometime Int.
Now how I will save data to the realm.
class Fields: Object {
#objc dynamic var default_value: String? = nil
}
API Response
{
access = 1;
default_value = " ";
},
{
access = 1;
default_value = 20;
}
This is the safest (where stringOrInt is the value you're receiving from the API):
fieldsObject.default_value = stringOrInt as? String
But you can also use string interpolation and inject the value directly into a string literal:
fieldsObject.default_value = "\(stringOrInt)"
You can try this solution
1- Relam object class
class Fields: Object {
#objc dynamic private var default_value: String? = nil
#objc var defaultValue: Any?{
didSet{
self.default_value = "\(defaultValue!)"
}
}
open override class func ignoredProperties()->[String] {
return ["defaultValue"]
}
}
1- Test add object in you'r DB
let obj = Fields()
obj.defaultValue = "ahmad"
let obj2 = Fields()
obj2.defaultValue = 1
let realm = try! Realm()
try! realm.write {
realm.add([obj,obj2])
}
3- Result

Is it possible to use dynamic variable for a class member variable

I have a class like
class Person {
var address:String
var number:String
var houseNo:String
var licenceNo:String
....
}
let jone = Person()
jone.number = "123456"
So in this i need to initialize the variable of person class one by one. And i have approx 30 variables in person class.
Is not there s simple way to do this ?
like I have all the keys coming from backend like "number = 123456". Is not there a way that i run a for loop and use something like.
for key in keys {
john."\(key)" = dict[key]
}
Is not there a way to shorten this lengthy procedure ?
You can try out this code
extension NSObject{
// fetch all class varible
func property() -> Mirror.Children {
return Mirror(reflecting: self).children
}
func propertyList() {
for (name, value) in property() {
guard let name = name else { continue }
print("\(name): \(type(of: value)) = '\(value)'")
}
}
}
Your class, set value like below code it's helpfull
class Person: NSObject {
var address:String = ""
var number:String = ""
var houseNo:String = ""
var licenceNo:String = ""
init(with response: [String: AnyObject]) {
for child in self.property() {
if let key = child.label, let value = response[key] {
self.setValue(value, forKey: key)
}
}
}
}
person.propertyList()
// Display all class property list
address: String = ''
number: String = ''
houseNo: String = ''
licenceNo: String = ''
Why don't use a Person method for load the backend response into a new Person object?
Somethind like that:
let jone = Person()
jone.loadDataFrom(response)
Or use a Person static method
let jone = Person.loadDataFrom(response)
static func loadDataFrom(response:Object) -> Person {
let p = Person()
...
set response data
...
return p
}

Create dictionary from an array of objects using property of object as key for the dictionary?

With Swift is it possible to create a dictionary of [String:[Object]] from an array of objects [Object] using a property of those objects as the String key for the dictionary using swift's "map"?
class Contact:NSObject {
var id:String = ""
var name:String = ""
var phone:String = ""
init(id:String, name:String, phone:String){
self.id = id
self.name = name
self.phone = phone
}
}
var contactsArray:[Contact]
var contactsDict:[String:Contact]
contactsDict = (contactsArray as Array).map { ...WHAT GOES HERE... }
Let's say you want to use id as the key for the dictionary:
var contactsArray = [Contact]()
// add to contactsArray
var contactsDict = [String: Contact]()
contactsArray.forEach {
contactsDict[$0.id] = $0
}
The difference between map and forEach is that map returns an array. forEach doesn't return anything.
You can achieve this via reduce in a one-line functional-style code:
let contactsDict = contactsArray.reduce([String:Contact]()) { var d = $0; d[$1.id] = $1; return d; }
This also keeps contactsDict immutable, which is the preferred way to handle variables in Swift.
Or, if you want to get fancy, you can overload the + operator for dictionaries, and make use of that:
func +<K,V>(lhs: [K:V], rhs: Contact) -> [K:V] {
var result = lhs
result[rhs.0] = rhs.1
return result
}
let contactsDict = contacts.reduce([String:Contact]()) { $0 + ($1.id, $1) }
Swift 4
There's now a direct way to do this:
https://developer.apple.com/documentation/swift/dictionary/3127163-init
It's an initializer for Dictionary that lets you return a string key for each element in a Collection that specifies how it should be grouped in the resulting Dictionary.

How to access property or method from a variable?

Is it possible to access a method or property using a variable as the name of the method or property in Swift?
In PHP you can use $object->{$variable}. For example
class Object {
public $first_name;
}
$object = new Object();
$object->first_name = 'John Doe';
$variable = 'first_name';
$first_name = $object->{$variable}; // Here we can encapsulate the variable in {} to get the value first_name
print($first_name);
// Outputs "John Doe"
EDIT:
Here is the actual code I'm working with:
class Punchlist {
var nid: String?
var title: String?
init(nid: String) {
let (result, err) = SD.executeQuery("SELECT * FROM punchlists WHERE nid = \(nid)")
if err != nil {
println("Error")
}
else {
let keys = self.getKeys() // Get a list of all the class properties (in this case only returns array containing "nid" and "title")
for row in result { // Loop through each row of the query
for field in keys { // Loop through each property ("nid" and "title")
// field = "nid" or "title"
if let value: String = row[field]?.asString() {
// value = value pulled from column "nid" or "title" for this row
self.field = value //<---!! Error: 'Punchlist' does not have a member named 'field'
}
}
}
}
}
// Returns array of all class properties
func getKeys() -> Array<String> {
let mirror = reflect(self)
var keys = [String]()
for i in 0..<mirror.count {
let (name,_) = mirror[i]
keys.append(name)
}
return keys
}
}
You can do it, but not using "pure" Swift. The whole point of Swift (as a language) is to prevent that sort of dangerous dynamic property access. You'd have to use Cocoa's Key-Value Coding feature:
self.setValue(value, forKey:field)
Very handy, and it crosses exactly the string-to-property-name bridge that you want to cross, but beware: here be dragons.
(But it would be better, if possible, to reimplement your architecture as a dictionary. A dictionary has arbitrary string keys and corresponding values, and thus there is no bridge to cross.)
Subscripting may help you.
let punch = Punchlist()
punch["nid"] = "123"
println(punch["nid"])
class Punchlist {
var nid: String?
var title: String?
subscript(key: String) -> String? {
get {
if key == "nid" {
return nid
} else if key == "title" {
return title
}
return nil
}
set {
if key == "nid" {
nid = newValue
} else if key == "title" {
title = newValue
}
}
}
}

Resources