About transaction in Realm database - ios

This is my Realm object:
class AchievementDate : Object {
dynamic var date: Date = Date()
dynamic var apple: Int = Int(0)
func save() {
do {
let realm = try Realm()
try realm.write {
realm.add(self)
}
} catch let error as NSError {
fatalError(error.localizedDescription)
}
}
}
I change apple's value in View controller's viewDidLoad() method, as you can see:
override func viewDidLoad() {
super.viewDidLoad()
achievementDate.apple = 2
achievementDate.save()
}
then I update the apple's value when user clicks the pause button on the screen like this:
#IBAction func pausedButtonTapped(_ sender: UIButton) {
achievementDate.apple += 1
achievementDate.save()
}
Xcode runs it successfully but when I click the pause button, the app crashes. In the console it says:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to modify object outside of a write transaction - call
beginWriteTransaction on an RLMRealm instance first.'
I am quite confused about this, btw, what does transaction mean in general? Thanks a lot.

A write transaction is used to group modifications of objects within a Realm into a single unit of work. Managed Realm objects may only be modified within a write transaction. The write transaction is scoped to the block you pass to the call to Realm.write(_:). The call to write begins a write transaction, the body is executed with the transaction active, and when the block returns the write transaction is committed and the changes are persisted to the Realm file.
You've not shared how achievementDate is initialized, but it seems safe to assume based on the exception you're seeing that it is an AchievementDate instance that is a managed object (that is, it was either created then added to a Realm, or it was loaded from a Realm). As the exception notes, you can only modify managed objects within a write transaction. You can either expand the scope of your write transaction to encompass the modification of the managed object, or you can avoid modifying a managed object altogether (by adding a primary key to your model class, and using either Realm.create(_:value:update:) or Realm.add(_:update:) with update: true to update an existing object with the given primary key value).

First, you need to define a Primary Key if you want to update the model:
class AchievementDate: Object {
dynamic var id = 0
dynamic var date:Date = Date()
dynamic var apple:Int = Int(0)
override static func primaryKey() -> String? {
return "id"
}
}
Then update the model like:
static func save(achievementDate:AchievementDate) {
let realm = try! Realm()
try! realm.write {
realm.add(achievementDate, update: true)
}
}
Alternatively, if you don't want primary Key, and the model is already retrieved from Realm, you could update the model like this:
let realm = try! Realm()
try! realm.write {
achievementDate.apple = 2
}

Related

Why do my linking objects not have a Realm?

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.

Realm Object in Completion Handler: 'Realm accessed from incorrect thread.'

I am developing an SDK that basically writes wrapper methods hiding API calls and returns data objects in completion Handler:
public static func getDogs(token: String?, completionhandler: #escaping (Int?, [Dog]?) -> Void) {
}
Dogs are Realm Objects:
public class Dog: Object {
#objc public dynamic var name: String?
#objc public dynamic var age: Int = 0
}
Now when I try to access Dogs in a call to getDogs,
getDogs(token: token) { status, dogs in
\\ Access Dog[0]
}
As expected, I get the RealmException:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.'
Now, How can I pass such a list of Realm objects in a completion handler and access it later without getting this exception? I know of ThreadSafeReference but I do not want end user to read a manual of third party dependency before being able to use the SDK. What should be the best practice here? Obviously, I cannot send a list of Realm objects. Also from the design considerations for an independent framework, I should not assume that our end users should know Realm apriori. Realm support should be good to have and not foremost.
Another option that I have is to keep two copies of the same data model: a normal object and a Realm object. But this will increase the parsing time.
As stated in the Realm documentation:
Instances of RLMRealm, RLMResults, or RLMArray, or managed instances of RLMObject are thread-confined, meaning that they can only be used on the thread on which they were created, otherwise an exception is thrown
In order to avoid this exception, you should create a Realm instance every time you need one and make sure you're releasing it with an autorelease pool.
// Query and update from any thread
autoreleasepool {
let realm = try! Realm()
let theDog = realm.objects(Dog.self).filter("age == 1").first
try! realm.write {
theDog!.age = 3
}
}

realm-cocoa save an empty object rather than object I passed

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

Design pattern for Realm persistence

I'm using Realm in a App and I'm trying to abstract much as possible so that in the future I can swap database providers without too much change.
This pattern has worked well although I'm concerned about the following.
Is creating a new realm object everytime a overhead (My current understanding is that Realm Objects are cached internally)?
Are there any problems with the way I'm using Realm ?
Are there any better design patterns for my purpose ?
public struct BookDataLayer: BookDataLayerProvider {
func isBookAvailable(bookIdentifier: String) throws -> Bool {
let database = try getDatabase()
return !database.objects(Book).filter("identifier = %#", bookIdentifier).isEmpty
}
func createOrUpdateBook(bookIdentifier: String, sortIndex: Int) throws {
let book = Book()
Book.bookIdentifier = bookIdentifier
Book.sortIndex = sortIndex
try create(book, update: true)
}}
protocol BookDataLayerProvider : DataAccessLayer {
func isBookAvailable(bookIdentifier: String) throws -> Bool
func createOrUpdateBook(bookIdentifier: String, sortIndex: Int) throws n}
extension DataAccessLayer {
func getDatabase() throws -> Realm {
do {
let realm = try Realm()
// Advance the transaction to the most recent state
realm.refresh()
return realm
} catch {
throw DataAccessError.DatastoreConnectionError
}
}
func create(object: Object, update: Bool = false) throws {
let database = try self.getDatabase()
do {
database.beginWrite()
database.add(object, update: update)
// Commit the write transaction
// to make this data available to other threads
try database.commitWrite()
} catch {
throw DataAccessError.InsertError
}
}}
// Usage
let bookDataLayer = BookDataLayer()
bookDataLayer.isBookAvailable("4557788")
bookDataLayer.createOrUpdateBook("45578899", 10)
That's a completely solid design pattern. It's pretty common for developers to abstract the data layer APIs way from their code in case they need to switch it out.
In response to your questions:
You're correct. Realm object instances are internally cached, so you can easily call let realm = try! Realm() multiple times with very little overhead.
Unless you've found a specific reason, it's probably not necessary to call refresh() on the Realm instance every time you use it. Realm instances on the main thread are automatically refreshed on each iteration of the run loop, so you only need to call refresh() if you're expecting changes on a background thread, or need to access changes before the current run loop has completed.
'Better' design patterns is probably a matter of opinion, but from what I've seen from other codebases, what you've got there is already great! :)

