Царство - Невозможно использовать объект после его удаления

У меня есть видеоплеер в моем приложении. В представлении коллекции есть список видео. Если вы нажмете на одну из ячеек, появится новый контроллер представления для воспроизведения выбранного видео. Кроме того, вы можете перебирать все видео из представления коллекции в этом новом контроллере представления, поскольку передается весь список.

Проблема в том, что когда пользователь находится в PlayerVC, он может удалить из избранного Video. Если они это сделают, я удалю объект Video из Realm. Однако это вызывает:

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

По сути, если пользователь просматривает видео в PlayerVC и удаляет его из избранного, я хочу, чтобы он какое-то время все еще мог смотреть это видео. Но когда они оставьте 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, который не добавлен в избранное, был полностью удален из Realm. Просто не уверен, как/когда это сделать в этом случае.

Какие-нибудь мысли?

Обновление 1

Вариант решения этой проблемы заключается в следующем:

  • Сделайте неуправляемую копию копии Video и используйте ее для питания пользовательского интерфейса контроллера представления.

То, как я вижу, что это, возможно, работает, таково:

  • PlayerVC получит два List: исходный, сохраненный в Realm, и копию этого List для питания пользовательского интерфейса. Назовем списки favoriteList и copyList.

  • Итак, внутри 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.
    }
}

Какие-нибудь мысли?


person JEL    schedule 28.03.2017    source источник


Ответы (2)


Это определенно сложно по ряду архитектурных причин.

Вы правы в том, что вы могли просто удалить объект из FavoriteList.videos, а затем должным образом удалить его из Realm при закрытии контроллера, но вы правы в том, что если пользователь нажмет кнопку "Домой", или приложение выйдет из строя до этого, вы получите безголовый видеообъект. Вы должны быть в состоянии убедиться, что вы можете отслеживать это.

Есть несколько вещей, которые вы могли бы рассмотреть.

  • Добавьте свойство isDeleted в класс Video. Когда пользователь удаляет видео из избранного, удалите объект Video из FavoriteList.videos, установите для этого свойства значение true, но оставьте его в Realm. Позже (либо когда приложение закрывается, либо когда контроллер представления закрывается), вы можете выполнить общий запрос для всех объектов, где isDeleted равно true, и затем удалить их (это решает проблему без головы).
  • Поскольку ваша архитектура требует, чтобы контроллер представления опирался на модель, которая может быть удалена из-под него, в зависимости от того, сколько информации вы используете из этого объекта Video, может быть безопаснее сделать неуправляемую копию копии Video и использовать ее. один для питания пользовательского интерфейса контроллера представления. Вы можете создать новую копию существующего объекта Realm, выполнив let unmanagedVideo = Video(value: video).
