Realm – No se puede usar el object después de haber sido eliminado.

Tengo un reproductor de video en mi aplicación. Hay una list de videos en una vista de colección. Si toca una de las celdas, aparecerá un nuevo controller de visualización para reproducir el video seleccionado. Además, puede recorrer todos los videos desde la vista de colección en este nuevo controller de vista porque se pasa toda la list.

El problema es: cuando el usuario se encuentra en PlayerVC , pueden desfavorecer un Video . Si lo hacen, elimino el object de Video de Realm. Sin embargo, esto provoca a:

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

Básicamente, si un usuario está viendo un video en PlayerVC y él no favorece un video, quiero que todavía puedan ver el video por el momento. Pero cuando dejan PlayerVC , la vista de colección en FavoritesVC debe actualizarse y no mostrar ese Video más.

Cuando elimino un object de Video , uso el método de delete del reino.

Este es mi código para mantener una list de objects de 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" } } 

Esta es mi class de Video que tiene una propiedad 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 stonetworking on disk for `Video` object). /* These are file names (eg, myFile.mp4, myFile.jpg) */ dynamic var previewLocalFileName: String? dynamic var stitchedImageLocalFileName: String? dynamic var placeholderLocalFileName: String? /* These are partial paths (eg, 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... } 

Este es mi código para mostrar los objects de Video en una vista de colección (Nota: uso RealmCollectionChange para actualizar la vista de colección para eliminar e insert celdas):

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

Finalmente, este es el código para permitir al usuario reproducir y hacer un ciclo entre todos los objects de 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) } } } 

En última instancia, quiero que el object de Video que está desfavorecido se elimine por completo de Realm . Simplemente no estoy seguro de cómo / cuándo hacerlo en este caso.

¿Alguna idea?

Actualización 1

Una opción para resolver esto es:

  • Realice una copy no administrada de la Copia de Video y use esa para alimentar la interfaz de usuario del controller de vista.

La forma en que veo esto posiblemente funcionando es la siguiente:

  • El PlayerVC recibirá dos List , la original guardada en el Reino y una copy de esta List para alimentar la interfaz de usuario. Llamemos a las lists favoriteList y copyList .

  • Entonces dentro del didToggleStarButton haríamos algo como esto:

Código:

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

¿Alguna idea?

Este es definitivamente complicado por varias razones arquitectónicas.

Tienes razón en que simplemente puedes eliminar el object de FavoriteList.videos y luego eliminarlo correctamente de Realm cuando salgas del controller, pero tienes razón si el usuario hace clic en el button de inicio o la aplicación falla antes luego, terminarás con un object de video sin cabeza. Debería ser capaz de asegurarse de poder rastrear eso.

Hay un par de cosas que podrías considerar.

  • Agregue una propiedad isDeleted a la class de Video . Cuando el usuario desfavorece el video, elimine el object Video de FavoriteList.videos , establezca esta propiedad en true , pero déjela en Realm. Más tarde (ya sea cuando la aplicación se cierra o se descarta el controller de vista), puede hacer una consulta general para todos los objects donde isDeleted es true y luego eliminarlos (Esto resuelve el problema sin cabeza).
  • Dado que su architecture requiere un controller de vista que confíe en un model que se pueda eliminar de debajo, dependiendo de la cantidad de información que esté utilizando desde ese object de Video , podría ser más seguro hacer una copy no administrada de la copy de Video y usarla uno para alimentar la interfaz de usuario del controller de vista. Puede crear una nueva copy de un object Realm existente haciendo let unmanagedVideo = Video(value: video) .

Aquí está la solución alternativa. Comtesting si funciona.

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