Realm and Parse integration: how to save the realm

I'm trying to create a bundle realm for my application. I thought it should be quite simple. Since I already have all needed records in Parse, I just have to:
create realm models and objects
load parse records to realm objects
save the realm
So, I created two realm models:
class RealmContainer : Object {
dynamic var rContainerId: String! //should be equal objectId from Parse
dynamic var rContainerName: String! //should be equal "name" field from Parse
...
var messages = List<RealmMessage>()
override static func primaryKey() -> String? {
return "rContainerId"
}
}
and
class RealmMessage : Object {
dynamic var rMessageId: String!
...
dynamic var rParentContainer: RealmContainer!
}
Getting results from Parse seems to be working. Also my realm objects are also good
var allUserContainers: [RealmContainer] = []
I was able to populate this array with values from Parse. But when I'm trying to save this realm, I'm getting a) nothing or b) error message
My code (this one'll get nothing):
let realm = try! Realm()
try! realm.write {
realm.add(self.allUserContainers[0])
print(Realm().path)
print(realm.path)
}
My code (this one'll get nothing too):
let realm = try! Realm()
try! realm.write {
realm.create(RealmContainer.self, value: self.allUserContainers[0], update: true)
print(Realm().path)
print(realm.path)
}
My code 3 (this will get me an error message "Terminating app due to uncaught exception 'RLMException', reason: 'Illegal recursive call of +[RLMSchema sharedSchema]. Note: Properties of Swift Object classes must not be prepopulated with queried results from a Realm"):
//from firstViewController, realm is a global variable
let realm = try! Realm()
//another swift module
try! realm.write {
realm.create(RealmContainer.self, value: self.allUserContainers[0], update: true)
print(Realm().path)
print(realm.path)
}
Obviously I don't properly understand how it should work, but I tried several swift/realm tutorials and they were actually straightforward. So, what did I do wrong?
Update
So, I updated my code to make it as much simple/readable as possible. I have a Dog class, and I am trying to get Dogs from Parse and put them to Realm.
AppDelegate.swift
let realm = try! Realm() //global
Dog.swift
class Dog : Object {
dynamic var name = ""
dynamic var age = 0
}
User.swift (getDogs and putDogs functions)
class User {
var newDogs:[Dog] = []
...
func getDogs() {
self.newDogs = []
let dogsQuery = PFQuery(className: "Dogs")
dogsQuery.limit = 100
dogsQuery.findObjectsInBackgroundWithBlock { (currentModes, error) -> Void in
if error == nil {
let tempModes:[PFObject] = currentModes as [PFObject]!
for var i = 0; i < tempModes.count; i++ {
let temp = Dog()
temp.name = currentModes![i]["dogName"] as! String
self.newDogs.append(temp)
}
} else {
print("something happened")
}
}
}
...
func putDogs() {
print(self.newDogs.count)
try! realm.write({ () -> Void in
for var i = 0; i < newDogs.count; i++ {
realm.add(newDogs[i])
}
})
try! realm.commitWrite() //doesn't change anything
}
error message still the same:
Terminating app due to uncaught exception 'RLMException', reason:
'Illegal recursive call of +[RLMSchema sharedSchema]. Note: Properties
of Swift Object classes must not be prepopulated with queried
results from a Realm
I believe I just have some global misunderstanding about how Realm is working because it is extremely simple configuration.
About your RealmSwift code : You have implemented it right.
When you declare a class of realm in swift, it's a subclass of Object class. Similarly for the parse it's subclass of PFObject.
You custom class have to have only one base class. So can't use functionalities of both the libraries Parse as well as Realm.
If you have to have use both Parse and Realm, you need to declare two different classes like RealmMessage and ParseMessage.
Retrieve data for ParseMessage from parse and copy properties to RealmMessage.
Why you want to use both Parse and Realm ?
parse also provides local data store by just writing Parse.enableLocalDatastore() in appDelegate before
Parse.setApplicationId("key",
clientKey: "key")
Your Realm Swift code seems fine. Realm is very flexible when creating objects using (_:value:update:), in being able to take in any type of object that supports subscripts. So you could even directly insert PFObject if the schemas matched.
The problem seems to be how you're populating the allUserContainers array. As the error says, you cannot fetch a Realm Object from Realm and then try and re-add it that way. If you're trying to update an object already in Realm, as long as the primary key properties match, you don't need to supply the whole object back again.
Can you please revisit the logic of how your allUserContainers variable is being populated, and if you can't fix it, post the code into your question?
Sidenote: You probably don't need to define your Realm properties as implicitly unwrapped as well. This is the recommended pattern:
class RealmContainer : Object {
dynamic var rContainerId = ""
dynamic var rContainerName = ""
}
Actually I found what was wrong: as i suspected it was a stupid mistake, some property in my class was UIImage type, and Realm just can't work with this type (even if I'm not trying to put objects of this class into realm). So, my bad. I am slightly embarassed by it, but will not delete my question because error messages from Realm were not very understandable in this case

Resources