Connecting Realm with Swift Bond - ios

I have read the following question on Stack Overflow (What is the best way to connect Realm and SwiftBond), which is unfortunately 2 years old. I am in the same position where I would like to create an observable instance of a Realm object, whereby the Realm object will get updated from the UI, and can then be written to Realm.
From what I have read and understood, I think that Observable(object:keyPath:) no longer exists in Bond v6, but I can't quite figure out what the alternative is, though I think it has something to do with dynamic(keyPath:ofType:).
I am struggling to find an example or any documentation which would allow me to do the Bond 6 version of the following:
class Dog: Object {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
}
extension Dog {
class ObservableDog {
let name: Observable<String>
let birthdate: Observable<NSDate>
init(dog: Dog) {
name = Observable(object: dog, keyPath: "name")
birthdate = Observable(object: dog, keyPath: "birthdate")
}
}
func observableVariant() -> Dog.ObservableDog {
return ObservableDog(dog: self)
}
}
let myDog = Dog().observableVariant()
myDog.name.observe { newName in
print(newName)
}
myDog.name.bindTo(nameLabel.bnd_text)
realm.write {
myDog.name.value = "Jim"
}
I'm now on my second day of attempting this, so I'm hoping that someone will be able to help.
Thanks in advance.

Related

Dictionary not appending values

I think I'm introducing some logic error and I might be missing something here.
Please consider the following code:
// Model
class MyModel: NSObject {
let month: Int
let destiny: String
init(month: Int, destiny: String) {
self.month = month
self.destiny = destiny
}
}
var datasource: [MyModel] = []
var dict: [Int : [MyModel]] = [:]
func fillDatasource() {
for _ in 0...20 {
let month = Int.random(in: 1...12)
let destiny = "Any"
let model = MyModel(month: month, destiny: destiny)
datasource.append(model)
}
}
func fillDict() {
datasource.forEach {
let month = $0.month
dict[month]?.append($0)
}
print(dict) // always empty
}
fillDatasource()
fillDict()
Inside my fillDict function the array is always nil.
I think this is because the key doesn't exist , so the value cannot be appended to that specific key.
My question is: if the key doesn't exist, calling the append function would insert the key as well?
Am I missing something here?
Thanks.
Your assumption is incorrect and there is no reason to think that this would insert a new array.
It might seem intuitive for this case but it may be very wrong for some cases. How about something like this:
garages[myName]?.parkCar(myCar)
Should this construct a new garage for my car? I think not. But even if so; what if default constructor is unavailable and this is actually defined as a protocol:
protocol Garage {
func parkCar(_ car: Car)
}
var garages[String: Garage]
there is no way for Swift to fill in this object automatically.
Technically there would be a possible solution for this work that Swift would automatically construct an object for you in dictionary if this object had a default constructor and possibly the object type is a struct or a final class... But this would most likely only introduce more confusion than it would solve.
The most straight forward solution to your example is what #Sh_Khan wrote (but later deleted) which is:
if dict[month] == nil {
dict[month] = [$0]
}
else {
dict[month]?.append($0)
}
Probably some more feasible approach would be
dict[month] = (dict[month] ?? []) + [$0]
but as described in a comment there is already a method that does exactly that for you:
dict[month, default: []].append($0)
I hope we can agree that this is a more general approach and it fixes all cases. For instance
garages[myName, default: PublicGarage(parkingSpot: myName)].parkCar(myCar)
You can update your fillDict method to the following:
func fillDict() {
datasource.forEach {
let month = $0.month
if dict.keys.contains(month) {
dict[month]?.append($0)
} else {
dict[month] = [$0]
}
}
print(dict)
}
Explanation:
We need to check if the month key already exits in the dictionary, than append in it's array else we are assigning a new array against a month in the dictionary
Dic is empty because dic[month] is nil, the value has never been altered.
To group array by a property of the array elem, I'd use the following:
dic = Dictionary(grouping: datasource) { (model) -> Int in
return model.month
}

How Query and Count entries in a Realm Database

