Optimal method to query specific items - swift

I have a list of
Sensor
Items in my realm database.
class Sensor: Object {
#objc dynamic var sensorID: Int8 = 0
#objc dynamic var value: Int16 = 0
#objc dynamic var timestamp: Int64 = 0
#objc dynamic var location: Location?
#objc dynamic var isIndoor: Bool = true
#objc dynamic var synced: Bool = false
}
What is the most optimal method to get all items, that are newer than 24 hours.
I wrote this method
func getSensors() -> [Sensor] {
guard let items = RealmManager.shared.getObjectsWith(type: Sensor.self), let sensors = Array(items) as? [Sensor] else {
return []
}
return sensors.filter({$0.value != -1 && Date(timeIntervalSince1970: TimeInterval($0.timestamp)).differenceInHours() < 24})
}
but if I have about 100000 items, it first gets all that items and after that makes a filter, which is not optimal and takes a long time.
How can I write query to get all items, with newer than 24 hours?
Thanks.

Related

How to save and load GKGameModelPlayer from Realm in Swift?

I am attempting to implement a GKGameModel in my application. In it, it holds variables to a few things, but for the purposes of my question I'm interested in the following two variables:
import GameplayKit
final class GameModel: NSObject, GKGameModel {
var players: [GKGameModelPlayer]?
var activePlayer: GKGameModelPlayer?
}
I do something like this to initialise the game with 3 players (not exact)
let game = GameModel.init()
game.players = [Player(),Player(),Player()] // Create 3 players
guard let firstPlayer = game.players.first else {
return
}
game.activePlayer = firstPlayer
A player class is defined as:
class Player : NSObject, GKGameModelPlayer {
var playerId: Int // GKGameModelPlayer protocol variable
let name: String
var cash: Int = 0
}
In my project I have Realm Entities and the models seperated. So there will be a PlayerEntity and a Player class.
I'm wanting to use RealmSwift to save and load the GKGameModelPlayer data, and more specifically the ability to store/re-store the active player.
I think the key here is the playerId variable; but I am not sure.
But what I'm not sure about is retrieving this information and then re-mapping it into a valid GKGameModelPlayer format
My current idea/theory is that I need to map my model to an entity class and vice-versa.
Ie:
// [REALM] Player entity
class PlayerEntity: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var playerId: Int = 0
#objc dynamic var name: String = ""
#objc dynamic var cash: Int = 0
override static func primaryKey() -> String {
return "id"
}
}
And then I extend this class to do some "mapping":
extension PlayerEntity {
// Map model -> entity
convenience init(model: Player) {
self.init()
self.playerId = model.playerId
self.name = model.name
self.cash = model.cash
}
}
extension Player {
// Map entity -> model
convenience init(entity: PlayerEntity) {
let playerId = entity.playerId
let name = entity.name
let cash = entity.cash
self.init(id: playerId, name: name, cash: cash)
}
}
Right now, the playerId is always zero (0) because I'm not really sure how to set it.
I can save a player to realm.
The issue comes from when I try to restore the player, and I want to restore the activePlayer variable in the GameModel
Therefore, my question is:
How would I go about saving and restoring the activePlayer variable so that it continues to comply to GKGameModelPlayer?
I appreciate any assistance on this.
With thanks
While you could use those extensions, sometimes simpler is better. Here's a rough example:
class PlayerEntity: Object {
#objc dynamic var playerId: Int = 0
#objc dynamic var name: String = ""
#objc dynamic var cash: Int = 0
convenience init(withPlayer: PlayerClass) {
self.init()
self.playerId = withPlayer.playerId
self.name = withPlayer.name
self.cash = withPlayer.cash
}
func getPlayer() -> Player {
let p = Player()
p.playerId = self.playerId
p.name = self.name
p.cash = self.cash
return p
}
override static func primaryKey() -> String {
return "playerId"
}
}
to load all the players into an array... this will do it
let playerResults = realm.objects(PlayerEntity.self)
for player in playerResults {
let aPlayer = player.getPlayer()
self.playerArray.append(aPlayer)
}
Notice the removal of
#objc dynamic var id = UUID().uuidString
because it's not really being used to identify the object as a primary key.
The primary key is really
var playerId: Int // GKGameModelPlayer protocol variable
which is fine to use as long as it's unique.

Properties in Realm results are nil

