簡體   English   中英

如何在iOS swift 2中為uisearchbar過濾大型數組

[英]How to filter large array in iOS swift 2 for uisearchbar

我有一個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)線程之外

您可以使用dispatch_async將過濾分派到后台線程,或者使用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秒。

我想補充幾點想法。

你似乎已經做了異步處理,這很棒。 它不會使搜索更快,但應用程序保持響應。 考慮讓它可以停止。 如果用戶鍵入三個字母,您將排隊三次搜索,並且僅在上次運行完成后才會獲得相關結果。 這可以使用在搜索中檢查的某種布爾停止標志來完成。 如果啟動了新搜索,請先刪除舊搜索。

顯示部分結果。 用戶不會同時觀看數千個單元格,而只會在前20個左右觀看。 根據輸入和輸出的順序,這可能很容易做到,並且快速地完成。

以您之前的搜索為基礎。 搜索“Ab”只有在搜索“A”(或“b”,如果搜索沒有錨定)成功的情況下才會成功。 因此,如果您的上次搜索是當前搜索的子字符串,請將先前搜索的輸出數組作為輸入。 顯然,請注意停止搜索。

檢查性能是否真的如此糟糕。 您是否在啟用優化的情況下運行? 調試模式可能會相當慢,但這並不重要。

數據來自哪里? 這是在內存中保留的相當大量的數據。 如果它來自數據庫,使用數據庫函數可能會更容易(並且上面的大多數單詞仍然符合)。

還是太慢了? 索引數據集。 如果您事先知道哪些元素包含“A”,則所需搜索的數量可能會顯着下降。 你已經有了第一次搜索的結果

當您使用錨定搜索時,處理排序數組可以提供更好的性能特征。 只需使用二進制搜索找到搜索詞的第一個和最后一個元素,然后使用該范圍即可。 也許甚至沒有復制到新陣列。 這種方法預先設置了一些工作負載(可能在用戶開始輸入之前)。 如果您的搜索數據位於較大的對象中,則可以使用某種索引表。

您可以在后台線程上執行過濾,以便讓主線程(管理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
}

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

編輯將此與去抖動結合使用,如另一個答案所示,你的表現還不錯!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM