From 12587cf6429a79b244f2a02fab00ca66ce2539a6 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Mon, 12 Sep 2016 18:24:52 -0400 Subject: [PATCH] SearchOperation Optimization --- .../Controller/Search/SearchResultTBVC.swift | 18 +-- Kiwix-iOS/Info.plist | 2 +- Kiwix-iOSWidgets/Bookmarks/Info.plist | 2 +- Kiwix.xcodeproj/project.pbxproj | 2 - Kiwix/Operations/SearchOperation-old.swift | 110 ------------------ Kiwix/Operations/SearchOperation.swift | 67 ++++++++++- 6 files changed, 77 insertions(+), 124 deletions(-) delete mode 100644 Kiwix/Operations/SearchOperation-old.swift diff --git a/Kiwix-iOS/Controller/Search/SearchResultTBVC.swift b/Kiwix-iOS/Controller/Search/SearchResultTBVC.swift index 43a521d6..9ee0bb09 100644 --- a/Kiwix-iOS/Controller/Search/SearchResultTBVC.swift +++ b/Kiwix-iOS/Controller/Search/SearchResultTBVC.swift @@ -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) } diff --git a/Kiwix-iOS/Info.plist b/Kiwix-iOS/Info.plist index 30ada7b1..4fac61d5 100644 --- a/Kiwix-iOS/Info.plist +++ b/Kiwix-iOS/Info.plist @@ -49,7 +49,7 @@ CFBundleVersion - 1.8.279 + 1.8.323 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/Kiwix-iOSWidgets/Bookmarks/Info.plist b/Kiwix-iOSWidgets/Bookmarks/Info.plist index 269099c1..ab47930a 100644 --- a/Kiwix-iOSWidgets/Bookmarks/Info.plist +++ b/Kiwix-iOSWidgets/Bookmarks/Info.plist @@ -21,7 +21,7 @@ CFBundleSignature ???? CFBundleVersion - 1.8.282 + 1.8.326 NSExtension NSExtensionMainStoryboard diff --git a/Kiwix.xcodeproj/project.pbxproj b/Kiwix.xcodeproj/project.pbxproj index 49096e25..4d658453 100644 --- a/Kiwix.xcodeproj/project.pbxproj +++ b/Kiwix.xcodeproj/project.pbxproj @@ -355,7 +355,6 @@ 97D6811C1D6F70AC00E5FA99 /* GlobalQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalQueue.swift; sourceTree = ""; }; 97D6811D1D6F70AC00E5FA99 /* RefreshLibraryOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshLibraryOperation.swift; sourceTree = ""; }; 97D6811E1D6F70AC00E5FA99 /* ScanLocalBookOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanLocalBookOperation.swift; sourceTree = ""; }; - 97D6811F1D6F70AC00E5FA99 /* SearchOperation-old.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SearchOperation-old.swift"; sourceTree = ""; }; 97D681201D6F70AC00E5FA99 /* UIOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIOperations.swift; sourceTree = ""; }; 97D681211D6F70AC00E5FA99 /* UpdateWidgetDataSourceOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateWidgetDataSourceOperation.swift; sourceTree = ""; }; 97D681221D6F70AC00E5FA99 /* URLSessionDownloadTaskOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionDownloadTaskOperation.swift; sourceTree = ""; }; @@ -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 */, diff --git a/Kiwix/Operations/SearchOperation-old.swift b/Kiwix/Operations/SearchOperation-old.swift deleted file mode 100644 index a02e625c..00000000 --- a/Kiwix/Operations/SearchOperation-old.swift +++ /dev/null @@ -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()) - } - - 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 - } -} - diff --git a/Kiwix/Operations/SearchOperation.swift b/Kiwix/Operations/SearchOperation.swift index 9c91c0f0..6f7f5984 100644 --- a/Kiwix/Operations/SearchOperation.swift +++ b/Kiwix/Operations/SearchOperation.swift @@ -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 } }