I don't understand why I am failing to print out the properties of my realm result. when I print existingCart[i].${property}, it always give me a default value for all iteration i.
the statement print(existingCart) has no problem. It contains non default values in the properties. It returns:
Results<Product> <0x7fe5d8f3a770> (
[0] Product {
itemImage = <ffd8ffe0 00104a46 49460001 01000048 00480000 ffe10058 — 232200 total bytes>;
itemName = Ground Beef;
price = 6.53;
unit = 450 g;
quantity = 1;
isInCart = 0;
},
[1] Product {
itemImage = <ffd8ffe0 00104a46 49460001 01000048 00480000 ffe1004c — 153015 total bytes>;
itemName = Chicken Drumsticks;
price = 9.06;
unit = 1.25 kg;
quantity = 1;
isInCart = 0;
},
[2] Product {
itemImage = <ffd8ffe0 00104a46 49460001 01000048 00480000 ffe10058 — 242980 total bytes>;
itemName = Ground Turkey;
price = 6.91;
unit = 450 g;
quantity = 2;
isInCart = 0;
},
[3] Product {
itemImage = <ffd8ffe0 00104a46 49460001 01000048 00480000 ffe10058 — 224128 total bytes>;
itemName = Canned Beans;
price = 1.79;
unit = 398 mL;
quantity = 1;
isInCart = 0;
},
[4] Product {
itemImage = <ffd8ffe0 00104a46 49460001 01000048 00480000 ffe10058 — 252231 total bytes>;
itemName = Frosted Flakes;
price = 9.49;
unit = 1.06 kg;
quantity = 1;
isInCart = 0;
},
[5] Product {
itemImage = <ffd8ffe0 00104a46 49460001 01000048 00480000 ffe1004c — 165948 total bytes>;
itemName = Gouda Cheese;
price = 4.99;
unit = 300 g;
quantity = 1;
isInCart = 0;
}
)
This is how I try to load the data from Realm:
private func loadExistingCart() {
let realm = try! Realm()
let existingCart = realm.objects(Product.self)
print(existingCart) // see above
for i in 0..<existingCart.count {
print(existingCart[i].itemName) // empty string
print(existingCart[i].price) // 0.0
print(existingCart[i].quantity) // 0
print(existingCart[i].unit) // empty string
cart.addItem(existingCart[i]) // adds
}
}
Here is the product class:
import UIKit
import Foundation
import Realm
import RealmSwift
class Product : Object {
// MARK: Properties
#objc var itemImage = Data()
#objc var itemName: String = ""
#objc var price: Float = 0.0
#objc var unit: String = ""
#objc var quantity: Int = 0
#objc var isInCart: Bool = false
convenience init?(itemImage: UIImage?, itemName: String, price: Float, unit: String, quantity: Int, isInCart: Bool = false) {
self.init()
// itemName, unit, category should not be empty
guard (!itemName.isEmpty && !unit.isEmpty) else {
return nil
}
// price should not be a negative number
guard (price >= 0) else {
return nil
}
// Initialize stored properties
self.itemImage = UIImageJPEGRepresentation(itemImage!, 0.9)!
self.itemName = itemName
self.price = price
self.unit = unit
self.quantity = quantity
self.isInCart = isInCart
}
override class func primaryKey() -> String? {
return "itemName"
}
}
This is how products are being saved
private func saveToCart(product: Product, update: Bool) {
let realm = try! Realm()
print(product)
try! realm.write {
realm.add(product, update: true)
}
}
You've made a tiny error in the declaration of the Product class there, and it's just a common gotcha with using Realm.
If you look back over all the Realm examples in their documentation, you'll see that each member field should be declared such as this corrected one:
#objc dynamic var itemImage = Data()
Yep, you've missed out the dynamic modifier on each of your properties. Go back and add that to each property and it should just start working. For completeness, this should be the properties declaration:_
// MARK: Properties
#objc dynamic var itemImage = Data()
#objc dynamic var itemName: String = ""
#objc dynamic var price: Float = 0.0
#objc dynamic var unit: String = ""
#objc dynamic var quantity: Int = 0
#objc dynamic var isInCart: Bool = false
You can google the dynamic keyword for an explanation. It's based on the fact that the object you have is just a proxy to a database object, and Realm needs to intercept the property access to actually interrogate the database and retrieve the value of the property. You can see the same effect as your problem if you ever try to look at an object in the debugger - it's just full of the default values.
I'm not familiar enough with how Realm works to say why you can't seem to access the objects by index, but perhaps you could try accessing them as part of a Sequence instead?
let existingCart = realm.objects(Product.self)
for item in existingCart {
print(item.itemName)
print(item.price)
print(item.quantity)
print(item.unit)
}

Filter Realm Results array returns elements that should be filtered

