【问题标题】:Realm - Can't use object after having been deleted领域 - 删除后无法使用对象
【发布时间】:2025-11-24 11:05:02
【问题描述】:

我的应用中有一个视频播放器。收藏视图中有一个视频列表。如果您点击其中一个单元格,则会出现一个新的视图控制器来播放选定的视频。此外,您可以循环浏览此新视图控制器中集合视图中的所有视频,因为整个列表都已传递。

问题是: 当用户在PlayerVC 中时,他们可以取消收藏Video。如果他们这样做,我会从 Realm 中删除 Video 对象。但是,这会导致:

Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'

基本上,如果用户正在观看 PlayerVC 的视频并且他取消收藏某个视频,我希望他们暂时仍能观看该视频。 strong> 但是当他们离开PlayerVC 时,FavoritesVC 中的集合视图应该更新,不再显示Video

当我删除 Video 对象时,我使用 Realm 的 delete 方法。

这是我保存Video 对象列表的代码:

/// Model class that manages the ordering of `Video` objects.
final class FavoriteList: Object {
    // MARK: - Properties

    /// `objectId` is set to a static value so that only
    /// one `FavoriteList` object could be saved into Realm.
    dynamic var objectId = 0

    let videos = List<Video>()

    // MARK: - Realm Meta Information

    override class func primaryKey() -> String? {
        return "objectId"
    }
}

这是我的 Video 类,它有一个 isFavorite 属性:

final class Video: Object {
    // MARK: - Properties

    dynamic var title = ""
    dynamic var descriptionText = ""
    dynamic var date = ""
    dynamic var videoId = ""
    dynamic var category = ""
    dynamic var duration = 0
    dynamic var fullURL = ""
    dynamic var creatorSite = ""
    dynamic var creatorName = ""
    dynamic var creatorURL = ""

    // MARK: FileManager Properties (Files are stored on disk for `Video` object).

    /*
        These are file names (e.g., myFile.mp4, myFile.jpg)
    */
    dynamic var previewLocalFileName: String?
    dynamic var stitchedImageLocalFileName: String?
    dynamic var placeholderLocalFileName: String?

    /*
        These are partial paths (e.g., bundleID/Feed/myFile.mp4,     bundleID/Favorites/myFile.mp4)
        They are used to build the full path/URL at runtime.
    */
    dynamic var previewLocalFilePath: String?
    dynamic var stitchedImageLocalFilePath: String?
    dynamic var placeholderLocalFilePath: String?

    // Other code...
}

这是我在集合视图中显示Video 对象的代码(注意:我使用RealmCollectionChange 更新集合视图以删除和插入单元格):

/// This view controller has a `collectioView` to show the favorites.
class FavoriteCollectionViewController: UIViewController {
    // MARK: Properties

    let favoriteList: FavoriteList = {
        let realm = try! Realm()
        return realm.objects(FavoriteList.self).first!
    }()

    // Realm notification token to update collection view.
    var notificationToken: NotificationToken?

    // MARK: Collection View

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return favoriteList.videos.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FavoritesCollectionViewCell.reuseIdentifier, for: indexPath) as! FavoritesCollectionViewCell
        cell.video = favoriteList.videos[indexPath.item]

        return cell
    }

    // I pass this lst forward to the PlayerVC
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if let playerVC = self.storyboard?.instantiateViewController(withIdentifier: "PlayerViewController") as? PlayerViewController {
            // I pass the videos here.
            playerVC.videos = favoriteList.videos
            self.parent?.present(playerVC, animated: true, completion: nil)
        }
    }

    // MARK: Realm Notifications


    func updateUI(with changes: RealmCollectionChange<List<Video>>) {
        // This is code to update the collection view.
    }
}

最后,这是允许用户在所有Video对象之间播放和循环的代码:

/// This view controller uses `AVFoundation` to play the videos from `FavoriteCollectionViewController`.
class PlayerViewControllerr: UIViewController {

    /// This `videos` is passed from `FavoriteCollectionViewController`
    var videos = List<Video>()

