Realm query nested object - swift

Hello everyone I'm having difficulties archiving one thing with a query of nested object. I have two realm object Championship and Game.
class Championship: Object {
dynamic var id: Int = 0
dynamic var name: String = ""
let games = List<Game>
override static func primaryKey() -> String? {
return "id"
}
}
class Game: Object {
dynamic var id: Int = 0
dynamic var homeTeamName: String = ""
dynamic var awayTeamName: String = ""
dynamic var status: String = "" //"inprogress", "finished", "scheduled"
override static func primaryKey() -> String? {
return "id"
}
}
And basically I want to retrieve all championships that have games with status "inprogress", so what I'm doing to archive that is:
let realm = try! Realm()
realm.objects(Championship.self).filter("ANY games.status = 'inprogress'")
What that query is doing is giving me all championships that have at least one game with that status, but is also giving me all the the games from that championship, but actually I just want the games with "inprogress" status.
There any way for doing that?

You could take two approaches here. If you want all games with inprogress status, you could write this:
let inProgressGames = realm.objects(Championship.self)
.flatMap { $0.games }
.filter { $0.status == "inprogress" }
This will return you all games in progress inside [Game] array. flatMap is used to combine all games from all championships and filter is used to filter all games with inProgress status.
If you want championships where every game is inprogress you could write:
let inProgressChampionships = realm.objects(Championship.self).filter {
let inProgressGames = $0.games.filter { $0.status == "inprogress"}
return $0.games.count == inProgressGames.count
}
This will return array of Championship where each game is inprogress.
Other that that, I would recommend using enum for game status instead of hard-coded strings.

Related

How do you pass data dynamically is a Swift array?

