领域 - 删除后无法使用 object
Realm - Can't use object after having been deleted
我的应用程序中有一个视频播放器。 collection 视图中有一个视频列表。如果您点击其中一个单元格,则会出现一个新的视图控制器来播放所选视频。此外,您可以在这个新视图控制器中循环浏览 collection 视图中的所有视频,因为整个列表都已传递。
问题是:
当用户在 PlayerVC
中时,他们可以取消收藏 Video
。如果他们这样做,我会从 Realm 中删除 Video
object。但是,这会导致:
Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
基本上,如果用户正在 PlayerVC
观看视频并且他取消了对视频的收藏,我希望他们仍然能够在这段时间内观看视频being. 但是当他们离开 PlayerVC
时,FavoritesVC
中的 collection 视图应该更新并且不再显示 Video
.
当我删除一个 Video
object 我使用 Realm 的 delete
方法。
这是我的代码,用于保存 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"
}
}
这是我的 Video
class 有一个 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...
}
这是我在 collection 视图中显示 Video
object 的代码(注意:我使用 RealmCollectionChange
来更新 collection 视图用于删除和插入单元格):
/// 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
object 之间播放和循环的代码:
/// 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
object从Realm中完全删除。只是不确定 how/when 在这种情况下是否应该这样做。
有什么想法吗?
更新 1
解决这个问题的一个方法是:
- 制作
Video
副本的非托管副本,并使用该副本为视图控制器的 UI 供电。
我认为这可能有效的方式是:
PlayerVC
将收到两个 List
,一个保存在 Realm 中的原始副本和一个 List
的副本,用于为 UI 供电。让我们调用列表 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.
}
}
有什么想法吗?
这是解决方法。检查它是否有效。
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)
}
}
}
由于多种体系结构原因,这绝对是棘手的。
你是对的,你 可以 简单地从 FavoriteList.videos
中删除 object 然后在要关闭时从 Realm 中正确删除它控制器,但你是对的,如果用户点击主页按钮,或者应用程序在此之前崩溃,你最终会得到一个无头视频 object。你需要能够确保你可以跟踪它。
您可以考虑几件事。
- 在
Video
class 中添加一个 isDeleted
属性。当用户取消收藏视频时,从 FavoriteList.videos
中删除 Video
object,将 属性 设置为 true
,但将其保留在 Realm 中。稍后(当应用程序退出或视图控制器被关闭时),您可以对所有 objects 进行一般查询,其中 isDeleted
是 true
然后删除它们(这解决了无头问题)。
- 由于您的体系结构需要一个依赖于模型的视图控制器,该模型可以从其下删除,具体取决于您从中使用了多少信息
Video
object,它可能更安全制作 Video
副本的非托管副本,并使用该副本为视图控制器的 UI 供电。您可以通过 let unmanagedVideo = Video(value: video)
. 创建现有领域 object 的新副本
我的应用程序中有一个视频播放器。 collection 视图中有一个视频列表。如果您点击其中一个单元格,则会出现一个新的视图控制器来播放所选视频。此外,您可以在这个新视图控制器中循环浏览 collection 视图中的所有视频,因为整个列表都已传递。
问题是:
当用户在 PlayerVC
中时,他们可以取消收藏 Video
。如果他们这样做,我会从 Realm 中删除 Video
object。但是,这会导致:
Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
基本上,如果用户正在 PlayerVC
观看视频并且他取消了对视频的收藏,我希望他们仍然能够在这段时间内观看视频being. 但是当他们离开 PlayerVC
时,FavoritesVC
中的 collection 视图应该更新并且不再显示 Video
.
当我删除一个 Video
object 我使用 Realm 的 delete
方法。
这是我的代码,用于保存 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"
}
}
这是我的 Video
class 有一个 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...
}
这是我在 collection 视图中显示 Video
object 的代码(注意:我使用 RealmCollectionChange
来更新 collection 视图用于删除和插入单元格):
/// 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
object 之间播放和循环的代码:
/// 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
object从Realm中完全删除。只是不确定 how/when 在这种情况下是否应该这样做。
有什么想法吗?
更新 1
解决这个问题的一个方法是:
- 制作
Video
副本的非托管副本,并使用该副本为视图控制器的 UI 供电。
我认为这可能有效的方式是:
PlayerVC
将收到两个List
,一个保存在 Realm 中的原始副本和一个List
的副本,用于为 UI 供电。让我们调用列表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.
}
}
有什么想法吗?
这是解决方法。检查它是否有效。
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)
}
}
}
由于多种体系结构原因,这绝对是棘手的。
你是对的,你 可以 简单地从 FavoriteList.videos
中删除 object 然后在要关闭时从 Realm 中正确删除它控制器,但你是对的,如果用户点击主页按钮,或者应用程序在此之前崩溃,你最终会得到一个无头视频 object。你需要能够确保你可以跟踪它。
您可以考虑几件事。
- 在
Video
class 中添加一个isDeleted
属性。当用户取消收藏视频时,从FavoriteList.videos
中删除Video
object,将 属性 设置为true
,但将其保留在 Realm 中。稍后(当应用程序退出或视图控制器被关闭时),您可以对所有 objects 进行一般查询,其中isDeleted
是true
然后删除它们(这解决了无头问题)。 - 由于您的体系结构需要一个依赖于模型的视图控制器,该模型可以从其下删除,具体取决于您从中使用了多少信息
Video
object,它可能更安全制作Video
副本的非托管副本,并使用该副本为视图控制器的 UI 供电。您可以通过let unmanagedVideo = Video(value: video)
. 创建现有领域 object 的新副本