I'd like to build a search on which the user can filter down the results step-by-step. So with no choice set, there is a button which says e.g. "1,234,567 Results" and if you choose a color for example the results set shrinks... we all know this kind of search. I did build it many times, but this is the first time in Realm (and swift).
Lets Say I have 5 Persons in my Person Table, then there are about 145,224 Dog entries per Person and about 2,507,327 Cat entries per Dog. How do I query and Count nested Objects in Realm?
class Person: Object {
#objc dynamic var name = ""
let dogs = List<Dog>()
// ...other Properties
}
extension Person {
static func all(in realm: Realm = try! Realm()) -> Results<Person> {
return realm.objects(Person.self)
}
}
// counts -> 145,224 db entries per person
class Dog: Object {
#objc dynamic var name = ""
dynamic var Person: Person?
let cats = List<Cats>()
// ...other Properties as well
}
extension Dog {
static func all(in realm: Realm = try! Realm()) -> Results<Dog> {
return realm.objects(Dog.self)
}
}
// counts -> 2,507,327 db entries per dogs
class Cat: Object {
#objc dynamic var name = ""
dynamic var Cat: Cat?
}
extension Cat {
static func all(in realm: Realm = try! Realm()) -> Results<Cat> {
return realm.objects(Cat.self)
}
}
// Get the default Realm
let realm = try! Realm()
// Query Realm for all dogs
let dogs = Person.all(in: realm).flatMap { $0.dogs }
dogs.count // => takes ~20 seconds to count
In other words, what is the fastest way to get (count) all Dog entries of all Persons (let the cats by side for now).
I tried to workaround the problem by limit the results to 1000. If the results are >1000, then label the button like so "> 1000 Results". But even than it takes very long (I guess the get all count anyway).
So what did I do wrong?
They way you were computing the count required all Dog objects to be loaded into memory which is really inefficient. This is why you were seeing such poor performance. You want to take advantage of Realm's lazy loading features. You may want to read up on that.
I would update your Dog object by getting rid of your managed Person property and replace it with LinkingObjects. If you store a Dog in a Person.dogs List, then realm will create a back link to the Person for you. You will likely want to do the same thing with Cat. That way you can set up some really powerful nested queries.
For convenience you can add a computed Person property to index into the LinkingObjects. Just know that you won't be able to use that property in any of your queries.
class Dog: Object {
#objc dynamic var name = ""
let persons = LinkingObjects(fromType: Person.self, property: "dogs")
var person: Person? { persons.first }
}
One way to compute the count is to query of all Person objects and sum the count of dogs for each Person.
let count = realm.objects(Person.self).reduce(0) { result, person in
return result + person.dogs.count
}
Another options is to query for Dog objects that have a corresponding Person. If a Dog is not in a Person.dogs List, then it won't show up the query.
let realm = try! Realm()
let count = realm.objects(Dog.self).filter("persons.#count > 0").count
Either option is going to be much, much more efficient than what you had.
Hope this helps.

filter list on inverse relationship

I struggle a bit today, taking the example given on RealmSwift documentation, what I am trying to do is find the query that will allow me to get the dogs (from the dog object) who only have at least one owner.
class Person: Object {
// ... other property declarations
let dogs = List<Dog>()
}
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
let owners = LinkingObjects(fromType: Person.self, property: "dogs")
}
I have this basic method :
public class func getDogs() -> Results<Dog>? {
do {
let aRealm = try Realm()
let dogs = aRealm.objects(Dog.self).filter("ANY owners != nil")
return dogs
} catch {
print(error)
}
return nil
}
but it fails so I assume my query is incorrect, though I failed to find any documentation on this, any insight would be much appreciated.
You can use the aggregate expression, #count. The following query filters dogs that have at lease more than one owner.
let dogs = aRealm.objects(Dog.self).filter("owners.#count > 0")
Please see more details: https://realm.io/docs/swift/latest/#filtering

A Good design in Swift for a model that has many aspects?

