【问题标题】:How to filter large array in iOS swift 2 for uisearchbar如何在 iOS swift 2 中为 uisearchbar 过滤大数组
【发布时间】:2016-06-05 13:02:31
【问题描述】:

我有一个UISearchBar,数组中有超过 80000 个元素,我必须根据用户输入过滤这个数组。

但是在搜索视图中输入时,它的工作速度非常慢,这意味着在键盘上输入值需要花费太多时间。

func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {

    if searchText.characters.count == 0 {
        searchActive = false
    } else {
        searchActive = true;
        filtered.removeAllObjects()

        dispatch_to_background_queue {
            for sumber in self.data {
                let nameRange: NSRange = sumber.rangeOfString(searchText, options: [NSStringCompareOptions.AnchoredSearch,NSStringCompareOptions.CaseInsensitiveSearch])
                if nameRange.location != NSNotFound {
                    self.filtered.addObject(sumber)
                }
            }//end of for

            self.dispatch_to_main_queue {
                /* some code to be executed on the main queue */
                self.tableView.reloadData()
            }
        } //end of dispatch
    }
}

func dispatch_to_main_queue(block: dispatch_block_t?) {
    dispatch_async(dispatch_get_main_queue(), block!)
}

func dispatch_to_background_queue(block: dispatch_block_t?) {
    let q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    dispatch_async(q, block!)
}