person TiM    schedule 29.03.2017
comment
Большое спасибо за ответ!! Из двух вариантов вариант 1 (добавить свойство isDeleted в класс Video) кажется проще, но менее безопасным (как вы упомянули). Что касается варианта 2, я не уверен, как именно это реализовать с точки зрения использования копии для управления пользовательским интерфейсом (Примечание: я обновил свой вопрос, чтобы показать больше данных, которые есть у Video — может быть, теперь будет легче увидеть если это лучший вариант). В целом, какой вариант из этих двух (или другой вариант) вы бы выбрали? (Я знаю, что может быть сложно выбрать точно, поскольку вы не знаете все приложение, но только для этого варианта использования в вопросе). - person JEL; 30.03.2017
comment
Пожалуйста! Хм, сложно. Вариант 1 требует запуска «сборки мусора» в другом месте вашего приложения, что может быть небольшой проблемой, но не слишком ужасно. Но поскольку в вашем классе Video нет никаких связывающих объектов, его будет очень легко дублировать, используя вариант 2, который может быть немного чище и более автономен. Но если вашему контроллеру представления необходимо выполнить какие-либо операции сохранения объекта (пока он не удален), вам придется побеспокоиться о том, чтобы и дубликат, и исходный объект получили эти изменения. В любом случае, оба варианта являются жизнеспособными. :) - person TiM; 30.03.2017
comment
Хорошо, потребовалось некоторое время, чтобы ответить, потому что мы действительно обдумывали это. Одна вещь (кстати, я приму ваш ответ). Каждый раз, когда Video добавляется в избранное, происходит копирование файлов на диск с FileManager; также, когда Video не является избранным, файлы удаляются, а Video из Realm. PlayerVC не использует эти файлы, а FavoritesVC использует. Мне кажется, что вариант 2 может быть лучшим, но я не уверен, как сделать это добавление/удаление избранного с неуправляемой копией копии Video. Вы сталкивались с каким-либо примером кода или небольшим фрагментом, чтобы проиллюстрировать это? Спасибо еще раз!! - person JEL; 01.04.2017
comment
Пожалуйста, дайте мне знать, что вы думаете о моем предыдущем комментарии ^^? Спасибо! В любом случае, я приму ваш ответ сейчас, несмотря ни на что. Еще раз спасибо за ваше время, цените его! :) (ПРИМЕЧАНИЕ. Мы попробовали и обновили наш вопрос, указав, как, по нашему мнению, выполнить вариант 2 ((Кроме того, когда PlayerVC закрывает, наша цель состояла в том, чтобы пользователь не видел, как видео удаляется из представления коллекции FavoriteVC .)) - person JEL; 03.04.2017
comment
Без проблем! Спасибо! Хм, нет, я не видел кода для такого рода вещей; это довольно уникальный архитектурный образец. Как я уже сказал выше, вариант 2 будет работать только в том случае, если вы не планируете сильно изменять данные. По сути, это «кэширование» копии исходного объекта, поэтому исходный объект не обязательно должен оставаться. - person TiM; 03.04.2017
comment
Хорошо, спасибо! Я рассмотрю оба варианта, вариант 1 может работать лучше, поскольку изменяется много данных. Спасибо и берегите себя! :) - person JEL; 03.04.2017
comment
Быстрое продолжение, единственный способ, которым я получил эту работу с Вариантом 1, - это преобразовать List в Swift array и передать его вперед PlayerVC. Я все еще использую List для FavoritesVC, но я преобразовываю его в array, чтобы перейти к PlayerVC. Я думаю, что это делает копию объектов. Если вместо этого я передам List, у меня начнутся сбои с Index out of range.., поскольку я полагаюсь на индекс списка или массива, чтобы определить, что воспроизводить --- если я удаляю видео из List, тогда возникает проблема. Создание array, похоже, решает эту проблему. Есть мысли по этому поводу? Благодарность! - person JEL; 06.04.2017
comment
Извините за беспокойство, вы видели комментарий выше ^^? Пожалуйста, дайте знать ваши мысли, спасибо! - person JEL; 07.04.2017
comment
Ха-ха, не беспокойтесь, но может быть лучше сделать новый выпуск в таком темпе. XD Э-э, нет, передача объектов Realm в массив Swift не приведет к их копированию; он просто сохранит их в памяти на протяжении всего жизненного цикла массива. Что-то тут не так, но я не думаю, что это можно исправить в комментариях Stack Overflow. ^_^;;;; - person TiM; 07.04.2017
comment
Лол, я знаю, что вы имеете в виду :) Хм, я снова постараюсь не создавать этот array, если нет, то я создам новый ТАК вопрос/вопрос (я, вероятно, свяжу его с вами). Спасибо за продолжение!!!! - person JEL; 07.04.2017

Вот обходное решение. Проверьте, работает ли он.

 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)
        }
       }
        }
person Abhishek Biswas    schedule 29.03.2017
comment
Я обновил свой вопрос, чтобы показать мою модель под названием Video, которая имеет свойство isFavorite. Пожалуйста, взгляните и дайте мне знать, что вы думаете - person JEL; 29.03.2017
comment
Спасибо за обновление! :) Что делать, если пользователь смотрит видео и удаляет видео из избранного, так что var arrayForIndex = [Int]() теперь имеет 1 значение. Если пользователь выйдет из приложения, viewWillDisappear не будет вызываться. Тогда нам придется беспокоиться о сохранении arrayForIndex в случае сбоя приложения или выполнять realm.delete(videoToDelete) в других местах, таких как список, когда приложение переходит в фоновый режим и т. д. Не уверен, что это правильный способ сделать - person JEL; 29.03.2017
comment
Я думаю, что viewWillDIsappear всегда будет вызываться. Я проверил это сейчас. - person Abhishek Biswas; 29.03.2017
comment
Из моих тестов, если вы нажмете кнопку «Домой» на iPhone, то viewWillDisappear не будет звонить. Вместо этого вызываются методы App Delegate, такие как applicationWillEnterForeground applicationDidEnterBackground. Поскольку это так, я не уверен, что это решение подходит для обработки каждого случая - я не знаю, если честно - person JEL; 29.03.2017
comment
Я проверил множество примеров и кода, и я понял, что вызывается viewWillDisappear. Проверьте, создав новый проект и вызвав другой viewController из первого, как вы это сделали (используя ссылку на раскадровку). И вызовите функцию viewWillDISappear во втором. Я уверен, что его вызовут. - person Abhishek Biswas; 29.03.2017
comment
Я как бы понимаю, что вы имеете в виду, и попробовал это. Одна проблема заключается в том, что когда вы вернетесь к представлению коллекции в FavoritesVC, вы увидите, что ячейка исчезла. Вероятно, это связано с тем, что при удалении внутри viewWillDisappear из PlayerVC у него недостаточно времени для удаления, чтобы пользователь не видел, как это происходит. - person JEL; 29.03.2017