SwiftUI 错误 - 列表更改锁定 UI -(旧标题:SwiftUI CoreData 获取非常慢)

2021-12-23 00:00:00 sqlite core-data xcode swiftui

更新 #4

  • 我对这篇文章进行了重新排序,以便更轻松地阅读.您将在下面阅读将详细介绍我在使用 SwiftUI 时遇到的错误.我最近请求 Apple 提供代码级别的支持,他们确认了这一点,并要求我联系解决问题的反馈(也已完成,但尚未得到答复).

错误是这样的:在 SwiftUI 视图中显示 List 或 ForEach 后,如果您通过更改列出的项目数量来更改该视图,则 UI 在尝试计算数量时会锁定已更改/需要更改的行数..

The bug is this: After displaying a List or ForEach in a SwiftUI View, if you alter that view by changing the number of items listed, the UI locks up while it attempts to calculate the number of rows that have changed / need to change..

我在 Apple 开发论坛中看到其他人遇到过此错误.他们的临时解决方案是将数组设置为空白",从而在修改列出的数据集之前彻底清除列表大约 100 毫秒.这将充分避免用户使用数据数组迭代 List 或 ForEach 时的锁定.

I have seen others who have experienced this bug in the Apple dev forums. Their temporary solution was to "set the array to blank" thereby clearing the list completely for about 100 milliseconds before modifying the listed dataset. This will avoid the lock up sufficiently for users iterating a List or ForEach using an Array of data.

问题是,使用 CoreData,如本文所述,似乎没有任何方法可以清除推送的字母之间的列表(获取请求).

Problem is, with CoreData, being used as is described in this post, there does not seem any way to clear the list in between letters being pushed (fetch requests).

在更新 #3 中,有一个 GitHub 项目显示了此问题的示例数据.

In Update #3, there is a GitHub project which shows a sample of this issue with sample data.

对解决方法的任何输入表示赞赏.

Any input on workarounds is appreciated.

更新 #3

不好...如这篇文章中所述,我能够改变从使用 CoreData 到本地 SQLite 数据库文件.我的结果是搜索和使用 CoreData 一样慢.我不知道这里发生了什么.但也许它与 SwiftUI 输出的渲染结果有关?无论哪种方式,搜索和显示大量数据似乎都是不可能的..

Not good.. As described in this post, I was able to change from using CoreData to a local SQLite database file. My results were that the search was just as slow as using CoreData. I do not know what is going on here. But maybe it is something with rendering results to the SwiftUI output? Either way, searching and displaying a large amount of data seems impossible..

我已根据 J. Doe 的要求在 GitHub 上发布了一个示例项目,该项目演示了此问题.这个项目可以在这里

Ive posted a sample project which demonstrates this problem on GitHub, per J. Doe's request. This project can be found here

我希望有人能看到我做错了什么.我很难相信这只是 iOS 的一个限制..

I hope someone can see what I'm doing wrong. I find it hard to believe that this is just a limitation of iOS..

原帖

有什么想法吗?

