Skip to content
This repository has been archived by the owner on Feb 8, 2022. It is now read-only.

attempt to delete and reload the same index path #66

Closed
Carpemeid opened this issue Jan 31, 2016 · 7 comments
Closed

attempt to delete and reload the same index path #66

Carpemeid opened this issue Jan 31, 2016 · 7 comments

Comments

@Carpemeid
Copy link

Here is my code:

let carsObjects : [CarObject] = carsDictionaries.map({CarObject(value: $0)})

let query : QueryModel = QueryModel(startDate: startDate, untilDate: untilDate, location: location)

carsObjects.forEach({$0.searchQueries.append(query)})

query.cars.appendContentsOf(carsObjects)

let realm : Realm = try! Realm()

try! realm.write({ () -> Void in
      realm.addNotified(carsObjects, update: true)
})

As mentioned in the title I receive the tableview error "attempt to delete and reload the same index path". It happens with the default RealmResultsController code inside the "didChangeObject". Which is like this:

func didChangeObject<U>(controller: AnyObject, object: U, oldIndexPath: NSIndexPath, newIndexPath: NSIndexPath, changeType: RealmResultsChangeType) {
        switch changeType {
        case .Delete:
            resultsTableView.deleteRowsAtIndexPaths([newIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
        case .Insert:
            resultsTableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
        case .Move:
            resultsTableView.deleteRowsAtIndexPaths([oldIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
            resultsTableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
        case .Update:
            resultsTableView.reloadRowsAtIndexPaths([newIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
        }
    }

And the error occurs when this delegate function is executed:

func didChangeResults(controller: AnyObject) {
        resultsTableView.endUpdates()
    }

Below is my CarObject model class:

class CarObject : Object
{
    dynamic var pictureURL : String = ""
    dynamic var modelName : String = ""
    dynamic var manufacturerName : String = ""
    dynamic var userName : String = ""
    dynamic var href : String = ""

    let distance : RealmOptional<Float> = RealmOptional<Float>()
    let price : RealmOptional<Float> = RealmOptional<Float>()

    let searchQueries : List<QueryModel> = List<QueryModel>()

    override static func primaryKey() -> String?
    {
        return "href"
    }

    class func resultsController() -> RealmResultsController<CarObject, CarObject>?
    {
        var resultsController : RealmResultsController<CarObject, CarObject>? = nil

        do
        {
            let realm : Realm = try Realm()
            let predicate : NSPredicate = NSPredicate(format: "href!=''")
            let request : RealmRequest = RealmRequest<CarObject>(predicate: predicate, realm: realm, sortDescriptors: [SortDescriptor(property: "manufacturerName")])

            resultsController = try RealmResultsController<CarObject, CarObject>(request: request, sectionKeyPath: nil)
        }
        catch
        {
            print("failed to create car object realm results controller")
        }

        return resultsController
    }
}

And the query model class:

class QueryModel : Object
{
    dynamic var startDate : NSDate?
    dynamic var untilDate : NSDate?
    dynamic var location : String = ""

    dynamic var uniqueKey : String = ""

    let cars : List<CarObject> = List<CarObject>()

    convenience init(startDate : NSDate?, untilDate : NSDate?, location : String?)
    {
        self.init()
        self.startDate = startDate
        self.untilDate = untilDate
        self.location = location ?? ""
        self.uniqueKey = (startDate?.completeDateString() ?? "") + (untilDate?.completeDateString() ?? "") + (location ?? "")
    }

    override static func primaryKey() -> String?
    {
        return "uniqueKey"
    }

@Carpemeid
Copy link
Author

If I comment "query.cars.appendContentsOf(carsObjects)" then everything seems to be ok with tableView updates/inserts/delete.

If I leave it uncommented then as I can conclude some additional notifications are sent to the RealmResultsController and it tries to delete and reload at the same index path or something like that.

But now I also encountered 'attempt to delete row 0 from section 0, but there are only 0 sections before the update'. Which makes no sense as at that point the db has 0 rows because it's the first app launch. If I preload the db with at least 1 record, then this error also does not occur.

And that becomes spooky. Maybe the models inside the results controller are not updated yet(background thread) when it responds the number of rows to the tableView and in the same time tries to already do some updates on the tableView. I really can't find any real clue about what's happening behind.

After more debugging:
This 'attempt to delete row 0 from section 0, but there are..' occurs because for some reasons it inserts that row, then inserts another, then updates that first row and then ends tableView updates.

And the tableView complains that it is illegal to insert and then "delete". In this case I suppose that update for the table view means = delete + insert. Thus the delete from the tableView point of view is illegal as at that point of time there were no objects yet, so you cannot delete it.

Supposal : the ResultsTableViewController should not insert and update the same row in the same begin/end updates session especially if it is about the initial state when there were no objects before that begin update.

@Carpemeid
Copy link
Author

Tried to listen to Realm Notifications and I this is what I've got in the notifications

NSConcreteNotification 0x15df0ee0 {name = CarObject-/cars/renault-megane-coupe}

Unfortunately it does not show what operation was made on object, in order to debug the ResultsController behaviour.

@polqf
Copy link
Owner

polqf commented Feb 5, 2016

Hi @Carpemeid ,
I am going to dive into this issue right now, has happened a few times in our app also.

Thanks for the detailed information.
I'll keep you up to date

@polqf
Copy link
Owner

polqf commented Feb 10, 2016

Hi @Carpemeid,
This issue has (theoretically) been solved, and will be included in the 0.4.1 version.

I want to comment that this issue is not completely related with RRC, is a bad usage of it.

The source of the problem occurs when you try to do different actions to the same object inside a write transaction. Such as:

realm.write {
    let object = // retrieve object with primaryKeyValue = 123
    let anotherObject = ObjectSubclass()
    anotherObject.primaryKeyValue = 123
    realm.deleteNotified(object)
    realm.addNotified(object)
}

What #67 is going to do, is to prevent a crash on the endUpdates, by handling these problem. But this is not going to ensure that the data is as expected. There is a restriction applied, the stablished order of importance is DELETE>UPDATE>ADD. So, if you add and remove the same object, it is going to be treated as a deletion.

Appart from that, we added a Log telling what has happened. If you want to take have more information about what is the duplicate, just set a symbolic breakpoint RealmResultsController.warnDuplicated

Also, you were complaining about because subscribing to notifications was sending the object. Now it is sending a RealmChange object that has all you need.

That said, feel free to reopen the issue if you that happens to you when we release 0.4.1

@Carpemeid
Copy link
Author

But the only thing I was doing in the realm transactions was adding with update (theoretically inside the transaction I have only that operation.. of course somewhere in the back Realm may delete/add/update in order to make the update work as expected).

And I cannot avoid using [add with update] because I cannot know if my db will contain the objects that are going to be "updated" unless I do an additional reading transaction and compare all that stuff manually. But doing an additional reading for that does not seam a good solution.

@polqf
Copy link
Owner

polqf commented Feb 10, 2016

@Carpemeid I understand, don't get me wrong. It is not that I am saying that doing this is wrong. My message was not in that direction, sorry about it.

My point was, the way we defined the usage of the RRC, was this one, and that was what we expected the users to use it. There's another thing here, we did not completely think about this scenario. We talked about this, and agreed that we are going to implement, in the future, a timeline based behaviour. That is, the last thing you do, is the one that is going to prevail.

@Carpemeid
Copy link
Author

Yep, of course. I understood) just wanted to make sure I got your point.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants