Realm - Cannot use object after deletion

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

The problem is this: When the user is in PlayerVC

, they may not greet a Video

. If they do, I remove the object Video

from the Realm. However, this causes:

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

Basically, if the user is watching the video in PlayerVC

, and he doesn't distort the video, I want them to still be able to watch the video for a while.
But when they leave PlayerVC

, the collection view in FavoritesVC

should be refreshed and not show anymore Video

.

When I delete an object Video

, I use the Realm method delete

.

This is my code for storing a list of objects 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"
    }
}

      

This is my class Video

which has the property 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...
}

      

This is my code for displaying objects Video

in the collection view (Note: I am using RealmCollectionChange

to update the collection view to delete and insert 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 through all objects 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)
        }
    }
}

      

Ultimately I want the object to Video

be unusable to be completely removed from the Realm . Just not sure how to do it in this case.

Any thoughts?

Update 1

Possibility to solve this problem:

  • Make an unmanaged copy of the copy Video

    and use it to enable the view controller UI.

As I see it, maybe this works:

  • PlayerVC

    will get two List

    , the original one saved in Realm, and a copy of that List

    to enable the UI. Call lists favoriteList

    and copyList

    .

  • So, internally, 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?

+3


source to share


2 answers


This is definitely tricky for a number of architectural reasons.

You are correct that you can simply remove the object from FavoriteList.videos

and then properly remove it from the Realm when you are about to dismiss the controller, but you are correct if the user presses the home button or you get a headless video object before the application crashes. You will need to make sure you can track this.



There are several things you might want to consider.

  • Add property isDeleted

    to class Video

    . When the user has disabled the video, remove the object Video

    from FavoriteList.videos

    , set this property to a value true

    , but leave it in Realm. Later (either when the application is closed or the view manager is rejected), you can then run a generic query for all objects where it isDeleted

    is true

    and then delete them (this solves the headless problem).
  • Since your architecture requires a view controller based on a model that can be removed from under it, depending on how much information you are using from that object Video

    it may be safer to make an unmanaged copy Video

    copy and use that to enable the view controller UI ... You can create a new copy of an existing Realm object by running let unmanagedVideo = Video(value: video)

    .
+1


source


Here is the solution to the problem. 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)
        }
       }
        }

      

0


source







All Articles