SearchOperation Optimization

This commit is contained in:
Chris Li 2016-09-12 18:24:52 -04:00
parent 6a2ff68936
commit 12587cf642
6 changed files with 77 additions and 124 deletions

View File

@ -121,17 +121,17 @@ class SearchResultTBVC: UIViewController, UITableViewDataSource, UITableViewDele
tableView.reloadData()
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)
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)
}

View File

@ -49,7 +49,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1.8.279</string>
<string>1.8.323</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@ -21,7 +21,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.8.282</string>
<string>1.8.326</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionMainStoryboard</key>

View File

@ -355,7 +355,6 @@
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>"; };
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>"; };
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>"; };
@ -990,7 +989,6 @@
97D6811D1D6F70AC00E5FA99 /* RefreshLibraryOperation.swift */,
97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */,
97D4D64E1D874E6E00C1B065 /* SearchOperation.swift */,
97D6811F1D6F70AC00E5FA99 /* SearchOperation-old.swift */,
97D681201D6F70AC00E5FA99 /* UIOperations.swift */,
97D681211D6F70AC00E5FA99 /* UpdateWidgetDataSourceOperation.swift */,
97D681221D6F70AC00E5FA99 /* URLSessionDownloadTaskOperation.swift */,

View File

@ -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
}
}

View File

@ -9,7 +9,72 @@
import Operations
class SearchOperation: GroupOperation {
private(set) var results = [SearchResult]()
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
}
}