Any ideas why this filter is not working correctly ?
for item in activeItems {
print("item.product: \(item.product), \(item.spaceRequired)")
}
returns
item.product: nil, 40.0
Filtering where product is nil
let f1 = activeItems.filter{$0.product != nil}
print("f1: \(f1)")
print("f1.count: \(f1.count)")
returns a count of ZERO but the array still appears to contain an item
f1: LazyFilterSequence<Results<AssortmentItem>>(_base: Results<AssortmentItem> <0x109ce2c90> (
[0] AssortmentItem {
...
f1.count: 0
And then filtering and mapping just spaceRequired
let f11 = f1.filter{$0.product!.isProduct == true}.map({$0.spaceRequired})
print("f11: \(f11)")
returns the same array with a single item
f11: LazyMapSequence<LazyFilterSequence<Results<AssortmentItem>>, Double>(_base: Swift.LazyFilterSequence<RealmSwift.Results<Merchandise_Manager.AssortmentItem>>(_base: Results<AssortmentItem> <0x109ce2c90> (
[0] AssortmentItem {
And then trying to reduce crashes
let w = f11.reduce(0,+)
This seems to fix the problem
let width = Array(activeItems.filter{$0.product != nil}).filter{$0.product!.isProduct == true}.map({$0.spaceRequired}).reduce(0,+)
Is this a bug in Swift 5 or in Realm ?
EDIT: It looks like this is a bug in Realm's handling of things.
To be a bit cleared below is a more complete set of the Realm objects.
import Foundation
import RealmSwift
let activeDate: NSDate = Date() as NSDate
let defaultWidth: Double = 40.0
class MyObject: Object {
#objc dynamic var number: Int = 0
#objc dynamic var name: String?
let items = List<ChildObject>()
}
extension MyObject {
var activeItems: Results<ChildObject> {
let activeDate = activeDate // Some globally defined value
let active = items.filter("startDate <= %# && (endDate >= %# || endDate == nil)", activeDate, activeDate).sorted(byKeyPath: "number")
return active
}
/// Works Correctly
var totalWidth: Double {
let width = Array(activeItems.filter{$0.product != nil}).filter{$0.product!.isProduct == true}.map({$0.spaceRequired}).reduce(0,+)
let width2 = Array(activeItems.filter{$0.product == nil}.map({$0.spaceRequired})).reduce(0,+)
return width+width2
}
/// Crashes
var totalWidth: Double {
let width = activeItems.filter{$0.product != nil}.filter{$0.product!.isProduct == true}.map({$0.spaceRequired}).reduce(0,+)
let width2 = activeItems.filter{$0.product == nil}.map({$0.spaceRequired}).reduce(0,+)
return width+width2
}
}
class ChildObject: Object {
#objc dynamic var parent: MyObject?
#objc dynamic var number: Int = 0
#objc dynamic var product: Product?
#objc dynamic var name: String?
#objc dynamic var spaceRequired: Double = 40.0
#objc dynamic var startDate: NSDate?
#objc dynamic var endDate: NSDate?
}
extension ChildObject {
var spaceRequired: Double {
if let p = product {
return p.width
} else {
return defaultWidth
}
}
}
class Product: Object {
#objc dynamic var isProduct: Bool = false
#objc dynamic var width: Double = 30.0
}
There's a couple of issues at work here but the main problem is that Realm Results are live updating; while you can filter data using the Swifty
let f1 = activeItems.filter{$0.product != nil}
it's going to give intermittent results since Realm doesn't know which items are filtered or not as .filter { is not a Realm function and Realm won't know what to update within the results.
You should generally use the built in Realm filtering mechanism
let results = realm.objects(ItemClass.self).filter("product != nil")
Those results will be live updating - if an object leaves the filter parameter, the results follow that. If an object matches the filter the results are updated as well.
I believe this Github issue #2138 provides some more light on the issue.
If you absolutely need static data, then I would suggest extending the Results class to return an array; like this
extension Results {
func toArray() -> [Element] {
return compactMap { $0 }
}
}
Keeping in mind this will use more memory as Realm objects are lazy loaded and and array isn't.
EDIT:
There's some additonal information in the question so I crafted up a simple example trying to replicate the issue. There's a HouseClass object which contains a List of RoomClass objects and then the HouseClass is extended to return the total width of all of the rooms in its list.
class RoomClass: Object {
#objc dynamic var room_name = ""
#objc dynamic var width = 0
#objc dynamic var length = 0
#objc dynamic var belongs_to_house: HouseClass!
}
class HouseClass: Object {
#objc dynamic var house_id = NSUUID().uuidString
#objc dynamic var house_name = ""
let rooms = List<RoomClass>()
override static func primaryKey() -> String? {
return "house_id"
}
}
extension HouseClass {
var totalWidth: Int {
let width = Array(rooms).map {$0.width}.reduce(0,+)
return width
}
var anotherTotalWidth: Int {
let width = rooms.map {$0.width}.reduce(0,+)
return width
}
}
and then the code to get all of the houses and output their room widths based on two different functions (see the HouseClass extension)
let houseResults = realm.objects(HouseClass.self)
for house in houseResults {
let w0 = house.totalWidth
print(w0)
let w1 = house.anotherTotalWidth
print(w1)
}
I added 100 houses each with three rooms and ran the above code several times without crash.
Count of f1 is 0 so map is not worked.
You can optimize your width calculation as following
let width = activeItems
.filter { $0.product?.isProduct ?? false }
.map { $0.spaceRequired }
.reduce(0,+)

Get associated object value from filter query using Realm swift

So Im pretty new to realm and i feel my question is very basic but i cant find the answer to it.
Basically I'm trying to query Realm for all playerName associated with a specific TeamID (ie. TeamID is not the primary key), however Im having trouble finding the solution. I keep getting a Value of type 'Results<playerInfoTable>' has no member 'playerName' error.
Below is my Realm class:
`class playerInfoTable: Object {
#objc dynamic var playerID: Int = 0
#objc dynamic var playerName: String = ""
#objc dynamic var jerseyNum: Int = 0
#objc dynamic var TeamID: String = ""
#objc dynamic var goalCount: Int = 0
#objc dynamic var shotCount: Int = 0
override class func primaryKey() -> String {
return "playerID"
}
}`
And the Code I'm using the query Realm:
let mainPlayerFilter = NSPredicate(format: "teamID == %#", "1")
let mainPlayerStrings = realm.objects(playerInfoTable.self).filter(mainPlayerFilter)
let mainPlayerTeamName = mainPlayerStrings.playerName
Solution!
let mainPlayerFilter = NSPredicate(format: "TeamID == %#", String(homeTeam!))
let mainPlayerStrings = realm.objects(playerInfoTable.self).filter(mainPlayerFilter)
let mainPlayerTeamName = mainPlayerStrings.value(forKeyPath: "playerName") as! [String]
I suppose you get the error when you declare the mainPlayerTeamName constant. Try the following:
let mainPlayerTeamName = mainPlayerStrings.first?.playerName
And I noticed that in your playerInfoTable class you declare "teamID" as "TeamID", while in your predicate you refer to it as "teamID". Decide which one you want because NSPredicate is case sensitive by default.

Invalid value when query on Int in realm

I have a realm declaration like that :
#objc dynamic var roomId = UUID().uuidString
#objc dynamic var roomName = ""
#objc dynamic var roomType = ""
#objc dynamic var floor = 1
#objc dynamic var placeId : String?
I am trying to get a query of all rooms for a specific floor in a specific place from realm DB with this function :
static func getAllRoomNamesAndTypesForQuery (placeName: String? ,room : String? , floor : Int?) -> [[String]] {
var result : [[String]] = [[]]
if placeName != nil , floor != nil {
let placeId = Places.specificPlaceQueries(placeName: placeName)[0] as! String
let allRooms = Users.realm.objects(Rooms.self).filter("placeId == '\(placeId)' AND floor == '\(floor!)'")
var roomNames = [""]
var roomTypes = [""]
for number in 0..<allRooms.count {
roomNames.append(allRooms[number].roomName)
roomTypes.append(allRooms[number].roomType)
}
let sortedRoomNames = roomNames.sorted()
let sortedRoomTypes = roomTypes.sorted()
result = [sortedRoomNames , sortedRoomTypes]
}
return result
}
but it keeps showing me the following strange error
Expected object of type int for property 'floor' on object of type
'Rooms', but received: 1
I dunno how it rejects 1 as Int ?? anyone know where is the problem here??
You shouldn't be using String interpolation when creating NSPredicates, since even though it is supported, it is really easy to mess up the predicate format. Simply use %# for substituting variable values into the predicate.
let allRooms = Users.realm.objects(Rooms.self).filter("placeId == %# AND floor == %#",placeId, floor)
Some further improvements to your code: don't use nil check, then force unwrapping, use optional binding when working with Optionals.
if let placeName = placeName , let floor = floor {
Also don't add an initial value to Arrays when creating them, instead of var roomNames = [""] and var roomTypes = [""], do
var roomNames = [String]()
var roomTypes = [String]()
Can you try
let allRooms = Users.realm.objects(Rooms.self).filter {
$0.placeId == placeId
&& $0.floor == floor
}
First :
set this
var roomNames = [""];
var roomTypes = [""];
to this
var roomNames = [String]()
var roomTypes = [String]()