Operation lib refactor

This commit is contained in:
Chris Li 2016-08-04 16:40:18 -04:00
parent c5f1f57728
commit 4ea03c6599
12 changed files with 28 additions and 355 deletions

View File

@ -10,7 +10,7 @@ import CoreData
import Operations
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, OperationQueueDelegate {
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var mainController: MainController? {

View File

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

View File

@ -106,7 +106,7 @@ class Network: NSObject, NSURLSessionDelegate, NSURLSessionDownloadDelegate, NSU
// MARK: - OperationQueueDelegate
func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation) {
func operationQueue(queue: OperationQueue, willAddOperation operation: NSOperation) {
guard operationQueue.operationCount == 0 else {return}
shouldReportProgress = true
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in
@ -114,7 +114,9 @@ class Network: NSObject, NSURLSessionDelegate, NSURLSessionDownloadDelegate, NSU
}
}
func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError]) {
func operationQueue(queue: OperationQueue, willFinishOperation operation: NSOperation, withErrors errors: [ErrorType]) {}
func operationQueue(queue: OperationQueue, didFinishOperation operation: NSOperation, withErrors errors: [ErrorType]) {
guard operationQueue.operationCount == 1 else {return}
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in
self.timer?.invalidate()
@ -122,6 +124,8 @@ class Network: NSObject, NSURLSessionDelegate, NSURLSessionDownloadDelegate, NSU
}
}
func operationQueue(queue: OperationQueue, willProduceOperation operation: NSOperation) {}
// MARK: - NSURLSessionDelegate
func URLSessionDidFinishEventsForBackgroundURLSession(session: NSURLSession) {

View File

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

View File

@ -10,7 +10,6 @@
7356F9FACBB84380CFC8F68F /* Pods_Kiwix_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC884ACBBA260AF741C4C4FE /* Pods_Kiwix_iOS.framework */; };
970C3DCA1CBD79450026A240 /* MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970C3DC91CBD79450026A240 /* MigrationPolicy.swift */; };
970C61971D34243600087758 /* URLSessionDownloadTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970C61961D34243600087758 /* URLSessionDownloadTaskOperation.swift */; };
970C61991D3429E400087758 /* ReachabilityCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970C61981D3429E400087758 /* ReachabilityCondition.swift */; };
970C65501D398D5A007032F8 /* BookmarkControllerAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970C654F1D398D5A007032F8 /* BookmarkControllerAnimator.swift */; };
970E68B21D37E1DD001E8514 /* SettingSearchTuneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970E68B11D37E1DD001E8514 /* SettingSearchTuneController.swift */; };
970E68B61D37E224001E8514 /* SettingSearchHistoryTBVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 970E68B51D37E224001E8514 /* SettingSearchHistoryTBVC.swift */; };
@ -273,7 +272,6 @@
6DCB0E958A1083CA248C5A12 /* Pods-Kiwix-OSX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Kiwix-OSX.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Kiwix-OSX/Pods-Kiwix-OSX.debug.xcconfig"; sourceTree = "<group>"; };
970C3DC91CBD79450026A240 /* MigrationPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MigrationPolicy.swift; path = CoreData/Migration/MigrationPolicy.swift; sourceTree = "<group>"; };
970C61961D34243600087758 /* URLSessionDownloadTaskOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = URLSessionDownloadTaskOperation.swift; path = Operations/URLSessionDownloadTaskOperation.swift; sourceTree = "<group>"; };
970C61981D3429E400087758 /* ReachabilityCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityCondition.swift; sourceTree = "<group>"; };
970C654F1D398D5A007032F8 /* BookmarkControllerAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BookmarkControllerAnimator.swift; path = "Kiwix-iOS/Controller/Bookmark/BookmarkControllerAnimator.swift"; sourceTree = SOURCE_ROOT; };
970E68B11D37E1DD001E8514 /* SettingSearchTuneController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SettingSearchTuneController.swift; path = "Kiwix-iOS/Controller/Setting/SettingSearchTuneController.swift"; sourceTree = SOURCE_ROOT; };
970E68B51D37E224001E8514 /* SettingSearchHistoryTBVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SettingSearchHistoryTBVC.swift; path = "Kiwix-iOS/Controller/Setting/SettingSearchHistoryTBVC.swift"; sourceTree = SOURCE_ROOT; };
@ -412,9 +410,7 @@
977998711C1E0B7900B1DD5E /* Article+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Article+CoreDataProperties.swift"; path = "Kiwix/CoreData/Article+CoreDataProperties.swift"; sourceTree = "<group>"; };
977998721C1E0B7900B1DD5E /* Language+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Language+CoreDataProperties.swift"; path = "Kiwix/CoreData/Language+CoreDataProperties.swift"; sourceTree = "<group>"; };
9779987A1C1E1C9600B1DD5E /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Extensions.swift; path = Kiwix/Extensions.swift; sourceTree = "<group>"; };
9779A1C21D34225E0071EFAB /* AlertOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlertOperation.swift; path = Operations/AlertOperation.swift; sourceTree = "<group>"; };
9779A1C31D34225E0071EFAB /* GlobalOperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GlobalOperationQueue.swift; path = Operations/GlobalOperationQueue.swift; sourceTree = "<group>"; };
9779A1C41D34225E0071EFAB /* NetworkObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NetworkObserver.swift; path = Operations/NetworkObserver.swift; sourceTree = "<group>"; };
9779A1C51D34225E0071EFAB /* RefreshLibraryOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RefreshLibraryOperation.swift; path = Operations/RefreshLibraryOperation.swift; sourceTree = "<group>"; };
9779A1C61D34225E0071EFAB /* SearchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SearchOperation.swift; path = Operations/SearchOperation.swift; sourceTree = "<group>"; };
9779A1C71D34225E0071EFAB /* UIOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIOperations.swift; path = Operations/UIOperations.swift; sourceTree = "<group>"; };
@ -793,17 +789,6 @@
name = Shared;
sourceTree = "<group>";
};
973DD4031D343F09009D45DB /* Basic */ = {
isa = PBXGroup;
children = (
9779A1C21D34225E0071EFAB /* AlertOperation.swift */,
9779A1C41D34225E0071EFAB /* NetworkObserver.swift */,
970C61981D3429E400087758 /* ReachabilityCondition.swift */,
9779A1C71D34225E0071EFAB /* UIOperations.swift */,
);
name = Basic;
sourceTree = "<group>";
};
973DD4261D36E395009D45DB /* Tools */ = {
isa = PBXGroup;
children = (
@ -1106,11 +1091,11 @@
97E5712A1CA0525300FF4F1D /* Operation */ = {
isa = PBXGroup;
children = (
973DD4031D343F09009D45DB /* Basic */,
9779A1C31D34225E0071EFAB /* GlobalOperationQueue.swift */,
9779A1C51D34225E0071EFAB /* RefreshLibraryOperation.swift */,
973DD4241D344558009D45DB /* ScanLocalBookOperation.swift */,
9779A1C61D34225E0071EFAB /* SearchOperation.swift */,
9779A1C71D34225E0071EFAB /* UIOperations.swift */,
971C4F0D1D400F010027B7D2 /* UpdateWidgetDataSourceOperation.swift */,
970C61961D34243600087758 /* URLSessionDownloadTaskOperation.swift */,
);
@ -1721,7 +1706,6 @@
970E68BB1D3809A3001E8514 /* MainControllerDelegates.swift in Sources */,
973DD4211D34434C009D45DB /* SearchResult.swift in Sources */,
9779A1C91D34225E0071EFAB /* GlobalOperationQueue.swift in Sources */,
970C61991D3429E400087758 /* ReachabilityCondition.swift in Sources */,
978C589C1C1CD86E0077AE47 /* Article.swift in Sources */,
973DD41E1D34428F009D45DB /* ZimMultiReader.swift in Sources */,
);

View File

@ -1,83 +0,0 @@
/*
Copyright (C) 2015 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
This file shows how to present an alert as part of an operation.
*/
import UIKit
import PSOperations
class AlertOperation: Operation {
// MARK: Properties
private let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .Alert)
private let presentationContext: UIViewController?
var title: String? {
get {
return alertController.title
}
set {
alertController.title = newValue
name = newValue
}
}
var message: String? {
get {
return alertController.message
}
set {
alertController.message = newValue
}
}
// MARK: Initialization
init(presentationContext: UIViewController? = nil) {
self.presentationContext = presentationContext ?? UIApplication.sharedApplication().keyWindow?.rootViewController
super.init()
addCondition(AlertPresentation())
/*
This operation modifies the view controller hierarchy.
Doing this while other such operations are executing can lead to
inconsistencies in UIKit. So, let's make them mutally exclusive.
*/
addCondition(MutuallyExclusive<UIViewController>())
}
func addAction(title: String, style: UIAlertActionStyle = .Default, handler: AlertOperation -> Void = { _ in }) {
let action = UIAlertAction(title: title, style: style) { [weak self] _ in
if let strongSelf = self {
handler(strongSelf)
}
self?.finish()
}
alertController.addAction(action)
}
override func execute() {
guard let presentationContext = presentationContext else {
finish()
return
}
dispatch_async(dispatch_get_main_queue()) {
if self.alertController.actions.isEmpty {
self.addAction("OK")
}
presentationContext.presentViewController(self.alertController, animated: true, completion: nil)
}
}
}

View File

@ -1,120 +0,0 @@
/*
Copyright (C) 2015 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
Contains the code to manage the visibility of the network activity indicator
*/
import UIKit
import PSOperations
/**
An `OperationObserver` that will cause the network activity indicator to appear
as long as the `Operation` to which it is attached is executing.
*/
struct NetworkObserver: OperationObserver {
// MARK: Initilization
init() { }
func operationDidStart(operation: Operation) {
print("NetworkObserver: \(operation.name ?? "Unknown") operation did start")
dispatch_async(dispatch_get_main_queue()) {
// Increment the network indicator's "reference count"
NetworkIndicatorController.sharedIndicatorController.networkActivityDidStart()
}
}
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { }
func operationDidFinish(operation: Operation, errors: [NSError]) {
print("NetworkObserver: \(operation.name ?? "Unknown") operation did finish")
dispatch_async(dispatch_get_main_queue()) {
// Decrement the network indicator's "reference count".
NetworkIndicatorController.sharedIndicatorController.networkActivityDidEnd()
}
}
func operationDidCancel(operation: Operation) { }
}
/// A singleton to manage a visual "reference count" on the network activity indicator.
private class NetworkIndicatorController {
// MARK: Properties
static let sharedIndicatorController = NetworkIndicatorController()
private var activityCount = 0
private var visibilityTimer: Timer?
// MARK: Methods
func networkActivityDidStart() {
assert(NSThread.isMainThread(), "Altering network activity indicator state can only be done on the main thread.")
activityCount += 1
updateIndicatorVisibility()
}
func networkActivityDidEnd() {
assert(NSThread.isMainThread(), "Altering network activity indicator state can only be done on the main thread.")
activityCount -= 1
updateIndicatorVisibility()
}
private func updateIndicatorVisibility() {
if activityCount > 0 {
showIndicator()
}
else {
/*
To prevent the indicator from flickering on and off, we delay the
hiding of the indicator by one second. This provides the chance
to come in and invalidate the timer before it fires.
*/
visibilityTimer = Timer(interval: 1.0) {
self.hideIndicator()
}
}
}
private func showIndicator() {
visibilityTimer?.cancel()
visibilityTimer = nil
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
}
private func hideIndicator() {
visibilityTimer?.cancel()
visibilityTimer = nil
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
}
/// Essentially a cancellable `dispatch_after`.
class Timer {
// MARK: Properties
private var isCancelled = false
// MARK: Initialization
init(interval: NSTimeInterval, handler: dispatch_block_t) {
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC)))
dispatch_after(when, dispatch_get_main_queue()) { [weak self] in
if self?.isCancelled == false {
handler()
}
}
}
func cancel() {
isCancelled = true
}
}