【问题讨论】:

  • 如何过滤数据?
  • for sumber in self.data { let nameRange: NSRange = sumber.rangeOfString(searchText, options: [NSStringCompareOptions.AnchoredSearch,NSStringCompareOptions.CaseInsensitiveSearch]) if nameRange.location != NSNotFound { self.filtered. addObject(sumber) }
  • 用你的代码编辑你的问题,不要放在评论里,它很难阅读。
  • 我已将搜索过滤器放在 dispatch_async 中,但它仍然运行缓慢
  • 我已经用我的搜索过滤器代码更新了我的问题

标签: ios arrays swift filter uisearchbar


【解决方案1】:

我想补充几点。

您似乎已经在进行异步处理,这很棒。它不会使搜索更快,但应用程序会保持响应。考虑让它停止。如果用户键入三个字母,您将排队三个搜索,并且只有在最后一次运行完成后才能获得相关结果。这可以使用在搜索中检查的某种布尔停止标志来完成。如果开始新的搜索,请先杀死旧的。

显示部分结果。用户不会同时观看数千个单元格,而只会观看前 20 个左右的单元格。根据您输入和输出的顺序,这可能很容易做到,而且速度非常快。

以您之前的搜索为基础。仅当搜索“A”(或“b”,如果搜索未锚定)成功时,搜索“Ab”才会成功。因此,如果您的上一次搜索是当前搜索的子字符串,请将上一次搜索的输出数组作为输入。显然,在这里停止搜索要小心。

检查性能是否真的一样差。您是否在开启优化的情况下运行?调试模式可能会慢很多,但这没关系。

数据从何而来?这是要保存在内存中的大量数据。如果它来自数据库,使用数据库函数可能会更容易(并且上面的大多数词仍然符合)。

还是太慢了?索引您的数据集。如果您预先知道哪些元素包含“A”,则所需搜索的数量可能会显着下降。而且你已经有了第一次搜索的结果

当您使用锚定搜索时,处理排序数组可以提供更好的性能特征。只需使用二进制搜索找到搜索词的第一个和最后一个元素并使用该范围。也许甚至没有复制到新数组中。这种方法预先设置了一些工作量(可能在用户开始打字之前)。如果您的搜索数据位于较大的对象中,则可以使用某种索引表。

【讨论】:

    【解决方案2】:

    这里有两种方法可以组合以获得最佳效果:

    首先,让长时间运行的操作远离主 (UI) 线程

    您可以使用dispatch_async 将过滤分派到后台线程,甚至在延迟一段时间后使用dispatch_after 将过滤分派到后台线程。

    第二,不要在每次按键后立即过滤数组

    这是浪费时间,因为通常用户会在等待查看弹出内容之前键入几个键。因此,您希望延迟过滤操作,并且仅在自上次按键后经过一小段时间后才执行它。这称为“去抖动”。

    这是在 Swift 中完成所有这些工作的一种巧妙方法:

    func debounce(delay:NSTimeInterval, queue:dispatch_queue_t, action: (()->())) -> (()->()) {
    
        var lastFireTime:dispatch_time_t = 0
        let dispatchDelay = Int64(delay * Double(NSEC_PER_SEC))
    
        return {
            lastFireTime = dispatch_time(DISPATCH_TIME_NOW,0)
            dispatch_after(
                dispatch_time(
                    DISPATCH_TIME_NOW,
                    dispatchDelay
                ),
                queue) {
                    let now = dispatch_time(DISPATCH_TIME_NOW,0)
                    let when = dispatch_time(lastFireTime, dispatchDelay)
                    if now >= when {
                        action()
                    }
            }
        }
    }
    
    class ViewController {
    
        lazy var debouncedFilterArray : () -> () = debounce(0.3, queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), action: self.filterArray)
    
        func filterArray() {
            // do filtering here, but don't call this function directly            
        }
    }
    

    debounce 函数本身返回一个函数,该函数在调用时会表现出这种“去抖动”行为,运行频率不超过传递给它的 delay 间隔。

    要使用,只需致电debouncedFilterArray()。它将依次调用filterArray,但始终在后台线程上,并且频率不会超过每 0.3 秒一次。

    【讨论】:

    • if ([newSearchText containsString:oldSearchText]),如果操作完成,在已过滤的数组上应用新搜索是否更有效,containsString: 与过滤器相比相当快,它不应该是太费时间了。
    • 可以想象,用户插入或删除了足够多的内容以使已经过滤的数组无效,因此每次都检查整个事情在技术上更安全。当然,您可以在那里进行额外的检查,如果新的搜索词包含以前的搜索词,那么对已经过滤的数组进行过滤是更可取的。
    • @Craig McMahon,这是一个很好的答案,我将添加书签以供将来参考。
    • 这当然可以更有效,但它可能是过早的优化。用户不会介意在最后一次按键后等待几分之一秒才能显示结果,只要他的键盘没有锁定即可。
    【解决方案3】:

    80000!这确实是很多数据。一种可以显着加快速度的解决方案是在每次击键后缩小搜索数组,并且如果连续键入许多击键则取消搜索,同时缓存搜索以防击键被删除。您可以将它与 appzYouLife 的答案结合起来,您将拥有一个更可靠的框架。这是一个如何工作的示例,令牌是必要的,以便您根据搜索更新 UI:

    var dataToSearch = [AnyObject]()
      var searchCache = NSCache()
      var currentSearchToken = 0
    
      func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
        performSearch(searchText, searchToken: ++currentSearchToken)
      }
    
      func performSearch(searchText: String, searchToken: Int) {
    
        if let searchResults = searchCache.objectForKey(searchText) as? [AnyObject] { //If the search is cached, we simply pull the results
          guard searchToken == currentSearchToken else {return} //Make sure we don't trigger unwanted UI updates
          performListUpdate(searchResults)
          return
        }
    
        var possiblePreviousSearch = searchText //We're going to see if we can build on any of previous searches
    
        while String(possiblePreviousSearch.characters.dropLast()).characters.count > 0 { //While we still have characters
          possiblePreviousSearch = String(possiblePreviousSearch.characters.dropLast()) //Drop the last character of the search string
          if let lastSearch = searchCache.objectForKey(possiblePreviousSearch) as? [AnyObject]{ //We found a previous list of results
            let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
    
            dispatch_async(queue) {
              let newResults = lastSearch.filter {object in /**put your conditions here instead of return true*/ return true} //Sort on top of a previous search
              self.searchCache.setObject(newResults, forKey: searchText)
              guard searchToken == self.currentSearchToken else {return} //We don't want to trigger UI Updates for a previous search
              dispatch_async(dispatch_get_main_queue()) {
                self.performListUpdate(newResults)
                return
              }
            }
          }
        }
    
        //If we got to this point, we simply have to search through all the data
        let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
    
        dispatch_async(queue) {
          let newResults = self.dataToSearch.filter {object in /**put your conditions here instead of return true*/ return true} //Sort on top of a previous search
          self.searchCache.setObject(newResults, forKey: searchText)
          guard searchToken == self.currentSearchToken else {return} //We don't want to trigger UI Updates for a previous search
          dispatch_async(dispatch_get_main_queue()) {
            self.performListUpdate(newResults)
            return
          }
        }
      } //end of perform search
    

    当然,这个答案并不完美。它假设您的列表可以在较小的列表之上进行排序(例如,搜索“abc”的结果将是搜索“ab”和“a”结果的子集)。

    编辑将其与去抖动结合起来,如另一个答案所示,您的表现还不错!

    【讨论】:

      【解决方案4】:

      您可以在后台线程上执行过滤,以便让主线程(管理 UI)保持响应。

      func filter(list:[String], keyword:String, completion: (filteredList:[String]) -> ()) {
      
          let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
      
          dispatch_async(queue) {
              let filtered = list.filter { $0.containsString(keyword) }
      
              dispatch_async(dispatch_get_main_queue()) {
                  completion(filteredList: filtered)
              }
          }
      }
      

      示例

      let data = ["dog", "cat", "eagle"]
      filtered(data, keyword: "do") { (filteredList) -> () in
          // update the UI here
      }
      

      【讨论】:

      • 我已经用我的代码更新了我的问题,你能看看它并建议我改变吗?
      猜你喜欢
      • 1970-01-01
      • 2021-09-24
      • 1970-01-01
      • 2019-08-15
      • 2023-04-07
      • 2023-03-17
      • 2016-06-06
      • 1970-01-01
      • 2018-05-05
      相关资源
      最近更新 更多