Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/arrays/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在iOS swift 2中为uisearchbar筛选大型阵列_Ios_Arrays_Swift_Filter_Uisearchbar - Fatal编程技术网

如何在iOS swift 2中为uisearchbar筛选大型阵列

如何在iOS swift 2中为uisearchbar筛选大型阵列,ios,arrays,swift,filter,uisearchbar,Ios,Arrays,Swift,Filter,Uisearchbar,我有一个UISearchBar,在一个数组中包含80000多个元素,我必须根据用户输入过滤这个数组 但在搜索视图中输入时,它的工作速度非常慢,这意味着在键盘上输入值要花费太多时间 func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { if searchText.characters.count == 0 { searchActive = false } else {

我有一个
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!)
}

您可以在后台线程上执行过滤,使主线程(管理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)
        }
    }
}
例子
这里有两种方法可以结合使用以获得最佳效果:

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

您可以使用
dispatch\u async
将筛选分派到后台线程,甚至在延迟一段时间后使用
dispatch\u 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秒。

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”结果的子集)


编辑如另一个答案所示,将此与去抖动相结合,您的表现正常

我想补充一些想法

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

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

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

检查性能是否真的一样差。您是否在打开优化的情况下运行?调试模式可能会慢很多,但这并不重要

数据来自哪里?这是一个相当大的数据量保留在内存中。如果它来自数据库,那么使用数据库函数可能会更容易(而且上面的大多数单词仍然符合要求)

还是太慢了?为数据集编制索引。如果您预先知道哪些元素包含“A”,那么所需的搜索数量可能会显著减少。你已经有了第一次搜索的结果了


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

如何筛选数据?对于self.data{let nameRange:NSRange=sumber.rangeOfString(搜索文本,选项:[NSStringCompareOptions.AnchoredSearch,NSStringCompareOptions.CaseInsensitiveSearch])中的sumber如果nameRange.location!=NSNotFound{self.filtered.addObject(sumber)}用你的代码编辑你的问题,不要把它放在评论中,这很难阅读。我已经把我的搜索过滤器放在dispatch\u async中,但它仍然有效。我已经用我的搜索过滤器代码更新了我的问题。我已经用我的代码更新了我的问题。你能看看它并建议我修改吗。
if([newSearchText containsString:oldsearchttext])
,在已过滤的数组上应用新搜索是否更有效如果操作完成,
包含字符串:
与过滤器相比速度非常快,应该不会花费太多时间。可以想象,用户插入或删除了足够的内容,使已过滤的数组无效,但o从技术上讲,每次检查整个搜索过程更安全。当然,您可以在其中添加额外的检查,这样,如果新的搜索项包含以前的搜索项,则最好在已过滤的数组上进行过滤。@Craig McMahon,这是一个很好的答案,我选择了
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