realm / realm-swift

Realm is a mobile database: a replacement for Core Data & SQLite
https://realm.io
Apache License 2.0
16.31k stars 2.14k forks source link

Realm addNotificationBlock returning error #4693

Closed tuomijal closed 7 years ago

tuomijal commented 7 years ago

Goals

I am trying to instantiate a NotificationToken to listen to changes in a specific property of my Realm objects. The goal is to display every object matching the query in viewController.

Expected Results

The variable called 'location' in my Realm objects is changed by a helper method which is triggered by a Timer instance. When 'location' property is changed to value 2, I expect the Realm to notify the notificationToken object about the change allowing me to push the new result to viewController.

Actual Results

Immediately after launching the app, addNotificationBlock method returns the following error: Error Domain=io.realm Code=1 "std::exception" UserInfo={NSLocalizedDescription=std::exception, Error Code=1} Notifications are not listened after this. (Which is of course the way this enumeration is supposed to work.) I opened a thread on StackOverflow and based on given instructions managed to setup a breakpoint which pointed to method called void RealmCoordinator::pin_version(VersionID versionid)

Steps to Reproduce

It is difficult to say how to reproduce this error since I am (as far as I'm concerned) using the addNotificationBlock as instructed and not trying to accomplish anything complicated. I am happy to deliver the full Xcode workspace if necessary.

Code Sample

The contents of my viewController:

import UIKit
import RealmSwift

class PatientCell: UITableViewCell {

    @IBOutlet weak var hetu: UITextView!
    @IBOutlet weak var name: UITextView!
    @IBOutlet weak var photo: UITextView!

}

class PäivystyslistaVC: UIViewController, UIScrollViewDelegate, UITableViewDelegate, UITableViewDataSource, UIPopoverControllerDelegate {

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet var patientPopupView: UIView!

    var patients: Results<Patient2>!
    var realm = try! Realm()
    var timer: Timer!
    var notificationToken: NotificationToken? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
        let realm = try! Realm()
        patients = realm.objects(Patient2.self).filter("location = 2")

        print("Patients on the ER: \(patients)")

        notificationToken = patients.addNotificationBlock { (changes: RealmCollectionChange) in
            switch changes {
            case .initial:
                print("From initial")
                break
            case .update:
                print("From update")
                break
            case .error(let err):
                print("Error occured \(err)")
                break
            }
        }

        timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(self.addPatient), userInfo: nil, repeats: true)
    }

    func addPatient() {
        print("")
        print("Timer launched")
        print("Patients on the ER: \(patients.count)")
        sendPatientToER()
        print("")
        tableView.reloadData()
    }

    // MARK: TableView:n hallinta
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return patients.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "patient", for: indexPath) as! PatientCell

        let patient = patients[indexPath.row]
        cell.hetu?.text = patient.hetu
        cell.name?.text = patient.fullName
        cell.photo?.text = patient.photo

        return cell

    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let potilastiedotVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PotilastiedotVC")
        self.present(potilastiedotVC, animated: true, completion: nil)

        tableView.deselectRow(at: indexPath, animated: true)
    }

    func tableView(_ tableView: UITableView, editActionsForRowAt: IndexPath) -> [UITableViewRowAction]? {
        let kotiuta = UITableViewRowAction(style: .normal, title: "Kotiuta") { action, index in
            try! self.realm.write {
                self.realm.create(Patient2.self, value: ["id": index.row, "location": 1], update: true)
            }
            self.tableView.deleteRows(at: [index], with: .left)

        }
        kotiuta.backgroundColor = UIColor.vihreä

        let osastolle = UITableViewRowAction(style: .normal, title: "Osastolle") { action, index in
            try! self.realm.write {
                self.realm.create(Patient2.self, value: ["id": index.row, "location": 3], update: true)
            }
            self.tableView.deleteRows(at: [index], with: .top)
        }
        osastolle.backgroundColor = UIColor.oranssi

        let lähetä = UITableViewRowAction(style: .normal, title: "Lähetä") { action, index in
            try! self.realm.write {
                self.realm.create(Patient2.self, value: ["id": index.row, "location": 3], update: true)
            }
            self.tableView.deleteRows(at: [index], with: .top)
        }
        lähetä.backgroundColor = UIColor.vaaleansininen

        return [kotiuta, lähetä, osastolle]
    }

In addition I have a helper method in another file:

import UIKit
import RealmSwift

func sendPatientToER() {
    let realm = try! Realm()
    let count = realm.objects(Patient2.self).filter("location == 1").count
    print("Count of patients waiting at home: \(count)")
    let randomId = Int(arc4random_uniform(UInt32(count)))
    print("Random id generated: \(randomId)")
    realm.beginWrite()
    realm.create(Patient2.self, value: ["id": randomId, "location": 2], update: true)
    try! realm.commitWrite()
}

Thank you in advance.

Version of Realm and Tooling

Realm version: 2.4.2

Xcode version: 8.2.1

iOS/OSX version: 10.12.3

Dependency manager + version: Cocoapods 1.1.1

pigeon-archive commented 7 years ago

Hi @EmilOsvald. Thanks for reaching out about this. My apologies for the delay in getting back to you. I'll have one of the engineers look at this and follow-up with a solution or additional questions.

tgoyne commented 7 years ago

There's nothing particularly suspicious in the code you've posted. If you're willing to share the full project I'd be happy to dig into it, as the function that's throwing is something that shouldn't be able to fail.

tuomijal commented 7 years ago

Hi @tgoyne, thank you for your input and sorry for the delay. I cleared the decks and everything works in my refactored project like a charm. I suppose the problem was that I had done some rookie mistake in my code (as I had suspected). Would you nevertheless like to see the original version?

tgoyne commented 7 years ago

Yes, that'd still be helpful. Even if you did only hit problems due to a mistake on your end, you still hit a case where Realm isn't behaving properly and it'd be good to be able to fix that.

TimOliver commented 7 years ago

Hey @EmilOsvald! Just checking in on this issue. Would you be able to provide us with a copy of the original version of your project for us to analyse? Thanks a lot!

tuomijal commented 7 years ago

Hi @TimOliver. I went through all my previous app versions on Git but unfortunately the version of interest seems to be lost. I am sorry.

TimOliver commented 7 years ago

@EmilOsvald Ahh okay. No problems then! It can't be helped. If you do manage to find the issue again, please let us know! Thanks!

hons82 commented 5 years ago

I know this issues quite old and probably not interesting anymore, I was struggling however with the same problem for quite a while now and I'd like to share my findings. In my case it happened after a bulk delete. Whenever a user does a logout I have to clear some of my tables

- (void)deleteSecureContent:(RLMRealm *)realm {
    NSError *error;
    NSArray *array = [realm.schema.objectSchema valueForKey:@"className"];
    [realm transactionWithBlock:^{
        for(NSString *className in array){
            Class<VLProtectedEntity> realmClass = NSClassFromString(className);
            if(realmClass && [[realmClass class] respondsToSelector:@selector(isProtectedEntity)]){
                BOOL protected = [[realmClass class] isProtectedEntity];
                if(protected){
                    [realm deleteObjects:[[realmClass class] allObjectsInRealm:realm]];
                }
            }
        }
    } error: &error];
    [realm refresh];
    if (error) {
        DDLogError(@"error %@ deleteSecureContent",error);
        [[Crashlytics sharedInstance] recordError:error];
    }
}

This is already the working version of the code. The important row in this case was the call to refresh the realm. Without that I had exactly the same error. It's probably important to say that I'm working with two realms in this project one is persistent and one is in-memory and only the latter, so the in-memory instance did suffer from this problem.

I'm on Realm (3.18.0)