Im creating a tinder like swipe app and I need a new CardData(name: "", age: "") to be created depending on how many profiles I pass through from my database. The number of cards will change. I need the number of cards created to match the the value of the results default. I have looked for the solution for a while and can't find it anywhere.
import UIKit
var nameArrayDefault = UserDefaults.standard.string(forKey: "nameArray")!
var ageArrayDefault = UserDefaults.standard.string(forKey: "ageArray")!
var nameArray = nameArrayDefault.components(separatedBy: ",")
var ageArray = ageArrayDefault.components(separatedBy: ",")
var results = UserDefaults.standard.string(forKey: "results")!
struct CardData: Identifiable {
let id = UUID()
let name: String
let age: String
static var data: [CardData] {[
CardData(name: “\(nameArray[0])”, age: “\(ageArray[0])”),
CardData(name: “\(nameArray[1])”, age: “\(ageArray[1])"),
CardData(name: “\(nameArray[2])”, age: “\(ageArray[2])”)
]}
}
You should initiate the array of CardData objects only the first time and update it after that. You can do the following
var _data = [CardData]()
var data: [CardData] {
if _data.isEmpty() {
self.initiateData()
}
return _data
}
// Initiate data for the first time only
func initiateData() -> [CardData] {
// Check that nameArray has the same size as ageArray
// If the size is different then the data are not valid.
guard nameArray.count == ageArray.count else {
return []
}
// For each of names inside nameArray create the corresponding CardData object
self.nameArray.forEach({ (index, item)
self._data.append(CardData(name: item, age: ageArray[index]))
})
}

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.

Performance issues on Realm List

I'm having some memory performance issues when doing operations on Realm List's. I have two objects similar to this one:
final class Contact: Object {
let phones = List<Phone>()
let emails = List<Email>()
}
Now I'm trying to find possible similarities between two objects of the same type (e.g. at least one element in common) that could potentially have duplicate emails or phones. In order to do so I was using Set operations.
func possibleDuplicateOf(contact: Contact) {
return !Set(emails).isDisjoint(with: Set(contact.emails)) || !Set(phones).isDisjoint(with: Set(contact.phones))
}
This is a function inside the Contact object. I know it has a performance hit when transforming Realm List into a Set or an Array, and I'm feeling this heavily when I have a large amount of Contacts (10k or more), memory consumption jumps to more then 1GB.
So I tried replacing the above function with this one:
func possibleDuplicateOf(contact: Contact) {
let emailsInCommon = emails.contains(where: contact.emails.contains)
let phonesInCommon = phones.contains(where: contact.phones.contains)
return emailsInCommon || phonesInCommon
}
This has the same performance has using the sets.
The isEqual method on the Emails and Phones is a simple string comparision:
extension Email {
static func ==(lhs: Email, rhs: Email) -> Bool {
return (lhs.email == rhs.email)
}
override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Email else { return false }
return object == self
}
override var hash: Int {
return email.hashValue
}
}
Email.swift
final class Email: Object {
enum Attribute: String { case primary, secondary }
#objc dynamic var email: String = ""
#objc dynamic var label: String = ""
/* Cloud Properties */
#objc dynamic var attribute_raw: String = ""
var attribute: Attribute {
get {
guard let attributeEnum = Attribute(rawValue: attribute_raw) else { return .primary }
return attributeEnum
}
set { attribute_raw = newValue.rawValue }
}
override static func ignoredProperties() -> [String] {
return ["attribute"]
}
convenience init(email: String, label: String = "email", attribute: Attribute) {
self.init()
self.email = email
self.label = label
self.attribute = attribute
}
}
I'm a bit out of options in here, I've spent the entire day trying to come up with a different approach to this problem but without any luck. If anyone has a better idea, I would love to hear it out :)
Thank you
Whenever something like this happens, a good start is using Instruments to find out where CPU cycles and memory are consumed. Here's a good tutorial: Using Time Profiler in Instruments
You omitted the code making the actual comparison, but I suspect it might be nested for loops or something along those lines. Realm doesn't know your use case and isn't caching appropriately for something like that.
Using Instruments, it's fairly easy to find the bottlenecks. In your case, this should work:
final class Contact: Object
{
let emails = List<Email>()
lazy var emailsForDuplicateCheck:Set<Email> = Set(emails)
func possibleDuplicateOf(other: Contact) -> Bool {
return !emailsForDuplicateCheck.isDisjoint(with: other.emailsForDuplicateCheck)
}
override static func ignoredProperties() -> [String] {
return ["emailsForDuplicateCheck"]
}
}
And for the comparison:
// create an array of the contacts to be compared to cache them
let contacts = Array(realm.objects(Contact.self))
for contact in contacts {
for other in contacts {
if contact.possibleDuplicateOf(other: other) {
print("Possible duplicate found!")
}
}
}
This implementation ensures that the Contact objects are fetched only once and the Set of Email is only created once for each Contact.
The problem you have might be solved more optimal by rebuilding a bit data structures. Getting everything in memory and trying to convert to set (building sets is expensive operation) is far from optimal :(. I suggest this solution.
Consider contact is this object (I've added id property). I didn't add phones objects for brevity but exactly the same approach may be used for phones.
class Contact: Object {
#objc dynamic var id = UUID().uuidString
var emails = List<Email>()
override public static func primaryKey() -> String? {
return "id"
}
}
And Email class is this one. Relation to contact is added.
class Email: Object {
#objc dynamic var email: String = ""
#objc dynamic var contact: Contact?
}
Having these "connected" tables in realm you may create query to find duplicated objects:
func hasDups(contact: Contact) -> Bool {
let realm = try! Realm()
let emails: [String] = contact.emails.map { $0.email }
let sameObjects = realm.objects(Email.self)
.filter("email in %# AND contact.id != %#", emails, contact.id)
// sameObject will contain emails which has duplicates with current contact
return !sameObjects.isEmpty
}
This works super fast, I've tested on 100000+ objects and executed immediately.
Hope this helps!

RealmSwift + ObjectMapper managing String Array (tags)

What I need to represent in RealmSwift is the following JSON scheme:
{
"id": 1234,
"title": "some value",
"tags": [ "red", "blue", "green" ]
}
Its a basic string array that I'm stumbling on. I'm guessing in Realm I need to represent "tags" as
dynamic id: Int = 0
dynamic title: String = ""
let tags = List<MyTagObject>()
making tags its own table in Realm, but how to map it with ObjectMapper? This is how far I got...
func mapping(map: Map) {
id <- map["id"]
title <- map["title"]
tags <- map["tags"]
}
... but the tags line doesn't compile of course because of the List and Realm cannot use a [String] type.
This feels like a somewhat common problem and I'm hoping someone who has faced this can comment or point to a post with a suggestion.
UPDATE 1
The MyTagObject looks like the following:
class MyTagObject: Object {
dynamic var name: String = ""
}
UPDATE 2
I found this post which deals with the realm object but assumes the array has named elements rather than a simple string.
https://gist.github.com/Jerrot/fe233a94c5427a4ec29b
My solution is to use an ObjectMapper TransformType as a custom method to map the JSON to a Realm List<String> type. No need for 2 Realm models.
Going with your example JSON:
{
"id": 1234,
"title": "some value",
"tags": [ "red", "blue", "green" ]
}
First, create an ObjectMapper TransformType object:
import Foundation
import ObjectMapper
import RealmSwift
public struct StringArrayTransform: TransformType {
public init() { }
public typealias Object = List<String>
public typealias JSON = [String]
public func transformFromJSON(_ value: Any?) -> List<String>? {
guard let value = value else {
return nil
}
let objects = value as! [String]
let list = List<String>()
list.append(objectsIn: objects)
return list
}
public func transformToJSON(_ value: Object?) -> JSON? {
return value?.toArray()
}
}
Create your 1 Realm model used to store the JSON data:
import Foundation
import RealmSwift
import ObjectMapper
class MyObjectModel: Object, Mappable {
#objc dynamic id: Int = 0
#objc dynamic title: String = ""
let tags = List<MyTagObject>()
required convenience init?(map: Map) {
self.init()
}
func mapping(map: Map) {
id <- map["id"]
title <- map["title"]
tags <- (map["tags"], StringArrayTransform())
}
}
Done!
This line is the magic: tags <- (map["tags"], StringArrayTransform()). This tells ObjectMapper to use our custom StringArrayTransform I showed above which takes the JSON String array and transforms it into a Realm List<String>.
First of all we should assume that our model extends both Object and Mappable.
Let's create a wrapper model to store the primitive (String) type:
class StringObject: Object {
dynamic var value = ""
}
Then we describe corresponding properties and mapping rules for the root model (not the wrapper one):
var tags = List<StringObject>()
var parsedTags: [String] {
var result = [String]()
for tag in tags {
result.append(tag.value)
}
return result
}
override static func ignoredProperties() -> [String] {
return ["parsedTags"]
}
func mapping(map: Map) {
if let unwrappedTags = map.JSON["tags"] as? [String] {
for tag in unwrappedTags {
let tagObject = StringObject()
tagObject.value = tag
tags.append(tagObject)
}
}
}
We need a tags property to store and obtain the data about tags from Realm.
Then a parsedTags property simplifies extraction of tags in the usual array format.
An ignoredProperties definition allows to avoid some failures with Realm while data savings (because of course we can't store non-Realm datatypes in the Realm).
And at last we are manually parsing our tags in the mapping function to store it in the Realm.
It will work if your tags array will contains a Dictionary objects with a key: "name"
{
"id": 1234,
"title": "some value",
"tags": [ ["name" : "red"], ... ]
}
If you cannot modify JSON object, I recommend you to map json to realm programmatically.
for tagName in tags {
let tagObject = MyTagObject()
tagObject.name = tagName
myObject.tags.append(tagObject)
}
Follow this code
import ObjectMapper
import RealmSwift
//Create a Model Class
class RSRestaurants:Object, Mappable {
#objc dynamic var status: String?
var data = List<RSData>()
required convenience init?(map: Map) {
self.init()
}
func mapping(map: Map) {
status <- map["status"]
data <- (map["data"], ListTransform<RSData>())
}
}
//Use this for creating Array
class ListTransform<T:RealmSwift.Object> : TransformType where T:Mappable {
typealias Object = List<T>
typealias JSON = [AnyObject]
let mapper = Mapper<T>()
func transformFromJSON(_ value: Any?) -> Object? {
let results = List<T>()
if let objects = mapper.mapArray(JSONObject: value) {
for object in objects {
results.append(object)
}
}
return results
}
func transformToJSON(_ value: Object?) -> JSON? {
var results = [AnyObject]()
if let value = value {
for obj in value {
let json = mapper.toJSON(obj)
results.append(json as AnyObject)
}
}
return results
}
}

Swift filter nested array of objects

I have an array of Business objects. Each Business object contains an array of key-value pairs, one element of which can be a nested array of ContentBlocks objects.
var masterArray = [
Business(busName: "Dave's Cafe", busId: 1, website: "http://www.davescafe.com", latLong: (45.541, -45.609),
actions: [["title": "About Us", "contentId": "123", "actionType": "content"],
["title": "Website", "url": "http://www.davescafe.com", "actionType": "web"]],
contentBlocks:[
ContentBlock(busName: "Dave's Cafe", busId: 1, contentId: "123", title: "Testola!", body: "Hello there!")
]),
Business(busName:...
]
I can filter the array to return a specific Businesses matching a unique busId by using something like this:
let rtnArray = masterArray.filter{$0.busId == id}
if rtnArray.count == 1{
return rtnArray[0]
} else {
return // feedback that no matches were found
}
Additionally, I'd like to return a specific contentBlock by filtering on the unique contentId (if necessary I can also pass the busId of the Business 'owner'). I'm really struggling to move forward so any pointers in the right direction would be great.
Here's a solution to what I think you're asking:
var contentBlocks = masterArray
.flatMap{$0.contentBlocks}
.flatMap{$0}
.filter{$0.contentId == "123"}
Outputs a [ContentBlock] containing all ContentBlock objects that match the filter from within all Business objects.
The first flatMap makes the list of Businesses into an [ContentBlock?]
The flatMap flattens the [ContentBlock?] into a [ContentBlock]
The [ContentBlock] is filtered
When you are dealing with model and want to search the nested custom object of class.
Here is class example.
public final class Model1 {
// MARK: Properties
public var firstname: String?
public var lastname: String?
public var email: String?
public var availabilities: [Availability]?
public var username: String?
}
public final class Availability {
var date: String
var day : String
init(date: String, day: String) {
self.date = date
self.day = day
}
}
public final class AvailData {
var am: String?
var pm: String?
init(am: String?,pm: String?) {
self.am = am
self.pm = pm
}
}
let make an array of Model1.
var arrData = [Model1]()
In my case, I want to search in availabilities array. So I put below logic and it work perfectly.
let searchData = arrData.filter({ singleObj -> Bool in
let result = singleObj.availabilities?.filter({ $0.date == "2019/09/17" })
return result?.count > 0 ? true : false
})
This swift code will return only those records which match 2019/09/17
Try this:
// The busId and contentId you want to find
let busId = 1
let contentId = "123"
let contentBlocks = masterArray.flatMap {
$0.contentBlocks.filter { $0.busId == busId && $0.contentId == contentId }
}