Realm errors: RLMArray and Migration - ios

I ran into some errors when I'm trying to compile my realm code, this is the first version of the code:
import RealmSwift
class Test: Object {
dynamic var name = ""
dynamic var dict = Dictionary<String, Int>()
dynamic var owner: TestList?
}
class TestList: Object{
dynamic var name = ""
let tests = List<Test>().filter("ALL Test.dict[hello] != nil")
}
//ViewController
import RealmSwift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let realm = Realm()
let test1 = Test()
test1.name = "test1"
test1.dict = ["hello": 1]
realm.write{realm.add(test1)}
let test2 = Test()
test2.name = "test2"
test2.dict = ["nihao": 2]
realm.write{realm.add(test2)}
let test3 = Test()
test3.name = "test3"
test3.dict = ["hello": 3]
realm.write{realm.add(test3)}
}
#IBOutlet weak var label: UILabel!
#IBAction func set(sender: UIButton){
let test = Test()
let realm = Realm()
test.name = "not using CoreData"
realm.write{realm.add(test)}
text = "abc"
}
#IBAction func show(sender: UIButton){
let test = Realm().objects(TestList)
var str = ""
println("got to here")
for i in test{
str += " \(i.name)"
}
label.text = str
}
}
With this setup I got an error in my log that says:
Terminating app due to uncaught exception 'RLMException', reason: 'This method can only be called on RLMArray instances retrieved from an RLMRealm'
When I removed the filtering in my TestList object, the error became: Terminating app due to uncaught exception 'RLMException', reason: 'Migration is required for object type 'Test' due to the following errors:
- Property 'dict' has been added to latest object model.
- Property 'owner' has been added to latest object model.'
Am I understanding Realm's documentation completely wrong?

The first error is right. You can't filter on properties. Furthermore Realm doesn't allow Dictionary properties at the moment. You would need to explicitly model that as a list property of a dedicated Realm object entity, which has fields to hold a String and an Int.
class Test: Object {
dynamic var name = ""
dynamic var dict = List<TestRelatedThing>()
dynamic var owner: TestList?
}
class TestRelatedThing : Object {
dynamic var key: String
dynamic var value: Int
}
class TestList: Object{
dynamic var name = ""
let tests = List<Test>()
}
You can then query at runtime by:
Realm().objects(TestList).filter("ANY tests.key == %#", "hello")
Hint: The aggregate operator ALL is not supported for Realm. See our predicate cheat sheet for a full list of supported operators. Here it makes anywhere more sense to existential quantify that operation, because of the changed data structure, which doesn't allow an entry of TestRelatedThing with it's value property to equal nil to exist in the first place. So you can just make sure that there is an entry with key "hello" instead.
For the second error: I guess you tried to run your code before and added after that first run further fields to your model objects.
You can reset the simulator state or might just add one of the following lines of code to ensure to start with a fresh Realm on each run while development.
# Swift 1.2
var error: NSError?
NSFileManager.defaultManager().removeItemAtPath(Realm.defaultPath, error:&error)
# Swift 2
try! NSFileManager.defaultManager().removeItemAtPath(Realm.defaultPath)

Related

iOS - App crash on changing a REALM object property

I am using RealmSwift in a project. I have a model as below
#objc final class Diary : Object, Codable {
#objc dynamic public var id: Int = 1
#objc dynamic public var notes: String = ""
}
public func persistDiary(){
let realm = StorageServiceManager.shared.getRealm()
do{
try realm.write {
realm.add(self)
}
}catch{
debugPrint(error)
}
}
I wrote few Diary objects to the REALM db. I was able to fetch them also using below code
let realm = StorageServiceManager.shared.getRealm()
let notes = realm.objects(Diary.self)
After fetching those objects, I just tried updating a property of an object and the app got crashed. The code for that is as below,
var currentNotes = notes[0]
currentNotes.id = 2//This line leads to the crash
currentNotes.notes = "testing"
Console message: libc++abi.dylib: terminating with uncaught exception of type NSException
Any help will be great, Thanks.
You need to update your object inside a write transaction. Your code should look something like:
let realm = try! Realm()
let notes = realm.objects(Diary.self)
if let currentNotes = notes[0] {
try! realm.write {
currentNotes.id = 2//This line leads to the crash
currentNotes.notes = "testing"
}
}
To make a copy of your object, you can do it like this:
let currentNoteCopy = Diary(value: notes[0])
currentNoteCopy.id = 2

Error when user tries to send next message immediately in Realm