    // HELP: The app crashes here if I unfavorite a `Video`.
    @IBAction func didToggleStarButton(_ sender: UIButton) {
        let realm = try! Realm()
        try! realm.write {
            let videoToDelete = videos[currentIndexInVideosList] /// Get the video that is currently playing
            realm.delete(videoToDelete)
        }
    }
}

最终我希望将不受欢迎的Video 对象从领域中完全删除。只是不确定在这种情况下如何/何时进行。

有什么想法吗?

更新 1

解决此问题的一种方法是:

  • 制作Video 副本的非托管副本,并使用该副本为视图控制器的 UI 供电。

我认为这可能有效的方式是:

  • PlayerVC 将收到两个List,一个保存在 Realm 中的原始地址和一个此 List 的副本以支持 UI。我们将列表称为favoriteListcopyList

  • 所以在 didToggleStarButton 内部我们会做这样的事情:

代码:

/// This view controller uses `AVFoundation` to play the videos from `FavoriteCollectionViewController`.
class PlayerViewControllerr: UIViewController {

    /// A button to allow the user to favorite and unfavorite a `Video`
    @IBOutlet weak var starButton: UIButton!

    /// This is passed from `FavoriteCollectionViewController`
    var favoriteList: FavoriteList!

    /// A copy of the `FavoriteList` videos to power the UI.
    var copiedList: List<Video>!

    var currentIndexOfVideoInCopiedList: Int!

    override func viewDidLoad() {
        super viewDidLoad()

        // Make a copy of the favoriteList to power the UI.
        var copiedVideos = [Video]()

        for video in favoriteList.videos {
            let unmanagedVideo = Video(value: video)
            copiedVideos.append(unmanagedVideo)
        }

        self.copiedList.append(copiedVideos)
    }

    // HELP: The app crashes here if I unfavorite a `Video`.
    @IBAction func didToggleStarButton(_ sender: UIButton) {
        // Do the unfavoriting and favoriting here.


        // An example of unfavoriting:
        let realm = try! Realm()
        try! realm.write {
            let videoToDeleteFromFavoriteList = favoriteList.videos[currentIndexOfVideoInCopiedList] /// Get the video that is currently playing
            realm.delete(videoToDeleteFromOriginalList)
        }

        // Update star button to a new image depending on if the `Video` is favorited or not.
        starButton.isSelected = //... update based on if the `Video` in the `FavoriteList` or not.
    }
}

有什么想法吗?

