在 ForEach 循环中删除项目会导致致命错误:索引超出范围

Posted

技术标签:

【中文标题】在 ForEach 循环中删除项目会导致致命错误:索引超出范围【英文标题】:Deleting item in ForEach loop causes fatal error: Index out of range 【发布时间】:2020-06-24 23:56:18 【问题描述】:

我有一个 ScrollView,它使用数组中的 ForEach 循环显示行列表。当我从数组中删除一个项目时,我收到错误:索引超出范围。

ScrollView 
    ForEach(viewModel.tasks.indices, id: \.self) index in
        TaskRow(
            task: self.$viewModel.tasks[index],
            deleteAction: 
                self.viewModel.deleteTask(task: self.viewModel.tasks[index])
            
        )
    

此错误仅在我切换到从 ForEach 循环而不是“任务”本身传递索引时才开始发生。我必须这样做才能在子视图中使用@Binding var task: Task:“TaskRow”

“删除操作”由子视图中的按钮触发。

viewModel.deleteTask 的工作方式如下(使用数据管理器):

final class StackDetailViewModel: ObservableObject 
    
    @Published var tasks = [Task]()
    
    var dataManager: DataManagerProtocol
    
    init(dataManager: DataManagerProtocol = DataManager.shared)
        self.dataManager = dataManager
        fetchTasks()
    

extension StackDetailViewModel 

    func fetchTasks() 
        tasks = dataManager.fetchTasks()
    

    func deleteTask(task: Task) 
        dataManager.deleteTask(task: task)
        fetchTasks()
    


dataManager 在哪里执行此操作:


Class DataManager 

...

    private var tasks = [Task]()

...
 
    func fetchTasks() -> [Task] 
        tasks
    

    func deleteTask(task: Task) 
        if let index = tasks.firstIndex(where:  $0.id == task.id ) 
            tasks.remove(at: index)
        
    


我在我的应用程序中使用协议,但为了简单起见,我在此处删除了它们。 任何帮助将不胜感激。

【问题讨论】:

你能展示你对viewModel.deleteTask的实现吗? @NewDev 我已经更新了 Q。谢谢 根据您显示的内容,它应该可以工作。我怀疑这个错误介于DataManager 和您的ViewModel 之间。 一般来说,您不希望在单步执行时从数组中删除 @NewDev 来自我所做的测试。仅当我从使用 ForEach(viewModel.tasks) task in 更改为上面显示的内容时,才会出现该错误。保持 dataManager 和 ViewModel 相同。 【参考方案1】:

直接在ForEach 中使用数组索引作为标识符并不是一个好习惯,因为索引是位置性的,并且不能识别它们所代表的项目。这可能会导致 SwiftUI 中出现重绘问题和崩溃。 例如。当您在常规List 中使用“滑动删除”时,SwiftUI 知道给定位置的项目已被删除,并且可能不会再次询问完整的 ID 列表(在这种不正确的情况下是索引)重绘内容。 SwiftUI 只会为下一次重绘提供剩余 ID 的列表,这会导致越界崩溃。

我并不是说这就是这里的确切情况,因为我无法重现您的崩溃。

在您自己编写时,当您修改 ForEach 以使用索引而不是您的任务时,就会出现问题。

代替

ForEach(viewModel.tasks.indices, id: \.self) index in

使用

ForEach(Array(viewModel.tasks.enumerated()), id: \.1.id)  (index, task) in

这使您可以访问Binding 的索引,以访问TaskRow 所需的任务和关联的任务。此外,通过将任务 ID 指定为每一行的标识符,SwiftUI 将不再依赖位置索引。

但请注意:此解决方案可能不适用于非常大的任务数组,因为EnumeratedSequence(由您的任务.enumerated() 返回的Array)被扁平化为一个新的Array.

因此,如果您要处理大量任务,我会推荐您的初始版本:ForEach(viewModel.tasks),并最终仅在 deleteAction 的情况下支付性能损失。

【讨论】:

以上是关于在 ForEach 循环中删除项目会导致致命错误:索引超出范围的主要内容,如果未能解决你的问题,请参考以下文章

将 GCP 订阅者与 Spring Boot 集成会导致内存致命错误

视图模型数组上的 ForEach 循环问题导致 Xcode 编译错误 - SwiftUI

SwiftUI - 致命错误:从数组中删除元素时索引超出范围

删除循环中的控件会导致奇怪的行为

在foreach循环中使用remove报ConcurrentModificationException异常原因

Swift,CollectionView,致命错误:索引超出范围