取消对大数据数组的过滤和排序

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