我觉得我缺少一些基本的东西.我的获取请求(下面的代码)非常慢.我试图向 CoreData 模型添加一个索引,但有负面改进(来自下面的 J. Doe 的建议).我想也许我需要以某种方式向 fetch 请求添加一个 fetchBatchSize 声明(想通了这个 - 请参阅下面的更新 #2 - 没有帮助),但是使用 SwiftUI 中的属性包装器 @FetchRequest,似乎没有办法做这个.

I feel like I am missing something fundamental. My fetch request (code below) is super slow. I have tried to add an index to the CoreData model with negative improvement (suggestion from J. Doe below). I am thinking maybe I need to somehow add a fetchBatchSize declaration to the fetch request (figured this out - see update #2 below - no help), but with the property wrapper @FetchRequest in SwiftUI, there does not seem to be a way to do this.

下面的代码正在处理一个包含大约 5,000 条记录的测试数据集.在搜索中,每次输入更改(输入每个字母)时,搜索将再次运行,这将拖累系统停止(CPU 100+% 和不断增长的内存使用).

The code below is working on a test dataset of about 5,000 records. In the search, each time the input is changed (with each letter typed), the search gets run again which drags the system to a halt (100+% on CPU and growing memory usage).

在以前的应用程序中,我完成了类似的任务,但这些应用程序使用 SQLite 数据文件并用 ObjC 编写.在这些情况下,事情真的很快,超过了这个测试数据集的 3 倍.

In previous apps, I have completed similar tasks, but those apps used an SQLite data file and were written in ObjC. In those instances, things were really fast, with more than 3 times this test dataset.

如果有人能指出我正确的方向来加速我的 CoreData 获取,我将不胜感激.如果我不需要,我不想回到 SQLite 文件..

If anyone can point me in the right direction to speed up my CoreData fetch, I would be very appreciative. I do not want to have to go back to an SQLite file if I don't have to..

非常感谢!

使用 SwiftUI,这是我的代码:

Using SwiftUI, here is my code:

struct SearchView: View {


    @Binding var searchTerm:String
    var titleBar:String

    var fetch: FetchRequest<MyData>
    var records: FetchedResults<MyData>{fetch.wrappedValue}

    init(searchTerm:Binding<String>, titleBar:String) {
        self._searchTerm = searchTerm
        self.titleBar = titleBar
        self.fetch = FetchRequest(entity: MyData.entity(), sortDescriptors: [NSSortDescriptor(keyPath:  MyData.header, ascending: true)], predicate: NSCompoundPredicate(type: .and, subpredicates: [ NSCompoundPredicate(type: .or, subpredicates: [NSPredicate(format: "%K CONTAINS[cd] %@", #keyPath(MyData.title),searchTerm.wrappedValue), NSPredicate(format: "%K CONTAINS[cd] %@", #keyPath(MyData.details),searchTerm.wrappedValue)]), NSPredicate(format: "%K == %@", #keyPath(MyData.titleBar),titleBar)])) //fetch request contains logic for module and search data - need to fix sort order later
    }

    var body: some View {


        List{

            Section(header: SearchBar(text: $searchTerm)) {

                ForEach(records, id: .self) { fetchedData in

                    VStack {
                        NavigationLink(destination: DetailView(titleBar: fetchedData.title!, statuteData: fetchedData.details!, isFavorite: fetchedData.isFavorite)) {

                            HStack {
                                Text(fetchedData.header!)
                                    .font(.subheadline)

                                VStack(alignment: .leading) {
                                    Text(fetchedData.title!)
                                }
                                .scaledToFit()

                                Spacer()

                                if fetchedData.isFavorite {
                                    Image(systemName: "star.fill")
                                        .imageScale(.small)
                                        .foregroundColor(.yellow)
                                }
                            }
                        }
                    }
                }
            }.navigationBarTitle(Text("Search"))
        }
    }
}

感谢您的帮助.

更新:

在编辑之前,我报告了存储数据的另一个问题,但是,该问题已通过以下帖子解决:

Before edit, I had reported another issue with storing data, however, that issue was resolved with the post below:

CoreData 写入对象慢

更新 #2:

我最初的问题是问如何在我的提取中添加批量限制以查看是否有帮助.我能够在不使用 FetchRequest 包装器的情况下重写获取,使用 NSFetchRequest 并添加了批处理限制.它对情况没有任何帮助..

My original question asked how to add the batch limit on my fetch to see if that helps. I was able to rewrite the fetch without using the FetchRequest wrapper, using NSFetchRequest and added the batch limit. It did nothing to help the situation..

再次感谢

推荐答案

这是解决方案,我可以补充一下,这是完全不可接受的.它使应用在技术上可以正常工作,但速度太慢,感觉就像在 8086 上加载 Windows 10.荒谬.

Here's the work around, which, may I add is completely unacceptable. It makes the app technically work, but is so slow, it feels like loading Windows 10 on an 8086. ridiculous.

此外,Apple 反馈仍然没有答复,甚至没有得到承认.即使他们表示无法帮助我,我的代码级支持请求也被扣款.不开心..

Also, still no answer or even acknowledgement from Apple Feedback. And my code level support request was debited even though they stated they could not help me. Not happy..

无论如何,如果您想构建一个感觉像是在挖泥巴的应用程序,使用 CoreData 并能够搜索该数据,这里是您的解决方法..

Anyway, if you want to build an app that feels like its digging through mud, using CoreData and being able to search that data, here's your workaround..

第一:创建数据的可散列模型,或者至少是您需要搜索和/或显示的数据部分:

1st: Create a hashable model of your data, or at least the part of the data that you will need to search and/or display:

struct MyDataModel: Hashable {
  let title: String
  let name: String
  let myData: String
}

第二:创建一个 ObservableObject 类,该类发布一个变量,该变量包含您刚刚创建的数据模型类的数组:

2nd: Create an ObservableObject class that publishes a variable which contains an array of your data's model class you just created:

class MyData:ObservableObject {
  @Published var searchDataArray = [MyDataModel]()
}

第三:确保将环境变量推送到您计划使用的视图:(这个例子在我的 SceneDelegate.swift 文件中

3rd: make sure you push your environment variable to the views you plan to use this is: (This example is in my SceneDelegate.swift file

let myData = MyData()

并将 .environmentObject(myData) 附加到您需要的任何视图中.

and append .environmentObject(myData) to whatever view you need it in.

第四:从您的视图访问环境变量:@EnvironmentObject var myData: MyData 并将您的提取结果加载到已发布的数据数组中,我使用此函数来完成任务:

4th: Access the Env Var from your view: @EnvironmentObject var myData: MyData and load your fetch results to the published data array, i used this function to complete the task:

func arrayFiller(){ 

    if self.myData.searchDataArray.count > 0 {
        self.myData.searchDataArray.removeAll()
    }

    for item in self.fetchRequest {
        self.myData.searchDataArray.append(MyDataModel(title: item.title!, name: item.name!, myData: item:myData!))
    }
}

最后,从您要搜索的视图中,您可以迭代您发布的环境变量,并且您可以在更改搜索条件之间延迟清除数组以避免错误.

Finally, from the view you want to search, you can iterate your published env var and you can clear the array in between changes to the search criteria with a delay to avoid the bug.

ForEach(self.myData.searchDataArray, id: .self) { fetchedItem in
    Text(fetchedItem.name)
}

然后,我使用 .onReceive 来观察我的 searchTerm 变量的变化,擦除已发布的数组,等待 10 毫秒并用与我的搜索词匹配的数据重新填充数组.

Then, I use an .onReceive to watch my searchTerm variable for changes, wipe the published Array, wait 10 milliseconds and refill the array with the data that matches my search terms.

它真的很慢而且很可怕.它有效,但我认为我无法在这种混乱中接近生产.

Its really slow and hideous. It works, but I don't think I could go anywhere near production with this mess.

相关文章