SwiftUI + Realm: удаление строки из списка приводит к исключению «RLMException», причина: «Объект был удален или признан недействительным». - PullRequest
0 голосов
/ 14 октября 2019

Я пытаюсь перенести небольшое приложение для чтения RSS-каналов из UIKit в SwiftUI, это приложение использует Realm для сохранения.

Чтобы сделать Realm привязываемым в SwiftUI, я добавил следующий код в свойproject:

import Foundation import SwiftUI import RealmSwift

final class FeedData: ObservableObject {

@Published var feeds: [Feed] {
    didSet {
        cleanRealm()
    }
}
private var feedsToken: NotificationToken?

private func activateFeedsToken() {
    let realm = try! Realm()
    let feeds = realm.objects(Feed.self)
    feedsToken = feeds.observe { _ in
        self.feeds = Array(feeds)
    }
}

func cleanRealm() {
    let realm = try! Realm()
    let tempFeeds = realm.objects(Feed.self)
    let diff = feeds.difference(from: tempFeeds)
    for change in diff {
        switch change {
        case .remove(_, let element, _):
            do {
                try realm.write {
                    print("Removing \(element.name)")
                    if element.isInvalidated {
                        print("Error: element invalidated.")
                    } else {
                        realm.delete(element)
                        print("Removed \(element.name)")
                    }
                }
            } catch {
                fatalError(error.localizedDescription)
            }
        default:
            break
        }
    }
}

init() {
    let realm = try! Realm()
    feeds = Array(realm.objects(Feed.self))
    activateFeedsToken()
}

deinit {
    feedsToken?.invalidate()
}

}

Итак, у нас есть массив каналов сОбозреватель DidSet, который при запуске вызывает функцию cleanRealm (), которая затем использует различие коллекций для удаления объектов Feed, которых больше нет в массиве, но которые все еще хранятся в Realm - я знаю, что это супер неуклюже, но я подумал, чтопо крайней мере синхронизируйте массив с базой данных Realm.

В моем представлении SwiftUI я затем использую FeedData в качестве EnvironmentObject и использую список, отображающий все объекты Feed с использованием ForEach.

Когда пользователь удаляетканал из списка, затем он удаляется из массива следующим образом:

            .onDelete(perform: deleteItems)

, который затем вызывает эту функцию:

func deleteItems(at offsets: IndexSet) {
    print("Removing feeds at requested offsets.")
    feedData.feeds.remove(atOffsets: offsets)
    print("Removed feeds at requested offsets.")
}

Problem: когда я запускаю свое приложение и затем удаляю запись из списка, выдается следующее исключение:

* Завершение работы приложения из-за необработанного исключения «RLMException», причина: «Объект был удален илианнулированной. * Первый бросок стека вызовов: (0x1a4a97ab0 0x1a47b1028 0x10098f6a8 0x100996324 0x1009962e8 0x10000dfa4 0x1b215d830 0x1b215d3f0 0x1b215d170 0x1b215f470 0x1db363c4c 0x1db36a0c8 0x1db36a240 0x1db36a6c0 0x1b22d647c 0x1db3ca2c4 0x1db3c9f04 0x1b21a924c 0x1b21a943c 0x1b21a9b50 0x1b22d647c 0x1daefcd50 0x1db3f0ac4 0x1db3eb7b8 0x1db3ead20 0x1db14dca4 0x1db14c5c0 0x1db4d5d0c 0x1db1bdc1c 0x1db1b836c 0x1db1bef70 0x1cf72c9c0 0x1cf713e9c 0x1cf714164 0x1cf719130 0x1db07f9f0 0x1db084e10 0x1db3ac770 0x1db07f96c 0x1cf7192840x1db081ca0 0x1db081a48 0x1db0816c8 0x1db081834 0x1db3ac770 0x1db0817fc 0x1db09f848 0x1daf00a10 0x1daf00970 0x1daf00a8c 0x1a4a12668 0x1a4a0d308 0x1a4a0d8b8 0x1a4a0d084 0x1aec56534 0x1a8b7b8b8 0x10004e520 0x1a488ce18) Libc ++ abi.dylib: оканчивающийся неперехваченным исключением типа NSException

1027 * Мои исследования до сих пор показывает, что, когда объект Realm являетсяудалено, оно видоизменяется до полного удаления.

Итак, я думаю, что здесь может произойти, когда объектМутировавший перед удалением из Realm, SwiftUI обнаруживает это изменение, перерисовывает представление и затем пытается получить доступ к теперь недействительному объекту Realm, что приводит к возникновению исключения.

У объектов Realm есть свойство isInvalidated, которое я, вероятно, долженпроверьте перед добавлением его в список, но AFAIK (и, пожалуйста, не стесняйтесь исправлять меня в этом), в блоке ForEach нет способа проверить такое условие и, если необходимо, «перейти» к следующему элементу массива.

Любая помощь с этим очень ценится, я весь день баловался с этой проблемой и просто не могу найти хорошее решение, это очевидно, но я также все еще изучаю все замечательные вещи, которые SwiftUI может сделать во время работыв этом примере проекта.

Спасибо!

Обновление:

Следуя совету Джея, мне удалось изменить свой класс FeedData, чтобы он выставлял свойство Realm Results для моего приложения наработать, а не копировать в отдельный массив.

Это также означает,Мне больше не нужно выполнять разбор коллекций при удалении каналов, но так как модификатор SwiftUI .onDelete (execute :) ожидает функцию, которая принимает IndexSet, моя функция удаления все еще немного взломана.

ОбновленныйКласс FeedData теперь выглядит следующим образом:

import Foundation import SwiftUI import RealmSwift

final class FeedData: ObservableObject {

@Published var feeds: Results<Feed>
private var feedsToken: NotificationToken?

private func activateFeedsToken() {
    let realm = try! Realm()
    let feeds = realm.objects(Feed.self)
    feedsToken = feeds.observe { _ in
        self.feeds = feeds
    }
}

func deleteItems(at offsets: IndexSet) {
    print("deleteItems called.")
    let realm = try! Realm()

    do {
        try realm.write {
            offsets.forEach { index in
                print("Attempting to access index \(index).")
                if index < feeds.count {
                    print("Index is valid.")
                    let item = feeds[index]
                    print("Removing \(item.name)")
                    realm.delete(item)
                    print("Removed item.")
                }
            }
        }
    } catch {
        fatalError(error.localizedDescription)
    }
}
init() {
    let realm = try! Realm()
    feeds = realm.objects(Feed.self)
    activateFeedsToken()
}

deinit {
    feedsToken?.invalidate()
}

}

Хотя это техническиработает, теперь моя проблема в том, что метод ForEach () моего представления будет вызываться, как только объект будет удален из области (проверено с использованием точек останова), это происходит даже до отправки уведомления области.

Это затем приводит к попытке доступа к индексу удаленного объекта, и в этом случае приложение вылетает с ошибкой индекса вне границ из Realm.

Хотя, вероятно, это должен быть отдельный вопрос,как разрешена моя первоначальная проблема.

@ Джей, не могли бы вы пометить свой первый комментарий как ответ на мой вопрос, чтобы я мог его одобрить?

Спасибо за большую помощь!

...