Imagine this code:
class StoredVersions: Object{
#objc dynamic var minimumAppVersion = 0.0
#objc dynamic var sets = 0.0
}
class LoadViewController: UIViewController {
let realm = try! Realm()
override func viewDidLoad() {
super.viewDidLoad()
let db = Firestore.firestore()
var newestVersions = StoredVersions()
if let resultsStoredVersion = self.realm.objects(StoredVersions.self).first{
print("found stored versions: \(resultsStoredVersion)")
self.storedVersions = resultsStoredVersion
}else{
try! self.realm.write {
print("no stored versions")
self.realm.add(self.storedVersions)
}
}
db.collection("data").document("version").getDocument(completion: { (data, someError) in
if let versions = data.flatMap({StoredVersions(value: $0.data()) }) {
try! self.realm.write {
self.storedVersions = versions
}
}
})
}
storedVersions is updated but when I restart the application, storedVersions is back to its initial state. I do see the print "found stored versions".
If I write just 1 variable at a time, it works. That looks like this:
try! self.realm.write {
self.storedVersions.sets = versions.sets
}
How can I update a whole class without having to put in variables one at a time?
When you do this:
if let versions = data.flatMap({StoredVersions(value: $0.data()) }) {
try! self.realm.write {
self.storedVersions = versions
}
}
You're creating a new, unmanaged StoredVersions object. You need to call realm.add(_:) to add it to the Realm, otherwise the object only exists in memory.
If you want to update the existing StoredVersions object rather than creating a new one, you should instead use Realm.add(_:update:), specifying true for the update argument. Note that this requires your type have a primary key property declared so that Realm knows which existing object to update.
Related
I am using RealmSwift to create a PIN code screen for an app. I have a manager class that has a few functions, including checkForExistingPin() which is intended to be used to check whether a pin exists (as the name suggests).
When I create an instance of the manager class and call the checkForExistingPin() function, it always tells me that there are 4 (It prints: "Optional(4)"), even though I have not created a pin yet.
Can anyone explain why this might be doing this and how I might get the correct output from the code?
Here is the class:
import Foundation
import RealmSwift
class pinCode: Object {
#objc dynamic var pin = ""
}
protocol pinCodeManager {
func checkForExistingPin() -> Bool
func enterNewPin(newPin:String)
func checkPin(pin:String) -> Bool
}
class manager:pinCodeManager {
let realm = try! Realm()
func checkForExistingPin() -> Bool {
let existingCode = realm.objects(pinCode.self).first?.pin
print("\n\nNumber of existing PINs: ", existingCode?.count as Any, "\n\n") // Number of existing PINs: Optional(4)
if existingCode?.count == 0 {
return false
}
else {
return true
}
}
func enterNewPin(newPin:String) {
if checkForExistingPin() {
let oldCode = realm.objects(pinCode.self).first
try! realm.write {
oldCode!.pin = newPin
}
}
let newPinObject = pinCode()
newPinObject.pin = newPin
realm.add(newPinObject)
}
func checkPin(pin:String) -> Bool {
if checkForExistingPin() {
print ("Realm object first: ", realm.objects(pinCode.self).first?.pin as Any)
if pin == realm.objects(pinCode.self).first?.pin {
print ("Pin Correct")
return true
}
else {
print ("Pin Incorrect")
return false
}
}
print ("No existing pin")
return false
}
}
And here is the relevant code snippet of the ViewController:
class InitialViewController: UIViewController {
let myPin = pinCode()
let myManager = manager()
let realm = try! Realm()
#IBAction func NewUserButton(_ sender: Any) {
print("No existing PINs: ", self.myManager.checkForExistingPin())
}
The output is : Number of existing PINs: Optional(4)
You must have created a pinCode object (or multiple of them). "Optional(4) doesn't mean you have created 4 pins. You are counting String. It means that the object you retrieved has a 4 digit pin. If you haven't created any pinCode object, you should get nil. Or if you have created one without assigning a pin, you should get 0.
I recommend your looking at your realm file. You should be able to print out its location this way:
print(Realm.Configuration.defaultConfiguration.fileURL!)
You can then open the file with Realm Studio and verify what is in there.
You have a few things going on here:
Although this is not really in the scope of the question, here's a tip for the future. Your types' names should be capitalized (following CamelCase standard), as per Swift API Design Guidelines. Thus, your pinCodes and manager classes and pinCodeManager protocol should be called PinCode, Manager and PinCodeManager respectively.
Assuming you renamed your types and as other users pointed out, you're not counting instances of PinCode. You're counting the length of the pin member of PinCode class. Refactoring your checkForExistingPin() function:
func checkForExistingPin() -> Bool {
return realm.objects(pinCode.self).count > 0
}
In your enterNewPin(newPin:) function, in the case you already have a PinCode object stored, note that you are actually updating the old PinCode and adding a new one with the same pin. For instance, if you previously have a PinCode object stored with pin=1234. After calling enterNewPin(newPin: "5678") you will have two such objects stored with the pin=5678. You might want to refactor that as well:
func enterNewPin(newPin:String) {
if checkForExistingPin() {
let oldCode = realm.objects(pinCode.self).first
try! realm.write {
oldCode!.pin = newPin
}
} else {
let newPinObject = pinCode()
newPinObject.pin = newPin
try! realm.write {
realm.add(newPinObject)
}
}
}
Before trying to do any debugging in your app. I recommend you first uninstalling and then reinstalling the app wherever you running (simulator or actual device). If things keep behaving weird, that's probably something related with your configuration if you're overriding the default one (i.e. I noticed that you just used try! Realm() for retrieving a Realm, but you might have overridden Realm.Configuration.defaultConfiguration somewhere else).
Hope that helps!
Using Xcode-8.2.1, Swift-3.0.2, RealmSwift-2.2.0, iOS-Simulator-10:
Trying to write a View-Model with a Realm-Object, I fail at creating a returnArray in another thread. The issue is that the access to the previously created realm-object fails (most likely due to the background-thread access ??).
Can anybody tell me what is wrong with the following code (see below):
Important: It is given that the "createDataEntries()-method" is called before the "getEntries-completionHandler" (as can be seen with correct SimPholders realmobject-entry)! Therefore the "category" is set as "Love" (see code)
import Foundation
import RealmSwift
class MVVMCBalancesModel: BalancesModel
{
fileprivate var entries = [BalancesDataEntry]()
let realm = try! Realm()
init() {
self.createDataEntries()
}
fileprivate func createDataEntries() {
let myBalance = BalancesDataEntry()
myBalance.index = 0
myBalance.category = "Love" // !!!!!!! Here the category is filled
try! self.realm.write {
self.realm.deleteAll()
self.realm.add(myBalance)
}
}
func getEntries(_ completionHandler: #escaping (_ entries: [BalancesDataEntry]) -> Void)
{
// Simulate Aysnchronous data access
DispatchQueue.global().async {
var returnArray: [BalancesDataEntry] = [BalancesDataEntry]()
let realmy = try! Realm()
let cnt = realmy.objects(BalancesDataEntry.self).count
for idx in 0 ..< cnt {
let obj = realmy.objects(BalancesDataEntry.self).filter("index = \(idx)").first!
returnArray.append(obj)
}
completionHandler(returnArray) // !!!!!!! BREAKPOINT (see screenshot below)
}
}
}
Running the above code and setting a breakpoint at the completionHandler(returnArray) produces the following:
Why is the "category" of the returnArray an empty String ???
Properties of objects retrieved from a Realm are lazily retrieved from the underlying storage. Accessing the properties from Swift will return the appropriate values. Likewise, if you run po returnArray from Xcode's LLDB console you should see the object's complete state. The instance variables, shown in the debugger popover, are only used when the object is unmanaged (prior to being added to the Realm).
I am trying to add a record to a realm DB table.
I have a class Connection which represents a table I need in my DB and have created dynamic vars which are to represent the columns:
import Foundation
import RealmSwift
import Realm
open class ConnectionState: Object {
open dynamic var _id : String = NSUUID().uuidString
open dynamic var a : String = ""
open dynamic var b : String = ""
open dynamic var c : Int = 0
open override class func primaryKey() -> String? {
return "_id"
}
required public init() {
super.init()
}
required public init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
required public init(value: Any, schema: RLMSchema) {
fatalError("init(value:schema:) has not been implemented")
}
}
Then in my code I am trying to write and commit the write transaction like so:
let ConnectionState = ConnectionState()
ConnectionState.a = "a"
ConnectionState.b = "b"
ConnectionState.c = 1
try! self.realm.write {
self.realm.add(ConnectionState)
}
try! self.realm.commitWrite()
When running this code, I am receiving the error:
Can't commit a non-existing write transaction
What am I missing? Do I need to have inits in my ConnectionState class?
Before adding in the commitWrite, I was trying to view the db with realm browser. I found my device in xCode and chose to download the container but it was empty. Then I thought I needed to add in commitWrite
In your example you called commitWrite without having called beginWrite. You cannot commit a write transaction because you did not start one. Either start a write transaction or delete the commitWrite line.
Start transaction and commit it
self.realm.beginWrite()
self.realm.add(ConnectionState)
try! self.realm.commitWrite()
Delete commitWrite
try! self.realm.write {
self.realm.add(ConnectionState)
}
The Realm docs have two examples of adding data to the database.
Use the realm.write method
// Use them like regular Swift objects
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
print("name of dog: \(myDog.name)")
// Get the default Realm
let realm = try! Realm()
// Query Realm for all dogs less than 2 years old
let puppies = realm.objects(Dog.self).filter("age < 2")
puppies.count // => 0 because no dogs have been added to the Realm yet
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
Use realm.beginWrite() and realm.commitWrite() to start the write transaction and commit data to the database
let realm = try! Realm()
// Break up the writing blocks into smaller portions
// by starting a new transaction
for idx1 in 0..<1000 {
realm.beginWrite()
// Add row via dictionary. Property order is ignored.
for idx2 in 0..<1000 {
realm.create(Person.self, value: [
"name": "\(idx1)",
"birthdate": Date(timeIntervalSince1970: TimeInterval(idx2))
])
}
// Commit the write transaction
// to make this data available to other threads
try! realm.commitWrite()
}
try! self.realm.write {
self.realm.add(ConnectionState)
}
This code is somewhat equivalent to (possibly with some additional error handling):
realm.beginWrite()
...
try! realm.commitWrite()
Which means you're trying to commit your writes twice.
Just change your code like this:
try! self.realm.write {
self.realm.add(ConnectionState)
}
// try! self.realm.commitWrite()
I have this class inherit from Object:
class Location: Object {
dynamic var id: String = ""
dynamic var name: String = ""
override class func primaryKey() -> String {
return "id"
}
}
This class is used as an instance inside my manager like this:
class LocationServiceAPI {
fileprivate var _location: Location?
var location: Location? {
get {
if _location == nil {
let realm = try! Realm()
_location = realm.objects(Location.self).first
}
return _location
}
set {
let realm = try! Realm()
if let newValue = newValue {
// delete previous locations
let locations = realm.objects(Location.self)
try! realm.write {
realm.delete(locations)
}
// store new location
try! realm.write {
realm.add(newValue, update: true)
_location = newValue
}
} else {
let locations = realm.objects(Location.self)
try! realm.write {
realm.delete(locations)
}
}
}
}
}
So whenever I get a location I delete the old one (new and old locations could be identical) and replace it with the new one, then I used the newValue as new value for the property _location but whenever I try to access the location it gives me 'Object has been deleted or invalidated'.
I am really confused since location will hold the value passed from the setter but not the realm!!
Note: If I stop the deleting then It will work fine.
The Object has been deleted or invalidated error will occur if an object has been deleted from a Realm, but you subsequently try and access a stored property of an instance of that object that your code was hanging onto since before the deletion.
You'll need to examine your logic paths and make sure there's no way you're deleting the location object, and not subsequently updating the _location property. There's no mention of deleting the object in the sample code you've provided, but your if let newValue = newValue line of code would mean that _location wouldn't actually get cleared if you passed in nil.
Finally, it's possible to manually check if an object has been deleted from a Realm by calling _location.invalidated, so if this happens a lot, it might be a good idea to include some extra checks in your code as well.
Without knowing really anything about your app and your design choices, it looks like you're trying to avoid reading/writing to the DB too often by caching the location property. Unless you're working with tons of LocationServiceAPI objects it shouldn't be a real performance penalty to actually read/write directly in the DB, like this :
class LocationServiceAPI {
var location: Location? {
get {
let realm = try! Realm()
return realm.objects(Location.self).first
}
set {
let realm = try! Realm()
if let newValue = newValue {
// store new location
try! realm.write {
realm.add(newValue, update: true)
}
} else {
// delete the record from Realm
...
}
}
}
}
Also, I would in general avoid keeping Realm objects along for longer periods, I don't say it's not possible but in general it leads to issues like you've experienced (especially if do multi-threading). In most cases I'd rather fetch the object from DB, use it, change it and save it back in the DB asap. If keeping references to specific records in the DB is necessary I'd rather keep the id and re-fetch it when I need it.
I would like to use the realm in my project, but I have a very complex filter and sort. I have to order the list by name,but the name is in other class.
class CustomObject: Object
{
dynamic var objectId = 0
let objectLangs = List<ObjectLang>()
}
class ObjectLang: Object
{
dynamic var objectId = 0
dynamic var name = ""
}
When I have more than 130 rows, it is very slow in main thread and it blocks the UI. I tried do it in a background thread, but when I want to update the UI, it was crashed by Realm. So what is the perfect solution? How could I use it? Could you give me an example or tutorial? I have read the guide line.
If program is crashed when updating UI on background thread, you should update UI on main thread when realm task finished.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {
let realm = try! Realm()
//do What you need
dispatch_async(dispatch_get_main_queue(), {
//updateUI()
})
})
You can't use Realm accessors across threads. You will need to retrieve the objects on the thread on which you want to use them. To make that happen, I'd recommend for each of your object classes which need to be passed between threads to designate a property as primary key. This property might be objectId in your case.
class CustomObject: Object {
dynamic var objectId = 0
let objectLangs = List<ObjectLang>()
override class func primaryKey() -> String {
return "objectId"
}
}
This primary key can then be used to identify your objects and pass them over to the main thread to retrieve them there again.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) {
let realm = try! Realm()
var objects = realm.objects(CustomObject)
// objects = objects.filter(…)
let sortedObjects: [CustomObject] = objects.sort { /* … */ }
let ids = sortedObjects.map { $0.objectId }
dispatch_async(dispatch_get_main_queue()) {
let realm = try! Realm()
let objects = ids.map {
realm.objectForPrimaryKey(CustomObject.self, key: $0)
}
updateUIWithObjects(objects)
}
}