mirror of
https://github.com/kiwix/kiwix-apple.git
synced 2025-09-28 06:25:04 -04:00
SearchOperation Optimization
This commit is contained in:
parent
6a2ff68936
commit
12587cf642
@ -121,17 +121,17 @@ class SearchResultTBVC: UIViewController, UITableViewDataSource, UITableViewDele
|
|||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// let operation = SearchOperation(searchTerm: searchText) { [unowned self] (results) in
|
|
||||||
// guard let results = results else {return}
|
|
||||||
// self.searchResults = results
|
|
||||||
// self.tableView.reloadData()
|
|
||||||
// if results.count > 0 {
|
|
||||||
// self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: .Top, animated: true)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
let operation = SearchOperation(searchTerm: searchText)
|
let operation = SearchOperation(searchTerm: searchText)
|
||||||
operation.addObserver(DidFinishObserver {(operation, errors) in
|
operation.addObserver(DidFinishObserver {(operation, errors) in
|
||||||
print("search op did finish, result injection")
|
guard let operation = operation as? SearchOperation else {return}
|
||||||
|
NSOperationQueue.mainQueue().addOperationWithBlock({
|
||||||
|
self.searchResults = operation.results
|
||||||
|
self.tableView.reloadData()
|
||||||
|
|
||||||
|
guard operation.results.count > 0 else {return}
|
||||||
|
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: .Top, animated: true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
GlobalQueue.shared.add(search: operation)
|
GlobalQueue.shared.add(search: operation)
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.8.279</string>
|
<string>1.8.323</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.8.282</string>
|
<string>1.8.326</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionMainStoryboard</key>
|
<key>NSExtensionMainStoryboard</key>
|
||||||
|
@ -355,7 +355,6 @@
|
|||||||
97D6811C1D6F70AC00E5FA99 /* GlobalQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalQueue.swift; sourceTree = "<group>"; };
|
97D6811C1D6F70AC00E5FA99 /* GlobalQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalQueue.swift; sourceTree = "<group>"; };
|
||||||
97D6811D1D6F70AC00E5FA99 /* RefreshLibraryOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshLibraryOperation.swift; sourceTree = "<group>"; };
|
97D6811D1D6F70AC00E5FA99 /* RefreshLibraryOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshLibraryOperation.swift; sourceTree = "<group>"; };
|
||||||
97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanLocalBookOperation.swift; sourceTree = "<group>"; };
|
97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanLocalBookOperation.swift; sourceTree = "<group>"; };
|
||||||
97D6811F1D6F70AC00E5FA99 /* SearchOperation-old.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SearchOperation-old.swift"; sourceTree = "<group>"; };
|
|
||||||
97D681201D6F70AC00E5FA99 /* UIOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIOperations.swift; sourceTree = "<group>"; };
|
97D681201D6F70AC00E5FA99 /* UIOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIOperations.swift; sourceTree = "<group>"; };
|
||||||
97D681211D6F70AC00E5FA99 /* UpdateWidgetDataSourceOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateWidgetDataSourceOperation.swift; sourceTree = "<group>"; };
|
97D681211D6F70AC00E5FA99 /* UpdateWidgetDataSourceOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateWidgetDataSourceOperation.swift; sourceTree = "<group>"; };
|
||||||
97D681221D6F70AC00E5FA99 /* URLSessionDownloadTaskOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionDownloadTaskOperation.swift; sourceTree = "<group>"; };
|
97D681221D6F70AC00E5FA99 /* URLSessionDownloadTaskOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionDownloadTaskOperation.swift; sourceTree = "<group>"; };
|
||||||
@ -990,7 +989,6 @@
|
|||||||
97D6811D1D6F70AC00E5FA99 /* RefreshLibraryOperation.swift */,
|
97D6811D1D6F70AC00E5FA99 /* RefreshLibraryOperation.swift */,
|
||||||
97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */,
|
97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */,
|
||||||
97D4D64E1D874E6E00C1B065 /* SearchOperation.swift */,
|
97D4D64E1D874E6E00C1B065 /* SearchOperation.swift */,
|
||||||
97D6811F1D6F70AC00E5FA99 /* SearchOperation-old.swift */,
|
|
||||||
97D681201D6F70AC00E5FA99 /* UIOperations.swift */,
|
97D681201D6F70AC00E5FA99 /* UIOperations.swift */,
|
||||||
97D681211D6F70AC00E5FA99 /* UpdateWidgetDataSourceOperation.swift */,
|
97D681211D6F70AC00E5FA99 /* UpdateWidgetDataSourceOperation.swift */,
|
||||||
97D681221D6F70AC00E5FA99 /* URLSessionDownloadTaskOperation.swift */,
|
97D681221D6F70AC00E5FA99 /* URLSessionDownloadTaskOperation.swift */,
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
//
|
|
||||||
// SearchOperation.swift
|
|
||||||
// Kiwix
|
|
||||||
//
|
|
||||||
// Created by Chris Li on 4/9/16.
|
|
||||||
// Copyright © 2016 Chris. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Operations
|
|
||||||
|
|
||||||
class SearchOperation: GroupOperation {
|
|
||||||
let completionHandler: ([SearchResult]?) -> Void
|
|
||||||
private(set) var results = [SearchResult]()
|
|
||||||
//private let startTime = NSDate()
|
|
||||||
|
|
||||||
init(searchTerm: String, completionHandler: ([SearchResult]?) -> Void) {
|
|
||||||
self.completionHandler = completionHandler
|
|
||||||
super.init(operations: [NSOperation]())
|
|
||||||
|
|
||||||
let sortOperation = SortSearchResultsOperation { (results) in
|
|
||||||
self.results = results
|
|
||||||
}
|
|
||||||
|
|
||||||
for (id, zimReader) in ZimMultiReader.shared.readers {
|
|
||||||
let managedObjectContext = UIApplication.appDelegate.managedObjectContext
|
|
||||||
guard let book = Book.fetch(id, context: managedObjectContext) else {continue}
|
|
||||||
guard book.includeInSearch else {continue}
|
|
||||||
let operation = SingleBookSearchOperation(zimReader: zimReader,
|
|
||||||
lowerCaseSearchTerm: searchTerm.lowercaseString,
|
|
||||||
completionHandler: { [unowned sortOperation] (results) in
|
|
||||||
sortOperation.results += results
|
|
||||||
})
|
|
||||||
|
|
||||||
addOperation(operation)
|
|
||||||
sortOperation.addDependency(operation)
|
|
||||||
}
|
|
||||||
|
|
||||||
addOperation(sortOperation)
|
|
||||||
|
|
||||||
addCondition(MutuallyExclusive<ZimMultiReader>())
|
|
||||||
}
|
|
||||||
|
|
||||||
override func operationDidFinish(errors: [ErrorType]) {
|
|
||||||
NSOperationQueue.mainQueue().addOperationWithBlock {
|
|
||||||
self.completionHandler(self.cancelled ? nil : self.results)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SingleBookSearchOperation: Operation {
|
|
||||||
let zimReader: ZimReader
|
|
||||||
let lowerCaseSearchTerm: String
|
|
||||||
let completionHandler: ([SearchResult]) -> Void
|
|
||||||
|
|
||||||
init(zimReader: ZimReader, lowerCaseSearchTerm: String, completionHandler: ([SearchResult]) -> Void) {
|
|
||||||
self.zimReader = zimReader
|
|
||||||
self.lowerCaseSearchTerm = lowerCaseSearchTerm
|
|
||||||
self.completionHandler = completionHandler
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override private func execute() {
|
|
||||||
var results = [String: SearchResult]()
|
|
||||||
let indexedDics = zimReader.searchUsingIndex(lowerCaseSearchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]()
|
|
||||||
let titleDics = zimReader.searchSuggestionsSmart(lowerCaseSearchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]()
|
|
||||||
let mixedDics = titleDics + indexedDics // It is important we process the title search result first, so that we always keep the indexed search result
|
|
||||||
for dic in mixedDics {
|
|
||||||
guard let result = SearchResult (rawResult: dic, lowerCaseSearchTerm: lowerCaseSearchTerm) else {continue}
|
|
||||||
results[result.path] = result
|
|
||||||
}
|
|
||||||
completionHandler(Array(results.values))
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SortSearchResultsOperation: Operation {
|
|
||||||
let completionHandler: ([SearchResult]) -> Void
|
|
||||||
var results = [SearchResult]()
|
|
||||||
|
|
||||||
init(completionHandler: ([SearchResult]) -> Void) {
|
|
||||||
self.completionHandler = completionHandler
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override private func execute() {
|
|
||||||
sort()
|
|
||||||
completionHandler(results)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func sort() {
|
|
||||||
results.sortInPlace { (result0, result1) -> Bool in
|
|
||||||
if result0.score != result1.score {
|
|
||||||
return result0.score < result1.score
|
|
||||||
} else {
|
|
||||||
if result0.snippet != nil {return true}
|
|
||||||
if result1.snippet != nil {return false}
|
|
||||||
return titleCaseInsensitiveCompare(result0, result1: result1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Utilities
|
|
||||||
|
|
||||||
private func titleCaseInsensitiveCompare(result0: SearchResult, result1: SearchResult) -> Bool {
|
|
||||||
return result0.title.caseInsensitiveCompare(result1.title) == NSComparisonResult.OrderedAscending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,72 @@
|
|||||||
import Operations
|
import Operations
|
||||||
|
|
||||||
class SearchOperation: GroupOperation {
|
class SearchOperation: GroupOperation {
|
||||||
|
private(set) var results = [SearchResult]()
|
||||||
|
|
||||||
init(searchTerm: String) {
|
init(searchTerm: String) {
|
||||||
super.init(operations: [])
|
let searches: [BookSearch] = ZimMultiReader.shared.readers.keys.map({ BookSearch(zimID: $0, searchTerm: searchTerm) })
|
||||||
|
let sort = Sort()
|
||||||
|
searches.forEach { (search) in
|
||||||
|
sort.injectResultFromDependency(search, block: { (operation, dependency, errors) in
|
||||||
|
operation.requirement += dependency.results
|
||||||
|
})
|
||||||
|
}
|
||||||
|
super.init(operations: searches + [sort])
|
||||||
|
|
||||||
|
sort.addObserver(DidFinishObserver { [unowned self] (operation, errors) in
|
||||||
|
guard let operation = operation as? Sort else {return}
|
||||||
|
self.results = operation.requirement
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BookSearch: Operation {
|
||||||
|
let zimID: String
|
||||||
|
let searchTerm: String
|
||||||
|
private var results = [SearchResult]()
|
||||||
|
|
||||||
|
init(zimID: String, searchTerm: String) {
|
||||||
|
self.zimID = zimID
|
||||||
|
self.searchTerm = searchTerm
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override private func execute() {
|
||||||
|
guard let reader = ZimMultiReader.shared.readers[zimID] else {return}
|
||||||
|
|
||||||
|
let indexedDics = reader.searchUsingIndex(searchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]()
|
||||||
|
let titleDics = reader.searchSuggestionsSmart(searchTerm) as? [[String: AnyObject]] ?? [[String: AnyObject]]()
|
||||||
|
let mixedDics = titleDics + indexedDics // It is important we process the title search result first, so that we always keep the indexed search result
|
||||||
|
|
||||||
|
for dic in mixedDics {
|
||||||
|
guard let result = SearchResult (rawResult: dic, lowerCaseSearchTerm: searchTerm) else {continue}
|
||||||
|
self.results.append(result)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Sort: Operation, AutomaticInjectionOperationType {
|
||||||
|
var requirement = [SearchResult]()
|
||||||
|
|
||||||
|
private override func execute() {
|
||||||
|
sort()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sort() {
|
||||||
|
requirement.sortInPlace { (result0, result1) -> Bool in
|
||||||
|
if result0.score != result1.score {
|
||||||
|
return result0.score < result1.score
|
||||||
|
} else {
|
||||||
|
if result0.snippet != nil {return true}
|
||||||
|
if result1.snippet != nil {return false}
|
||||||
|
return titleCaseInsensitiveCompare(result0, result1: result1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func titleCaseInsensitiveCompare(result0: SearchResult, result1: SearchResult) -> Bool {
|
||||||
|
return result0.title.caseInsensitiveCompare(result1.title) == NSComparisonResult.OrderedAscending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user