I am using Realm and it looks good.But I am confused here. Because I did not get how Realm is working and what is its structure of saving data.
I am basically an Android developer and you can say I am newbie in iOS. So in my mind I was thinking That there will be a main file of database. and then inside it, there will be different tables and in tables I can save data.
But I am very amazed that I saved data but It did not asked me for table name and I really do not know how to create table in it. AS in Java we have first create table then we reflect our model as row in that table in database. I am using following file to save Data in Realm . Please have a look and clear following confusions
tell me how realm works?
how the Table is created in realm ?
How to check if db is existed I mean if data is saved already?
How to check if any table let say (Country table) is already created so to retrieve data ??
and see the following class. It is basically a helper class.
public class DbHelper {
private init() {}
static let sharedDbHelper = DbHelper()
var realmObj = try! Realm()
/**
Generic function to create Object in the DB
*/
func save <T: Object> (_ obj : T){
do {
try realmObj.write {
realmObj.add(obj)
}
}catch{
print("DbHelperException","Create",error)
}
}
/**
Generic function to update Object in the DB
*/
func update <T: Object> (_ obj : T, with dictionary: [String : Any?]){
do{
try realmObj.write {
for (key,value) in dictionary{
obj.setValue(value, forKey: key)
}
}
}catch {
print("DbHelperException","Update",error)
}
}
/**
Generic function to delete Object in the DB
*/
func delete <T: Object> (_ obj : T){
do {
try realmObj.write {
realmObj.delete(obj)
}
}catch {
print("DbHelperException","Delete",error)
}
}
/**
Function to manage the error and post it
*/
func postDbError(_ error : Error) {
NotificationCenter.default.post(name: NSNotification.Name(""), object: error)
}
/**
Function to observe the error and post it
*/
func observeDbErrors(in Vc: UIViewController, completion: #escaping (Error?) -> Void) {
NotificationCenter.default.addObserver(forName: NSNotification.Name(""), object: nil, queue: nil) { (notification) in
completion(notification.object as? Error)
}
}
/**
Function to remove observer of the error
*/
func stopDbErrorObserver (in Vc: UIViewController ){
NotificationCenter.default.removeObserver(Vc, name: Notification.Name(""), object: nil)
}
}
I think all your questions are concerned with the same issue, you think that a Realm Database works using tables, however, this is not the case, instead a Realm Database uses Realms. They can be local (your case), in-memory, or synchronized.
A Realm can contain multiple kinds of objects, which would represent the tables you are thinking of.
If you want to have different tables, the solution would be to define multiple RealmObjects.
To check if your Realm doesn't contain any instance of a specific object you could do something like this: database.objects(YourRealmObject).isEmpty()
The equivalent to a "table" in Realm is a model that you define as a class . You can read about this here: https://realm.io/docs/swift/latest/#models
Here's an example that Realm provides:
class Dog: Object {
#objc dynamic var name = ""
#objc dynamic var owner: Person? // Properties can be optional
}
So in this case, Dog is your table name, and name and owner are your column names (each with a specific data type).
Related
In my iOS app, I get the following exception:
'Linking objects notifications are only supported on managed objects.'
when I try to add an observer block:
y.xxx.observe { ... }
to a property that is defined as such:
class Y: Object {
...
let xxx = LinkingObjects(fromType: X.self, property: "y")
...
}
I believe this means that y.xxx does not have a Realm, and indeed I can see in the debugger that y.xxx.realm is nil. However, y.realm is NOT nil.
How can the linking objects not have a Realm if the object I am linking to does have one?
For completeness, this is how Class X is defined:
class X: Object {
...
#Persisted var y: Y?
...
}
Realm version 10.11.0, RealmDatabase version 11.1.1.
Context: I am in the last phase of migrating an app that was originally written in ObjC to be purely in Swift. This means switching to the Swift version of Realm. I have not encountered this problem in the previous version of the app that is largely the same code base except that it uses a very old version of the Realm framework and the Realm objects are defined in ObjC.
You can add an observer to a linking objects property as long as the objects are managed. Let me set this up starting with the PersonClass who has a List property of DogClass objects
class PersonClass: Object {
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var name = "Jay"
let dogList = List<DogClass>()
override static func primaryKey() -> String? {
return "_id"
}
}
and then our DogClass has an inverse relationship to the PersonClass objects
class DogClass: Object {
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var name = ""
let linkingOwners = LinkingObjects(fromType: PersonClass.self, property: "dogList")
override static func primaryKey() -> String? {
return "_id"
}
}
Then suppose we want to observe Spot's linkingOwners - both Jay and Cindy had Spot added to their dogList previously
let spot = realm.objects(DogClass.self).filter("name == 'Spot'").first!
self.peopleToken = spot.linkingOwners.observe { changes in
switch changes {
case .initial(let dogs):
print(" spots owners have been loaded")
case .update(_, let deletions, let insertions, let modifications ):
print(" something changed in spots owners")
case .error(let error):
print(error.localizedDescription)
}
}
Running this section of code outputs this to console and adds the observer the linkingObjects
spots owners have been loaded
Then, lets make a change to one of the owners properties
let jay = realm.objects(PersonClass.self).filter("name == 'Jay'").first!
try! realm.write {
jay.name = "Jay, Spots owner"
}
that last piece of code will output this to the console
something changed in spots owners
The above code creates a PersonClass object and a DogClass object object with a inverse relationship. The code then adds an observer the linkingObjects (PersonClass) and fires when one of them changes.
Turns out the linking objects now have to be declared like so:
class Y: Object {
...
#Persisted(originProperty: "y") var xxx: LinkingObjects<X>
...
}
I am not sure if the declaration style I used in my question is still supposed to be valid and if this is a bug, but using the new style gets rid of the exception.
I met a problem in realm-cocoa 2.8.0(in 2.7.0 it works good) which is when I want to save an object into the realm file, I saw an empty object with default value is saved into the realm rather than the object I created(even the primary key is different.)
Eg.
class XXXRealmObject: RLMObject {
#objc dynamic var id: String = UUID().uuidString.lowercased()
#objc dynamic var name: String = ""
#objc init(name: String) {
self.name = name
super.init()
}
#objc override init() {
super.init()
}
override class func primaryKey() -> String {
return "id"
}
}
let obj = XXXRealmObject(name: "jojo")
let realm = try! RLMRealm(configuration: .default())
try? realm.transaction {
*breakpoint*
realm.addOrUpdate(object)
}
I add a before realm.addOrUpdate(object) and print the object, it show correct object, but after realm.addOrUpdate(object) get executed, in realm file, I can only see an object
{
id: 169e6bc2-9b34-44ae-8ac3-70e6b9145adc,
name: ""
}
and the id is also different from what I saw in break point. It looks like Realm create an object rather use the object I passed in. I am asking for some help here.
So what will cause realm create an empty object(maybe default value?) rather than save the object I passed. I just want to get some possible reasons here.
I think I got it, in my project, we have a category for NSObject which include a method called objectForKey, and in Realm's src, when we read value from RLMObject, we check if it can response to objectForKey, normally it should return false and keep executing following code to get the real value, but in my project, the code will return nil because it's not a dictionary.
So will close this
OK, first, I know that there is no such thing as AnyRealmObject.
But I have a need to have something the behaves just like a Realm List, with the exception that any kind of Realm Object can be added to the list -- they don't all have to be the same type.
Currently, I have something like this:
enter code here
class Family: Object {
var pets: List<Pet>
}
class Pet: Object {
var dog: Dog?
var cat: Cat?
var rabbit: Rabbit?
}
Currently, if I wanted to add in, say, Bird, I'd have to modify the Pet object. I don't want to keep modifying that class.
What I really want to do is this:
class Family: Object {
var pets: List<Object>
}
Or, maybe, define a Pet protocol, that must be an Object, and have var pets: List<Pet>
The point is, I want a databag that can contain any Realm Object that I pass into it. The only requirement for the databag is that the objects must be Realm Objects.
Now, since Realm doesn't allow for this, how could I do this, anyway? I was thinking of creating something like a Realm ObjectReference class:
class ObjectReference: Object {
var className: String
var primaryKeyValue: String
public init(with object: Object) {
className = ???
primaryKeyValue = ???
}
public func object() -> Object? {
guard let realm = realm else { return nil }
var type = ???
var primaryKey: AnyObject = ???
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
}
The stuff with the ??? is what I'm asking about. If there's a better way of doing this I'm all ears. I think my approach is ok, I just don't know how to fill in the blanks, here.
(I'm assuming that you are writing an application, and that the context of the code samples and problem you provided is in terms of application code, not creating a library.)
Your approach seems to be a decent one given Realm's current limitations; I can't think of anything better off the top of my head. You can use NSClassFromString() to turn your className string into a Swift metaclass object you can use with the object(ofType:...) API:
public func object() -> Object? {
let applicationName = // (application name goes here)
guard let realm = realm else { return nil }
guard let type = NSClassFromString("\(applicationName).\(className)") as? Object.Type else {
print("Error: \(className) isn't the name of a Realm class.")
return nil
}
var primaryKey: String = primaryKeyValue
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
My recommendation is that you keep things simple and use strings exclusively as primary keys. If you really need to be able to use arbitrary types as primary keys you can take a look at our dynamic API for ideas as to how to extract the primary key value for a given object. (Note that although this API is technically a public API we don't generally offer support for it nor do we encourage its use except when the typed APIs are inadequate.)
In the future, we hope to offer enhanced support for subclassing and polymorphism. Depending on how this feature is designed, it might allow us to introduce APIs to allow subclasses of a parent object type to be inserted into a list (although that poses its own problems).
This may not be a complete answer but could provide some direction. If I am reading the question correctly (with comments) the objective is to have a more generic object that can be the base class for other objects.
While that's not directly doable - i.e. An NSObject is the base for NSView, NSString etc, how about this...
Let's define some Realm objects
class BookClass: Object {
#objc dynamic var author = ""
}
class CardClass: Object {
#objc dynamic var team = ""
}
class MugClass: Object {
#objc dynamic var liters = ""
}
and then a base realm object called Inventory Item Class that will represent them
class InvItemClass: Object {
#objc dynamic var name = ""
#objc dynamic var image = ""
#objc dynamic var itemType = ""
#objc dynamic var book: BookClass?
#objc dynamic var mug: MugClass?
#objc dynamic var card: CardClass?
}
then assume we want to store some books along with our mugs and cards (from the comments)
let book2001 = BookClass()
book2001.author = "Clarke"
let bookIRobot = BookClass()
bookIRobot.author = "Asimov"
let item0 = InvItemClass()
item0.name = "2001: A Space Odyssey"
item0.image = "Pic of Hal"
item0.itemType = "Book"
item0.book = book2001
let item1 = InvItemClass()
item1.name = "I, Robot"
item1.image = "Robot image"
item1.itemType = "Book"
item1.book = bookIRobot
do {
let realm = try Realm()
try! realm.write {
realm.add(item0)
realm.add(item1)
}
} catch let error as NSError {
print(error.localizedDescription)
}
From here, we can load all of the Inventory Item Objects as one set of objects (per the question) and take action depending on their type; for example, if want to load all items and print out just the ones that are books.
do {
let realm = try Realm()
let items = realm.objects(InvItemClass.self)
for item in items {
switch item.itemType {
case "Book":
let book = item.book
print(book?.author as! String)
case "Mug":
return
default:
return
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
As it stands there isn't a generic 'one realm object fits all' solution, but this answer provides some level of generic-ness where a lot of different object types could be accessed via one main base object.
I have a Models class defined like this:
class BaseModel: Object {
var data: JSON = JSON.null
convenience init(_ data: JSON) {
self.init()
self.data = data
}
override static func ignoredProperties() -> [String] {
return ["data"]
}
}
class RecipeModel: BaseModel {
dynamic var title: String {
get { return data["fields"]["title"].stringValue }
set { self.title = newValue }
}
... more vars ...
var ingredients: List<IngredientsModel> {
get {
let ingredients = List<IngredientsModel>()
for item in data["fields"]["ingredients"] {
ingredients.append(IngredientsModel(item.1))
}
return ingredients
}
set { self.ingredients = newValue }
}
}
class IngredientsModel: BaseModel {
dynamic var text: String {
get { return data["text"].stringValue }
set { self.text = newValue }
}
... more vars ...
}
And I would like to use it something like this:
Api.shared.fetchAllEntries().call(onSuccess: {response in
print(response.json)
let realm = try! Realm()
try! realm.write {
realm.deleteAll()
}
for item in response.json["items"].arrayValue {
let recipe = RecipeModel(item)
try! realm.write {
realm.add(recipe)
}
}
}, onError: {
print("error")
})
So basically the idea is to just pass the whole JSON to the initial RecipeModel class, and it should parse it out and create the objects I need in the Realm database. It works quite well except for the nested list of IngredientsModel. They do not get added to the realm database.
What I see as a potential problem is that I call self.init() before I call self.data in the convenience init, but I do not see any way to work around this. Do you guys please know how I could achieve that also the IngredientsModel would have its contents set up properly and I would have a list of ingredients in the RecipeModel?
Your current implementation doesn't work, because you are not calling the getter/setter of ingredients in the init method of RecipeModel and hence the IngredientsModel instances are never persisted in Realm.
Moreover, using a computed property as a one-to-many relationship (Realm List) is a really bad idea, especially if you are parsing the results inside the getter for this property. Every time you call the getter of ingredients, you create new model objects instead of just accessing the existing ones that are already stored in Realm, but you are never deleting the old ones. If you were actually saving the IngredientsModel instances to Realm (which you don't do at the moment as mentioned above) you would see that your database is full of duplicate entries.
Your whole approach seems really suboptimal. You shouldn't store the unparsed data object in your model class and use computed properties to parse it. You should parse it when initializing your models and shouldn't store the unparsed data at all. You can use the ObjectMapper library for creating Realm objects straight away from the JSON response.
I am using Objectmapper and Realm for my project.
I have an object like following
class File
{
dynamic var name
dynamic var folder
dynamic var path // This is not coming from JSON // this should be combination of both name+folder
}
I thought of writing a computed property to achieve this but Realm does not support computed properties as primary key.
But I should use this as primary key. Is there any way I can manipulate to add that value after coming from server response.
Note: I am using AlamofireObjectMapper.
I am using the following method which parses the server response and gives me the model object.
Alamofire.request(router).responseObject{ (response: DataResponse<T>) in
{
let myModel = response.result.value // Parsed object
===== What can i do here to achieve my requirement=====
}
You should really consider having some kind of id as the primary key and not computing it from other properties (what happens if they are empty or the computation goes wrong? You'd be left without a valid primary key).
However, if you really need to, you could try
let realm = try Realm()
try realm.write {
items.forEach({ (item) in
item.path = item.name + item.folder
}
realm.add(items, update: true)
}
and don't forget to define path as the primary key in the File class:
class File
{
dynamic var name
dynamic var folder
dynamic var path // This is not coming from JSON // this should be combination of both name+folder
override static func primaryKey() -> String? {
return "path"
}
}