I would like to ask a question about a good example of how to define a model with many aspects in swift, especially when the project gets bigger and bigger and one model has many aspects. The questions is pretty long but I just would like to know how ppl design a model in a big project. Any comments or thought would be appreciated.
Let's say there is a model called "Book" and it is defined like below:
class Book {
var id: String
var title: String
var author: String
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
Book has a failable initialiser that parses properties from JSON sent from the server.
On view controller A, it describes the more detailed information about the mode Book, so some properties are added to the model when it is used on view controller r A:
class Book {
var id: String
var title: String
var author: String
// required on View Controller A
var price: Int
var seriersName: String
var reviewNumber: Int
var detailedDescription: String
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
On another view controller B, we want to show the history of book purchase. So the model needs additional properties as below:
class Book {
var id: String
var title: String
var author: String
// required on View Controller A
var price: Int
var seriersName: String
var reviewNumber: Int
var detailedDescription: String
// required on View Controller B (Not required on VC A)
var purchasedDate: NSDate
var expireDate: NSDate
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
This definition of Book lacks flexibility because the JSON passed to the failabel initialiser must have all of the properties even on an VC that uses only some of the properties.
Solution A:
I think the simplest solution for this is just declaring those additional properties as optional, but I personally think this is not so cool because whenever those optional properties are used they need to be checked if they are not nil.
if let seriesName = book.seriesName {
self.seriesNameLable.title = seriesName
}
This kind of optional binding code will be overflowed all over the codes I assume. Implicit optional binding might be able to used but that is not really safe to use.
Solution B:
Another solution might be to define different models that inherits Book, like BookA and BookB. But what if we need a model that has BookA and BookB's aspects at the same time?
I guess there is no single solution for this kind of problem but I would like to know how other ppl define a model in a big project. (I wonder if someone would have a cool solution using some "swift specific" features like protocol and its extension :). I would appreciate any kind of opinions... Thank you.
Disclaimer: I'm not a Swift programmer, this are extrapolations I make from other languages with the same features and my Swift syntax might not be 100% accurate
Using protocols I would do something like:
class EBook: Book, HasOnlineSource {
...
}
class OtherKindOfBook: Book, WithCollectorEditions, HasBleh, WithFoo {...}
But you must ask yourself:
Do I need this to change dinamically?
If that is the case, you would need to go with Delegation through composition.
Are different parts of the application using the core models differently?
Or in other words, are there different users of those models needing different behavior? In that case, extensions are very useful since allow to expose different behaviors for the same model depending on their context. For instance, the Reporting module can send the message numberOfReaders while the Selling module could ask for promotionalCodes. Both are using the same model, but interacting with different protocols. In your case, you have different controllers wanting different things, so this might apply.
Using delegates
This follows the Composition over inheritance principle, but after reviewing how delegates work in Swift, I understood that they are not a native implementation but still a design pattern (a feature request you might say), the delegation is being made by hand.
Other languages allow you to make a JSSONSerializableBook with a BookProtocol, but instead of implementing what is required on BookProtocol you can set a delegate upon initialization which will implement such protocol. This delegate will be an internal collaborator of JSSONSerializableBook and all messages that are part of BookProtocol received by JSSONSerializableBook will be delegated to it.
Or in other words, the message passing is handled automatically (here is the Kotlin documentatin on delegates if you want to check how other language implements it).
If you want to do the same on Swift, you must explicitly make the message passing to your delegate.
This design has several advantages but since there is no native support for message passing it becomes very verbose to implement.
For further reference you can check the papers on mixins and traits to have some insight on the design decisions behind this features.
This is why Swift has optionals.
If not all Books have, say, a purchasedDate then that should be an optional and then your initialiser doesn't need to fail if that field isn't present in the JSON.
It is your view controllers that will enforce the business logic - so if ViewControllerB gets a Book without a purchasedDate it should generate some sort of error.
It also may be that your base class needs to be decomposed into smaller objects.
For example, you could probably have a PurchaseEvent that is associated with a Book rather than storing that data in the Book itself.
Think of the characteristics of a book in the real world; it has the basic properties you first listed and may even have a price (on the back cover) but it doesn't tell you when it was sold and for how much.
However, #Logain's idea of using Protocols is also good, although it may be tricky when it comes to keeping the server JSON side in sync with the app code.
First of all, if you have a view controller responsible for presenting “more detailed information” about a book, then let's call it BookDetailsViewController, instead of the meaningless “View Controller A”. Similarly we should talk about BookHistoryViewController, not “View Controller B”.
If you want to treat the “more detailed information” as all-or-nothing, then encapsulate it as such, in a Details object that the Book object optionally reference. Same with the history. So here's a model:
class Book {
let id: String
let title: String
let author: String
// These can't be lets because each requires self to initialize.
private(set) var details: Details?
private(set) var history: History?
init?(json: [String: AnyObject]) {
guard let
id = json["id"] as? String,
title = json["title"] as? String,
author = json["author"] as? String
else {
// These assignments will be unnecessary in the next version of Swift.
self.id = ""
self.title = ""
self.author = ""
return nil
}
self.id = id
self.title = title
self.author = author
self.details = Details(book: self, json: json)
self.history = History(book: self, json: json)
}
class Details {
unowned let book: Book
let price: Int
let seriesName: String
let reviewNumber: Int
let detailedDescription: String
init?(book: Book, json: [String: AnyObject]) {
self.book = book
guard let
price = json["price"] as? Int,
seriesName = json["seriesName"] as? String,
reviewNumber = json["reviewNumber"] as? Int,
detailedDescription = json["detailedDescription"] as? String
else {
// These assignments will be unnecessary in the next version of Swift.
self.price = 0
self.seriesName = ""
self.reviewNumber = 0
self.detailedDescription = ""
return nil
}
self.price = price
self.seriesName = seriesName
self.reviewNumber = reviewNumber
self.detailedDescription = detailedDescription
}
}
class History {
unowned let book: Book
let purchasedDate: NSDate
let expireDate: NSDate
init?(book: Book, json: [String: AnyObject]) {
self.book = book
guard let
purchasedDate = (json["purchasedDate"] as? Double).map({ NSDate(timeIntervalSince1970: $0) }),
expireDate = (json["expireDate"] as? Double).map({ NSDate(timeIntervalSince1970: $0) })
else {
// These assignments will be unnecessary in the next version of Swift.
self.purchasedDate = NSDate()
self.expireDate = NSDate()
return nil
}
self.purchasedDate = purchasedDate
self.expireDate = expireDate
}
}
}
Given this model, you can allow the user to ask for a BookDetailsViewController or a BookHistoryViewController only when the appropriate property isn't nil.
class BookViewController: UIViewController {
#IBOutlet var detailsButton: UIButton!
#IBOutlet var historyButton: UIButton!
var book: Book!
override func viewDidLoad() {
super.viewDidLoad()
detailsButton.hidden = book.details == nil
historyButton.hidden = book.history == nil
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segue.identifier {
case .Some("details"): prepareForDetailsSegue(segue)
case .Some("history"): prepareForHistorySegue(segue)
case let other: fatalError("unknown segue identifier \(other)")
}
}
private func prepareForDetailsSegue(segue: UIStoryboardSegue) {
let destination = segue.destinationViewController as! BookDetailsViewController
destination.details = book.details!
}
private func prepareForHistorySegue(segue: UIStoryboardSegue) {
let destination = segue.destinationViewController as! BookHistoryViewController
destination.history = book.history!
}
}
class BookDetailsViewController: UIViewController {
var details: Book.Details!
// etc.
}
class BookHistoryViewController: UIViewController {
var history: Book.History!
// etc.
}

Storing an array of strings using Realm's RLMArray

Does anyone know how you can use Realm to store an array of strings? I'm trying to map the following response into Realm correctly:
"zoneInfo": {
"tariffInfoLines": [
"In this city you pay per minute."
]
}
We have a zoneInfo object that contains a tariffInfoLines array. This tariffInfoLines array contains strings. In Realm there are two different variable types for storing data. The first is RLMObject which allows your standard NSString, int, long etc.
The second type is RLMArray, which is used for arrays (as NSArray is not supported). You have to give the array a type, which must be a class that subclasses RLMObject. We have so far got around this by using a ABCRealmString object, as shown below:
#property RLMArray<ABCRealmString> *tariffInfoLines;
ABCRealmString contains an NSString property (it is basically a wrapper):
#property NSString *value;
However what this means is that when Realm tries to map the response to persist the data, it is looking for a value for the key "value" (the name of the property). It appears that it expects a response similar to the following:
"zoneInfo": {
"tariffInfoLines": [
{
"value": "In this city you pay per minute."
},
]
}
In the project, we have it working for the following structure:
"userOptions": [
{
"wantsEmailNotifications": true,
"wantsPushNotifications": false
},
]
This has an array, with objects inside that have clear key value pairs that Realm can map to. The zoneInfo structure appears to be the only place that we have an array with sets of values inside without them being inside an object or having any keys.
If anyone could shed some light on this, regarding if this is possible using Realm, or whether an API change is required to match a structure that Realm can map.
Cross posting from the github issue response: Although this example demonstrates how to store flat arrays of strings on a Realm model, you can extend this pattern to store anything from arrays of integers to native Swift enum's. Basically anything that you can map to a representable type in Realm.
class RealmString: Object {
dynamic var stringValue = ""
}
class Person: Object {
var nicknames: [String] {
get {
return _backingNickNames.map { $0.stringValue }
}
set {
_backingNickNames.removeAll()
_backingNickNames.appendContentsOf(newValue.map({ RealmString(value: [$0]) }))
}
}
let _backingNickNames = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["nicknames"]
}
}
// Usage...
let realm = try! Realm()
try! realm.write {
let person = Person()
person.nicknames = ["John", "Johnny"]
realm.add(person)
}
for person in realm.objects(Person) {
print("Person's nicknames: \(person.nicknames)")
}
// Prints:
// Person's nicknames: ["John", "Johnny"]
UPDATE (most of the previous answers are no longer correct):
You can now store primitive types or their nullable counterparts (more specifically: booleans, integer and floating-point number types, strings, dates, and data) directly within RLMArrays or Lists. If you want to define a list of such primitive values you no longer need to define cumbersome single-field wrapper objects. Instead, you can just store the primitive values themselves.
Lists of primitive values work much the same way as lists containing objects, as the example below demonstrates for Swift:
class Student : Object {
#objc dynamic var name: String = ""
let testScores = List<Int>()
}
// Retrieve a student.
let realm = try! Realm()
let bob = realm.objects(Student.self).filter("name = 'Bob'").first!
// Give him a few test scores, and then print his average score.
try! realm.write {
bob.testScores.removeAll()
bob.testScores.append(94)
bob.testScores.append(89)
bob.testScores.append(96)
}
print("\(bob.testScores.average()!)") // 93.0
All other languages supported by Realm also supports lists of primitive types.
For the Swift 3.0 here is the change (in my case the Xcode 8 compiler didn't offer auto fix when i switched to swift 3.0 so I had some pain to resolve it).
_backingNickNames.append(objectsIn: newValue.map { RealmString(value: [$0]) })
The RealmString approach is good, but you end up with a new RealmString every time you update the values, leaving a ton of unused objects laying around if you don't clean them up.
I would suggest using something like:
fileprivate let separator = "\u{FFFF}"
class Person: Object {
fileprivate dynamic var _nicknames: String?
var nicknames: [String] {
get { return _nicknames?.components(separatedBy: separator) ?? [] }
set { _nicknames = newValue.isEmpty ? nil : newValue.joined(separator: separator) }
}
override static func ignoredProperties() -> [String] {
return ["nicknames"]
}
}
extension String {
func toStringObject() -> StringObject {
return StringObject(initValue: self)
}
}
extension Sequence where Iterator.Element == String {
func toStringObjects() -> List<StringObject> {
let list = List<StringObject>()
for s in self {
list.append(s.toStringObject())
}
return list
}
}
extension Int {
func toIntObject() -> IntObject {
return IntObject(initValue: self)
}
}
extension Sequence where Iterator.Element == Int {
func toIntObjects() -> List<IntObject> {
let list = List<IntObject>()
for s in self {
list.append(s.toIntObject())
}
return list
}
}

Resources