Realm - Can't use object after having been deleted

狂风中的少年 提交于 2019-11-30 18:01:06

问题


I have a video player in my app. There is a list of videos in a collection view. If you tap on one of the cells, a new view controller appears to play the selected video. Also, you can cycle through all of the videos from the collection view in this new view controller because the entire list is passed.

The problem is: When the user is in the PlayerVC, they can unfavorite a Video. If they do this, I delete the Video object from Realm. However, this causes a:

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

Basically, if a user is watching a video in PlayerVC and he unfavorites a video, I want them to still be able to watch the video for the time being. But when they leave the PlayerVC, the collection view in FavoritesVC should be updated and not show that Video any more.

When I delete a Video object I use Realm's delete method.

This is my code to hold a list of Video objects:

/// 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"
    }
}

This is my Video class which has an isFavorite property:

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...
}

This is my code to show the Video objects in a collection view (Note: I use RealmCollectionChange to update the collection view for deleting and inserting cells):

/// 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.
    }
}

Finally, this is the code to allow the user to play and cycle between all Video objects:

/// 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)
        }
    }
}

Ultimately I want the Video object that is unfavorited to be completely deleted from Realm. Just not sure how/when to do it in this case.

Any thoughts?

Update 1

An option to solve this is to:

  • Make an unmanaged copy of the Video copy, and use that one to power the UI of the view controller.

The way I see this possibly working is this:

  • The PlayerVC will receive two List, the original one saved in Realm and a copy of this List to power the UI. Let's call the lists favoriteList and copyList.

  • So inside of the didToggleStarButton we would do something like this:

Code:

/// 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.
    }
}

Any thoughts?


回答1:


This one is definitely tricky for a number of architectural reasons.

You're right in that you could simply remove the object from the FavoriteList.videos and then properly delete it from Realm when going to dismiss the controller, but you're right that if the user clicks the home button, or the app crashes before then, you'll end up with a headless video object. You would need to be able to make sure you can track that.

There's a couple of things you might be able to consider.

  • Add an isDeleted property to the Video class. When the user unfavorites the video, remove the Video object from FavoriteList.videos, set that property to true, but leave it in Realm. Later on (either when the app quits or the view controller is dismissed), you can then do a general query for all objects where isDeleted is true and delete them then (This solves the headless problem).
  • Since your architecture requires a view controller relying on a model that could be deleted from under it, depending on how much information you're using from that Video object, it might be safer to make an unmanaged copy of the Video copy, and use that one to power the UI of the view controller. You can create a new copy of an existing Realm object by doing let unmanagedVideo = Video(value: video).



回答2:


Here is the workaround solution. Check if it works.

 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)
        }
       }
        }


来源:https://stackoverflow.com/questions/43081852/realm-cant-use-object-after-having-been-deleted

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!