取消对大数据数组的过滤和排序
cancel filter and sorting of big data array
我正在使用领域构建词汇应用程序。我有几个 objects 的词汇表,其中包含单词列表。一个词汇包含45000个单词
UI是这样构建的,如果选择相应的选项卡,用户可以通过"BEGINSWITH"、"CONTAINS"或"ENDSWITH"搜索词的标题。
因为,有几个词汇表,有一些单词,出现在几个词汇表中,我需要从UI中删除"duplicates"。
当我在结果 objects 上执行此过滤重复项 + 按字母顺序对它们进行排序时,应用程序的 UI 冻结,直到过程完成。
我的问题是:
1) 如果选项卡发生变化(例如从“包含”到“结束”),我如何取消以前的过滤器和领域过滤请求?
2) 我怎样才能在后台执行所有这些 filter/sorting 请求,所以 UI 不会冻结?
我的代码:
let vocabularyPredicate = NSPredicate(format: "enabled == 1 AND lang_from CONTAINS[c] %@", self.language.value)
self.vocabularies = Array(realm.objects(Vocabulary.self).filter(vocabularyPredicate).sorted(byKeyPath: "display_order"))
let result = List<Word>()
for object in self.vocabularies {
let predicate = NSPredicate(format: "title \(selectedFilter.value)[c] %@", self.query.value.lowercased())
result.append(objectsIn: object.words.filter(predicate))
}
self.words = Array(result).unique{[=10=].title}.sorted {
(s1, s2) -> Bool in return s1.title.localizedStandardCompare(s2.title) == .orderedAscending
}
selectedFilter.value 是选择的选项卡值:"BEGINSWITH"、"CONTAINS" 或 "ENDSWITH"
self.query.value.lowercased() - 搜索查询。
unique{$0.title} 是数组的扩展方法
extension Array {
func unique<T:Hashable>(map: ((Element) -> (T))) -> [Element] {
var set = Set<T>() //the unique list kept in a Set for fast retrieval
var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
for value in self {
if !set.contains(map(value)) {
set.insert(map(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
实际上,领域搜索非常快,但是由于遍历词汇表和过滤重复项 + 通过 objects 数组按字母顺序排序操作 - 请求冻结 1-2 秒。
更新,基于 EpicPandaForce and Manuel 建议:
我又潜伏了一次,发现 .distinct(by: [keypath]) 已经出现在新版本的 RealmSwift 的结果中。
我已将 filter/sorting 请求更改为
realm.objects(Word.self).filter(vocabularyPredicate).distinct(by: ["title"]).sorted(byKeyPath: "title", ascending: true)
知道效果更好,但我想确保 UI 无论如何都不会冻结,方法是在后台线程和 UI 线程之间传递 objects。我已将建议的构造更新为:
DispatchQueue.global(qos: .background).async {
let realm = try! Realm()
let cachedWords = CashedWords()
let predicate = NSPredicate(format: "enabled == 1")
let results = realm.objects(Word.self).filter(predicate).distinct(by: ["title"]).sorted(byKeyPath: "title", ascending: true)
cachedWords.words.append(objectsIn: results)
try! realm.write {
realm.add(cachedWords)
}
let wordsRef = ThreadSafeReference(to: cachedWords)
DispatchQueue.main.async {
let realm = try! Realm()
guard let wordsResult = realm.resolve(wordsRef) else {
return
}
self.words = Array(wordsResult.words)
if ((self.view.window) != nil) {
self.tableView.reloadData()
}
}
print("data reload finalized")
}
1) 如果选项卡发生变化(例如从 Contains 到 Ends”,我如何取消之前的过滤器和领域过滤请求?
您可以创建一个 NSOperation 来执行任务并检查它是否在每个步骤之间被取消(获取、检查 isCancelled、过滤、检查 isCancelled、排序)。您不会立即取消它,但它可以提高您的表现。它还取决于这三个步骤(获取、过滤、排序)中哪一个花费的时间更长...
2) 我怎样才能在后台执行所有这些 filter/sorting 请求,这样 UI 就不会冻结?
您可以 运行 在新的 NSOperationQueue 中执行该操作。
或者只是使用 GCD,将一个块分派到后台队列,在块中创建一个 Realm 实例,然后 运行 你的代码在那里,然后将结果分派回主队列以更新 UI。
像这样:
DispatchQueue.global(qos: .userInitiated).async {
guard let realm = try? Realm() else {
return // maybe pass an empty array back to the main queue?
}
// ...
// your code here
// ...
let words = Array(result).unique{[=10=].title}.sorted {
(s1, s2) -> Bool in return s1.title.localizedStandardCompare(s2.title) == .orderedAscending
}
// Can't pass Realm objects directly across threads
let wordReferences = words.map { ThreadSafeReference(to: [=10=]) }
DispatchQueue.main.async {
// Resolve references on main thread
let realm = try! Realm()
let mainThreadWords = wordReferences.flatMap { realm.resolve([=10=]) }
// Do something with words
self.words = mainThreadWords
}
}
此外,您应该尝试优化您的查询:
let predicate = NSPredicate(format: "vocabulary.enabled == 1 AND vocabulary.lang_from CONTAINS[c] %@ AND title \(selectedFilter.value)[c] %@", self.language.value, self.query.value.lowercased())
let words = realm.objects(Word.self).filter(predicate).sorted(byKeyPath: "title")
let wordsReference = ThreadSafeReference(words)
// resolve this wordsReference in the main thread
我正在使用领域构建词汇应用程序。我有几个 objects 的词汇表,其中包含单词列表。一个词汇包含45000个单词
UI是这样构建的,如果选择相应的选项卡,用户可以通过"BEGINSWITH"、"CONTAINS"或"ENDSWITH"搜索词的标题。
因为,有几个词汇表,有一些单词,出现在几个词汇表中,我需要从UI中删除"duplicates"。
当我在结果 objects 上执行此过滤重复项 + 按字母顺序对它们进行排序时,应用程序的 UI 冻结,直到过程完成。
我的问题是: 1) 如果选项卡发生变化(例如从“包含”到“结束”),我如何取消以前的过滤器和领域过滤请求? 2) 我怎样才能在后台执行所有这些 filter/sorting 请求,所以 UI 不会冻结?
我的代码:
let vocabularyPredicate = NSPredicate(format: "enabled == 1 AND lang_from CONTAINS[c] %@", self.language.value)
self.vocabularies = Array(realm.objects(Vocabulary.self).filter(vocabularyPredicate).sorted(byKeyPath: "display_order"))
let result = List<Word>()
for object in self.vocabularies {
let predicate = NSPredicate(format: "title \(selectedFilter.value)[c] %@", self.query.value.lowercased())
result.append(objectsIn: object.words.filter(predicate))
}
self.words = Array(result).unique{[=10=].title}.sorted {
(s1, s2) -> Bool in return s1.title.localizedStandardCompare(s2.title) == .orderedAscending
}
selectedFilter.value 是选择的选项卡值:"BEGINSWITH"、"CONTAINS" 或 "ENDSWITH" self.query.value.lowercased() - 搜索查询。
unique{$0.title} 是数组的扩展方法
extension Array {
func unique<T:Hashable>(map: ((Element) -> (T))) -> [Element] {
var set = Set<T>() //the unique list kept in a Set for fast retrieval
var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
for value in self {
if !set.contains(map(value)) {
set.insert(map(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
实际上,领域搜索非常快,但是由于遍历词汇表和过滤重复项 + 通过 objects 数组按字母顺序排序操作 - 请求冻结 1-2 秒。
更新,基于 EpicPandaForce and Manuel 建议:
我又潜伏了一次,发现 .distinct(by: [keypath]) 已经出现在新版本的 RealmSwift 的结果中。
我已将 filter/sorting 请求更改为
realm.objects(Word.self).filter(vocabularyPredicate).distinct(by: ["title"]).sorted(byKeyPath: "title", ascending: true)
知道效果更好,但我想确保 UI 无论如何都不会冻结,方法是在后台线程和 UI 线程之间传递 objects。我已将建议的构造更新为:
DispatchQueue.global(qos: .background).async {
let realm = try! Realm()
let cachedWords = CashedWords()
let predicate = NSPredicate(format: "enabled == 1")
let results = realm.objects(Word.self).filter(predicate).distinct(by: ["title"]).sorted(byKeyPath: "title", ascending: true)
cachedWords.words.append(objectsIn: results)
try! realm.write {
realm.add(cachedWords)
}
let wordsRef = ThreadSafeReference(to: cachedWords)
DispatchQueue.main.async {
let realm = try! Realm()
guard let wordsResult = realm.resolve(wordsRef) else {
return
}
self.words = Array(wordsResult.words)
if ((self.view.window) != nil) {
self.tableView.reloadData()
}
}
print("data reload finalized")
}
1) 如果选项卡发生变化(例如从 Contains 到 Ends”,我如何取消之前的过滤器和领域过滤请求?
您可以创建一个 NSOperation 来执行任务并检查它是否在每个步骤之间被取消(获取、检查 isCancelled、过滤、检查 isCancelled、排序)。您不会立即取消它,但它可以提高您的表现。它还取决于这三个步骤(获取、过滤、排序)中哪一个花费的时间更长...
2) 我怎样才能在后台执行所有这些 filter/sorting 请求,这样 UI 就不会冻结?
您可以 运行 在新的 NSOperationQueue 中执行该操作。 或者只是使用 GCD,将一个块分派到后台队列,在块中创建一个 Realm 实例,然后 运行 你的代码在那里,然后将结果分派回主队列以更新 UI。 像这样:
DispatchQueue.global(qos: .userInitiated).async {
guard let realm = try? Realm() else {
return // maybe pass an empty array back to the main queue?
}
// ...
// your code here
// ...
let words = Array(result).unique{[=10=].title}.sorted {
(s1, s2) -> Bool in return s1.title.localizedStandardCompare(s2.title) == .orderedAscending
}
// Can't pass Realm objects directly across threads
let wordReferences = words.map { ThreadSafeReference(to: [=10=]) }
DispatchQueue.main.async {
// Resolve references on main thread
let realm = try! Realm()
let mainThreadWords = wordReferences.flatMap { realm.resolve([=10=]) }
// Do something with words
self.words = mainThreadWords
}
}
此外,您应该尝试优化您的查询:
let predicate = NSPredicate(format: "vocabulary.enabled == 1 AND vocabulary.lang_from CONTAINS[c] %@ AND title \(selectedFilter.value)[c] %@", self.language.value, self.query.value.lowercased())
let words = realm.objects(Word.self).filter(predicate).sorted(byKeyPath: "title")
let wordsReference = ThreadSafeReference(words)
// resolve this wordsReference in the main thread