Here are my two realm models used to create objects in realm database.
class Users: Object {
dynamic var phoneNumber:String = ""
dynamic var messageSenderName:String = ""
let userMessages = List<Messages>()
override static func primaryKey() -> String? {
return "phoneNumber"
}
// convenience init() here
}
class Messages: Object{
dynamic var id = UUID().uuidString
dynamic var phoneNumber:String = ""
dynamic var messageSenderName:String = ""
dynamic var messageBody:String = ""
dynamic var message_id:String = ""
dynamic var messageTime:String = ""
override static func primaryKey() -> String? {
return "id"
}
// convenience init here
}
This my RosterViewController where I show the 'phoneNumber' and latest 'messageBody' in tableViewCell. I am using XMPP Framework to receive messages and SwiftyXMLParser to parse the xml messages.
class RosterViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var realm : Realm!
dynamic var users = Users()
var userResult : Results<Users>!
dynamic var messagedata = Messages()
var messageResult : Results<Messages>!
override func viewDidLoad() {
super.viewDidLoad()
realm = try! Realm()
notificationToken = realm.observe{ notification, realm in
self.contactsTableView.reloadData()
}
}
func xmppStream(_ sender: XMPPStream, didReceive message: XMPPMessage) {
.......
// parse xml and set object values here
userResult = realm.objects(Users.self)
messageResult = realm.objects(Messages.self)
try! realm.write {
users.userMessages.append(messagedata)
realm.add(users, update: true)
realm.add(messagedata, update: true)
}
}
}
}
I want to append the messages dynamically to the users List of the respective message sender. I am not able to do so. I am receiving messages in real time so they should get appended to the list and thus reflect the same on tableView.
First message is shown properly, but when same user sends another message immediately then I get an error saying:
RLMException', reason: 'Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first
I have also tried a lot, but could not get any fixes or workaround. Please help me solve this issue, just began with realm. Thank you!

Save User using Realm Object

I am creating a project. I want if the userID already exist, it doesn't add the user. But somehow my code isn't working properly.
This is my Realm Model Object (User.swift):
import Foundation
import RealmSwift
class User: Object {
#objc dynamic var userID = Int()
#objc dynamic var username = ""
#objc dynamic var full_name = ""
#objc dynamic var myBool = Bool()
override static func primaryKey() -> String? {
return "userID"
}
}
And this is the button to add users:
#IBAction func add(_ sender: Any) {
let myUser = User()
let JSON_userID = Int(arc4random_uniform(5)) // This is temporary. I am going to get code from JSON, but using random for testing purpose.
if (myUser.userID != JSON_userID) {
myUser.userID = JSON_userID
myUser.username = "myUsername"
myUser.full_name = "My Name"
let realm = try! Realm()
try! realm.write {
realm.add(myUser)
}
}
else {
print("Already exist")
}
}
Sometimes it runs the code, but most of the times it crashes with error:
libc++abi.dylib: terminating with uncaught exception of type NSException.
As you defined a primary key in your User object, Realm can handle this automatically if you set the update parameter to true inside the write closure.
let realm = try! Realm()
try! realm.write {
realm.add(myUser, update: true)
}
If the update parameter is not set or false, Realm will throw an exception when you try to add an object with an existing primary key.
This makes the if / else condition useless. It can be removed.
If you need to know if the user already exists, you can request the Realm with the primary key value:
realm.object(ofType: User.self, forPrimaryKey: JSON_userID)
The result will be nil if the user does not exist.

Issue with updating Realm objects