【问题讨论】:

    标签: ios objective-c swift realm realm-list


    【解决方案1】:

    由于许多架构原因,这绝对是棘手的。

    您是对的,您可以简单地从 FavoriteList.videos 中删除对象,然后在关闭控制器时从 Realm 中正确删除它,但是如果用户单击主页按钮,或者应用程序在此之前崩溃,您最终会得到一个无头视频对象。您需要能够确保您可以跟踪它。

    您可以考虑几件事。

    • isDeleted 属性添加到Video 类。当用户取消收藏视频时,从FavoriteList.videos 中删除Video 对象,将该属性设置为true,但将其保留在Realm 中。稍后(当应用退出或视图控制器关闭时),您可以对 isDeletedtrue 的所有对象进行一般查询,然后将其删除(这解决了无头问题)。
    • 由于您的架构需要一个依赖于可以从其下删除的模型的视图控制器,这取决于您从 Video 对象中使用了多少信息,因此制作 @ 的非托管副本可能更安全987654330@ 复制,并使用它来为视图控制器的 UI 供电。您可以通过 let unmanagedVideo = Video(value: video) 创建现有 Realm 对象的新副本。

    【讨论】:

    • 非常感谢您的回答!!从 2 个选项中,选项 1(将 isDeleted 属性添加到 Video 类)似乎更容易但不太安全(就像你提到的那样)。对于选项 2,我不确定如何在使用副本管理 UI 方面实现这一点(注意:我更新了我的问题以显示 Video 拥有的更多数据 - 也许现在会更容易看看这是否是最佳选择)。总体而言,您会选择这 2 个(或其他选项)中的哪个选项? (我知道可能很难准确选择,因为您不了解整个应用程序,但仅针对问题中的这个用例)。
    • 不客气!嗯,这很棘手。选项 1 需要在您的应用程序的其他地方运行“垃圾收集”,这可能有点麻烦,但也不算太糟糕。但是由于您的 Video 类没有任何链接对象,因此使用选项 2 进行复制将非常容易,这可能会更干净且更独立。但是,如果您的视图控制器确实需要对对象执行任何保存操作(虽然它没有被删除),那么您就必须担心确保副本和原始对象都获得这些更改。无论哪种方式,两者都是可行的选择。 :)
    • 好的,需要一段时间来回复,因为我们真的在考虑这个问题。一件事(顺便说一句,我会接受你的回答)。每次收藏Video 时,都会在磁盘上复制带有FileManager 的文件;同样,当Video 不受欢迎时,文件将被删除,Video 来自 Realm。 PlayerVC 不使用这些文件,但 FavoritesVC 使用。在我看来,选项 2 可能是最好的,但不确定如何使用“Video 副本的非托管副本”来支持/取消收藏。您是否遇到过任何示例代码或小 sn-p 来说明这一点?再次感谢!!
    • 请告诉我您对我之前评论的看法^^ ?谢谢!无论如何,我现在无论如何都会接受你的回答。再次感谢您的宝贵时间,不胜感激! :)(注意:我们试了一下,并用我们认为如何执行选项 2 更新了我们的问题((另外,当 PlayerVC 关闭时,我们的目标是用户看不到从 FavoriteVC 集合视图中删除的视频.))
    • 别担心!谢谢!嗯,不,我还没有看到这种事情的任何代码;这是一个非常独特的架构模式。就像我上面说的,选项 2 仅在您不打算大量修改数据时才有效。它本质上是“缓存”原始对象的副本,因此原始对象不必保留。
    【解决方案2】:

    这是解决方法。检查它是否有效。

     class PlayerViewControllerr: UIViewController {
    
              var arrayForIndex = [Int]()
              var videos = List<Video>()
    
    
              @IBAction func didToggleStarButton(_ sender: UIButton) {
                   self.arrayForIndex.append(currentIndexInVideosList)     
               }
    
             @overide func viewWillDisappear(_ animated : Bool){
                  super.viewWillDisappear(animated)
                  for i in arrayForIndex{
                     let realm = try! Realm()
            try! realm.write {
                let videoToDelete = videos[i] /// Get the video that is currently playing
                realm.delete(videoToDelete)
            }
           }
            }
    

    【讨论】:

    • 我更新了我的问题以显示我的模型 Video 具有 isFavorite 属性。请看看,让我知道你的想法
    • 感谢更新! :) 如果用户正在观看视频并且他们不喜欢该视频以使var arrayForIndex = [Int]() 现在有 1 个值怎么办。如果用户退出应用程序,则不会调用 viewWillDisappear。然后我们将不得不担心在应用程序失败的情况下坚持arrayForIndex,或者在其他地方执行realm.delete(videoToDelete),例如列出应用程序何时进入后台等。不确定这是否是正确的做法
    • 我认为 viewWillDIsappear 总是会被调用。我现在检查了。
    • 根据我的测试,如果你按下 iPhone 上的 Home 键,viewWillDisappear 将不会被调用。而是调用 App Delegate 方法,例如 applicationWillEnterForeground applicationDidEnterBackground。既然是这种情况,我不确定这个解决方案是否适合处理每种情况 - 老实说我不知道​​
    • 我检查了许多示例和代码,我发现调用了 viewWillDisappear。通过创建一个新项目并按照您的方式(通过使用情节提要参考)调用与第一个不同的 viewController 进行检查。并在 secondOne 中调用 viewWillDIsappear 函数。我相信它会被调用。
    最近更新 更多