View File

@ -25,7 +25,7 @@ class RefreshLibraryOperation: GroupOperation {
// 0.Download library
let url = NSURL(string: "http://www.kiwix.org/library.xml")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { [unowned parseOperation] (data, response, error) -> Void in
if let error = error {self.aggregateError(error)}
if let error = error {self.addFatalError(error)}
parseOperation.xmlData = data
}
let fetchOperation = URLSessionTaskOperation(task: task)
@ -34,7 +34,8 @@ class RefreshLibraryOperation: GroupOperation {
#if os(iOS) || os(watchOS) || os(tvOS)
fetchOperation.addObserver(NetworkObserver())
#endif
fetchOperation.addCondition(ReachabilityCondition(host: url, allowCellular: Preference.libraryRefreshAllowCellularData))
let reachibility = ReachabilityCondition(url: url, connectivity: Preference.libraryRefreshAllowCellularData ? .AnyConnectionKind : .ViaWiFi)
fetchOperation.addCondition(reachibility)
if invokedAutomatically {
addCondition(AllowAutoRefreshCondition())
@ -45,8 +46,8 @@ class RefreshLibraryOperation: GroupOperation {
parseOperation.addDependency(fetchOperation)
}
override func finished(errors: [NSError]) {
completionHandler?(errors: errors)
override func operationDidFinish(errors: [ErrorType]) {
completionHandler?(errors: [NSError]())
}
}
@ -129,22 +130,22 @@ class ParseLibraryOperation: Operation, NSXMLParserDelegate {
}
private struct AllowAutoRefreshCondition: OperationCondition {
static let name = "LibraryAllowAutoRefresh"
static let isMutuallyExclusive = false
let name = "LibraryAllowAutoRefresh"
let isMutuallyExclusive = false
init() {}
func dependencyForOperation(operation: Operation) -> NSOperation? {
private func dependencyForOperation(operation: Operation) -> NSOperation? {
return nil
}
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
private func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
let allowAutoRefresh = !Preference.libraryAutoRefreshDisabled
if allowAutoRefresh {
completion(.Satisfied)
} else {
let error = NSError(code: .ConditionFailed, userInfo: [OperationConditionKey: self.dynamicType.name])
let error = NSError(domain: "", code: 1, userInfo: nil)
completion(.Failed(error))
}
}

View File

@ -54,7 +54,7 @@ class ScanLocalBookOperation: Operation {
updateCoreData()
}
override func finished(errors: [NSError]) {
override func operationDidFinish(errors: [ErrorType]) {
context.performBlockAndWait {self.context.saveIfNeeded()}
NSManagedObjectContext.mainQueueContext.performBlockAndWait {NSManagedObjectContext.mainQueueContext.saveIfNeeded()}
NSOperationQueue.mainQueue().addOperationWithBlock {

View File

@ -41,8 +41,7 @@ class SearchOperation: GroupOperation {
addCondition(MutuallyExclusive<ZimMultiReader>())
}
override func finished(errors: [NSError]) {
//print("Search Operation finished, status \(cancelled ? "Canceled" : "Not Canceled"), \(NSDate().timeIntervalSinceDate(startTime))")
override func operationDidFinish(errors: [ErrorType]) {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.completionHandler(self.cancelled ? nil : self.results)
}
@ -58,6 +57,7 @@ private class SingleBookSearchOperation: Operation {
self.zimReader = zimReader
self.lowerCaseSearchTerm = lowerCaseSearchTerm
self.completionHandler = completionHandler
super.init()
}
override private func execute() {
@ -80,6 +80,7 @@ private class SortSearchResultsOperation: Operation {
init(completionHandler: ([SearchResult]) -> Void) {
self.completionHandler = completionHandler
super.init()
}
override private func execute() {

View File

@ -20,14 +20,15 @@ public class URLSessionDownloadTaskOperation: Operation {
self.task = downloadTask
super.init()
addObserver(BlockObserver(cancelHandler: { _ in
addObserver(DidCancelObserver { _ in
if self.produceResumeData {
downloadTask.cancelByProducingResumeData({ (data) in
})
} else {
downloadTask.cancel()
}
}))
}
)
}
public func cancel(produceResumeData produceResumeData: Bool) {

View File

@ -1,115 +0,0 @@
/*
Copyright (C) 2015 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
This file shows an example of implementing the OperationCondition protocol.
*/
import Foundation
import SystemConfiguration
import Operations
/**
This is a condition that performs a very high-level reachability check.
It does *not* perform a long-running reachability check, nor does it respond to changes in reachability.
Reachability is evaluated once when the operation to which this is attached is asked about its readiness.
*/
struct ReachabilityCondition: OperationCondition {
static let hostKey = "Host"
static let name = "Reachability"
static let isMutuallyExclusive = false
let host: NSURL
let allowCellular: Bool
init(host: NSURL, allowCellular: Bool = true) {
self.host = host
self.allowCellular = allowCellular
}
func dependencyForOperation(operation: Operation) -> NSOperation? {
return nil
}
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
ReachabilityController.requestReachability(host, allowCellular: allowCellular) { reachable in
if reachable {
completion(.Satisfied)
}
else {
let userInfo = ["title": "Network Error",
"message": "Unable connecting to the internet. Please check your connection."]
let error = NSError(code: .ConditionFailed, userInfo: userInfo)
completion(.Failed(error))
}
}
}
}
/// A private singleton that maintains a basic cache of `SCNetworkReachability` objects.
private class ReachabilityController {
static var reachabilityRefs = [String: SCNetworkReachability]()
static let reachabilityQueue = dispatch_queue_create("Operations.Reachability", DISPATCH_QUEUE_SERIAL)
static func requestReachability(url: NSURL?, allowCellular: Bool, completionHandler: (Bool) -> Void) {
if let host = url?.host {
dispatch_async(reachabilityQueue) {
var ref = self.reachabilityRefs[host]
if ref == nil {
let hostString = host as NSString
ref = SCNetworkReachabilityCreateWithName(nil, hostString.UTF8String)
}
if let ref = ref {
self.reachabilityRefs[host] = ref
let reachable = getReachibility(ref, allowCellular: allowCellular)
completionHandler(reachable)
} else {
completionHandler(false)
}
}
} else {
// Test for general internet connectibility
dispatch_async(reachabilityQueue) {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
guard let ref = withUnsafePointer(&zeroAddress, {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}) else {completionHandler(false); return}
let reachable = getReachibility(ref, allowCellular: allowCellular)
completionHandler(reachable)
}
}
}
static func getReachibility(ref: SCNetworkReachability, allowCellular: Bool) -> Bool {
var reachable = false
var flags: SCNetworkReachabilityFlags = []
if SCNetworkReachabilityGetFlags(ref, &flags) != false {
/*
Here to check forother considerations,
such as whether or not the connection would require
VPN, a cellular connection, etc.
*/
#if os(iOS) || os(watchOS) || os(tvOS)
if allowCellular {
reachable = flags.contains(.Reachable)
} else {
reachable = flags.contains(.Reachable) && !flags.contains(.IsWWAN)
}
#elseif os(OSX)
return flags.contains(.Reachable)
#endif
}
return reachable
}
}