I have two Realm model classes
class ModelA: Object {
let id = RealmOptional<Int>()
dynamic var name: String!
// some other variables that are also String! type
}
class ModelB: Object {
let id = RealmOptional<Int>()
let models = List<ModelA>()
// other variables
}
And I have some JSON object which contains data for that models. I create ModelB instance then populate it with ModelA instances list the following way:
let json: JSON = ... // get it from somewhere, then use SwiftyJSON
let myModelB = ModelB()
myModelB.id.value = json["id"].object as? Int
// set other properties
let modelsA = json["models"].map { ModelA(value: $0.1.object) }
myModelB.models.appendContentsOf(modelsA)
The reason why I am using different approaches here is that propery names in JSON doesn't match to my property names for ModelB but for ModelA it is ok.
Somewhere later I use realm.add(objects, update: true) (inside realm.write) and that leads to the following exception:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key (null).'
According to the documentation:
If your model class includes a primary key, you can have Realm intelligently update or add objects based off of their primary key values using Realm().add(_:update:).
so both ModelA and ModelB has primaryKey() function and I believe that should work but it is not.
Furthermore, I removed update parameter in the call and added call to the realm.deleteAll() before adding new objects (both in write callback). In that case I get the following exception:
Terminating app due to uncaught exception 'RLMException', reason: 'Can't set primary key property 'id' to existing value 'xxxxxxx'.
Additionally if I try to go through the call stack, Xcode crashes. It also crashes if I try to inspect any Realm object in the debugger. I already have installed Realm Xcode plugin but nothing changes. I cannot understand what is going wrong here and why I get so strange behavior. Could someone tell me where is my mistake please?
I start project from scratch, base on example project. So I finished with:
import UIKit
import RealmSwift
// Dog model
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
dynamic var owner: Person? // Properties can be optional
override class func primaryKey() -> String? { return "name" }
}
// Person model
class Person: Object {
dynamic var name = ""
let dogs = List<Dog>()
override class func primaryKey() -> String? { return "name" }
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
do {
try NSFileManager.defaultManager().removeItemAtPath(Realm.Configuration.defaultConfiguration.path!)
} catch {}
let dogRexJSON: AnyObject = ["name": "Rex", "age" : 20]
let dogLuckyJSON: AnyObject = ["name": "Lucky", "age" : 25]
var somePerson = Person(value: ["name" : "Shurik", "dogs" : [dogRexJSON]])
// Realms are used to group data together
let realm = try! Realm() // Create realm pointing to default file
// Save your object
realm.beginWrite()
realm.add(somePerson)
try! realm.commitWrite()
somePerson = Person(value: ["name" : "Shurik", "dogs" : [dogRexJSON, dogLuckyJSON]])
try! realm.write { () -> Void in
realm.add([somePerson], update: true)
return
}
let val = realm.objectForPrimaryKey(Dog.self, key: "Lucky")
print(val!.name) // as expected log >> Lucky
return true
}
}
Seems all works fine.

Realm Swift EXC_BAD_ACCESS During Segue with Unpersisted Object

I've got an app that I've started adding Realm to and I think I must be doing something wrong because I keep running into EXC_BAD_ACCESS when passing unpersisted objects between view controllers.
Here's a stripped down version of my app.
class TodoTask: Object {
dynamic var name: String = ""
let steps = List<Step>()
convenience init(name: String) {
self.init()
self.name = name
}
}
class Step: Object {
dynamic var name: String = ""
convenience init(name: String) {
self.init()
self.name = name
}
}
class TodoListController: UIViewController {
let todos = List<TodoTask>()
override func viewDidLoad() {
super.viewDidLoad()
var t1 = TodoTask(name: "Todo1")
let steps = [
Step("Do this"),
Step("Do that"),
]
t1.steps.appendContentsOf(steps)
var t2 = TodoTask(name: "Todo2")
let steps = [
Step("Do these"),
Step("Do those"),
]
t2.steps.appendContentsOf(steps)
todos.appendContentsOf([t1, t2])
// Display table of todos.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let detailsController = segue.destinationViewController as? TodoDetailViewController,
let selectedTask = getSelectedTask() {
detailsController.task = selectedTask
}
}
}
class TodoDetailViewController: UIViewController {
var task: TodoTask? // <<< EXC_BAD_ACCESS
// Display the task's steps.
}
Unfortunately, I can't figure out what triggers the EXC_BAD_ACCESS and it happens intermittently. I didn't copy a stacktrace (d'oh!) but I remember it being in the C++ destructor for some sort of Row object. This is odd to me because there doesn't seem to be a database file in my Documents folder.
I'm pretty confident this is a Realm-based error because I experienced no weird crashes until I converted my plain Swift objects to Realm objects.
My only hunch is that I might be using List wrong since the warning This method can only be called during a write transaction. is in the comments of the appendContentsOf method. However, I was under the impression that I could use Realm objects that weren't stored in a Realm just like normal Swift objects.
Am I using Realm objects wrong? Is there anything else I can look into?
I'm on Realm 0.95.2.
Thanks!
Edit:
func getSelectedTask() -> TodoTask? {
if let index = taskTableView.indexPathForSelectedRow {
return tasks[index]
}
return nil
}
The issue is that a Realm List should not be created directly. This class is only used to manage to-many relationships on Object models. Instead, you should use a standard Swift array to store the unpersisted Realm Objects.
So switch:
let todos = List<TodoTask>()
to
let todos = [TodoTask]()

Resources