mirror of
https://github.com/samsonjs/Advanced-NSOperations.git
synced 2026-04-27 14:57:37 +00:00
Update for Xcode 13 and Swift 4, runs but doesn't work
This commit is contained in:
parent
d60e09b136
commit
2ba16ef4e5
48 changed files with 630 additions and 601 deletions
|
|
@ -17,10 +17,10 @@
|
||||||
553F500F1B081A9D005F991E /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F500E1B081A9D005F991E /* NetworkObserver.swift */; };
|
553F500F1B081A9D005F991E /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F500E1B081A9D005F991E /* NetworkObserver.swift */; };
|
||||||
553F50111B082BCF005F991E /* BackgroundObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50101B082BCF005F991E /* BackgroundObserver.swift */; };
|
553F50111B082BCF005F991E /* BackgroundObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50101B082BCF005F991E /* BackgroundObserver.swift */; };
|
||||||
553F50161B08E98A005F991E /* LoadModelOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50151B08E98A005F991E /* LoadModelOperation.swift */; };
|
553F50161B08E98A005F991E /* LoadModelOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50151B08E98A005F991E /* LoadModelOperation.swift */; };
|
||||||
55817C3A1B18FDF8001C0CE2 /* OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551B9C061AEB2D7800302388 /* OperationQueue.swift */; };
|
55817C3A1B18FDF8001C0CE2 /* EarthquakeOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551B9C061AEB2D7800302388 /* EarthquakeOperationQueue.swift */; };
|
||||||
55817C3B1B18FDF8001C0CE2 /* ExclusivityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CD4D201AF5C05300E3A9E3 /* ExclusivityController.swift */; };
|
55817C3B1B18FDF8001C0CE2 /* ExclusivityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CD4D201AF5C05300E3A9E3 /* ExclusivityController.swift */; };
|
||||||
55817C3C1B18FDF8001C0CE2 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551B9C021AEB1CA900302388 /* Operation.swift */; };
|
|
||||||
55817C3D1B18FDF8001C0CE2 /* EarthquakeBlockOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55857B3A1AF20DE800219D5A /* EarthquakeBlockOperation.swift */; };
|
55817C3D1B18FDF8001C0CE2 /* EarthquakeBlockOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55857B3A1AF20DE800219D5A /* EarthquakeBlockOperation.swift */; };
|
||||||
|
55817C3C1B18FDF8001C0CE2 /* EarthquakeOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551B9C021AEB1CA900302388 /* EarthquakeOperation.swift */; };
|
||||||
55817C3E1B18FDF8001C0CE2 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55727FB11AF2798C00EC6916 /* GroupOperation.swift */; };
|
55817C3E1B18FDF8001C0CE2 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55727FB11AF2798C00EC6916 /* GroupOperation.swift */; };
|
||||||
55817C3F1B18FDF8001C0CE2 /* URLSessionTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55727FB91AF2849E00EC6916 /* URLSessionTaskOperation.swift */; };
|
55817C3F1B18FDF8001C0CE2 /* URLSessionTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55727FB91AF2849E00EC6916 /* URLSessionTaskOperation.swift */; };
|
||||||
55817C401B18FDF8001C0CE2 /* LocationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50031B07FB5E005F991E /* LocationOperation.swift */; };
|
55817C401B18FDF8001C0CE2 /* LocationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50031B07FB5E005F991E /* LocationOperation.swift */; };
|
||||||
|
|
@ -84,9 +84,9 @@
|
||||||
551B9BE71AEB1C9700302388 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
551B9BE71AEB1C9700302388 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
551B9BE91AEB1C9700302388 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
551B9BE91AEB1C9700302388 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||||
551B9BEC1AEB1C9700302388 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
551B9BEC1AEB1C9700302388 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||||
551B9C021AEB1CA900302388 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
|
551B9C021AEB1CA900302388 /* EarthquakeOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EarthquakeOperation.swift; sourceTree = "<group>"; };
|
||||||
551B9C041AEB1CC800302388 /* OperationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationCondition.swift; sourceTree = "<group>"; };
|
551B9C041AEB1CC800302388 /* OperationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationCondition.swift; sourceTree = "<group>"; };
|
||||||
551B9C061AEB2D7800302388 /* OperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationQueue.swift; sourceTree = "<group>"; };
|
551B9C061AEB2D7800302388 /* EarthquakeOperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EarthquakeOperationQueue.swift; sourceTree = "<group>"; };
|
||||||
551B9C0B1AEBE4F300302388 /* CloudCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudCondition.swift; sourceTree = "<group>"; };
|
551B9C0B1AEBE4F300302388 /* CloudCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudCondition.swift; sourceTree = "<group>"; };
|
||||||
551B9C0D1AEBE52800302388 /* ReachabilityCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityCondition.swift; sourceTree = "<group>"; };
|
551B9C0D1AEBE52800302388 /* ReachabilityCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityCondition.swift; sourceTree = "<group>"; };
|
||||||
551B9C101AEBE54D00302388 /* PassbookCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassbookCondition.swift; sourceTree = "<group>"; };
|
551B9C101AEBE54D00302388 /* PassbookCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassbookCondition.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -207,7 +207,7 @@
|
||||||
553F2D621AFFED5300BF4093 /* Operation Queue */ = {
|
553F2D621AFFED5300BF4093 /* Operation Queue */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
551B9C061AEB2D7800302388 /* OperationQueue.swift */,
|
551B9C061AEB2D7800302388 /* EarthquakeOperationQueue.swift */,
|
||||||
55CD4D201AF5C05300E3A9E3 /* ExclusivityController.swift */,
|
55CD4D201AF5C05300E3A9E3 /* ExclusivityController.swift */,
|
||||||
);
|
);
|
||||||
name = "Operation Queue";
|
name = "Operation Queue";
|
||||||
|
|
@ -275,8 +275,8 @@
|
||||||
55857B301AF1E59900219D5A /* Operations */ = {
|
55857B301AF1E59900219D5A /* Operations */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
551B9C021AEB1CA900302388 /* Operation.swift */,
|
|
||||||
55857B3A1AF20DE800219D5A /* EarthquakeBlockOperation.swift */,
|
55857B3A1AF20DE800219D5A /* EarthquakeBlockOperation.swift */,
|
||||||
|
551B9C021AEB1CA900302388 /* EarthquakeOperation.swift */,
|
||||||
55727FB11AF2798C00EC6916 /* GroupOperation.swift */,
|
55727FB11AF2798C00EC6916 /* GroupOperation.swift */,
|
||||||
55727FB91AF2849E00EC6916 /* URLSessionTaskOperation.swift */,
|
55727FB91AF2849E00EC6916 /* URLSessionTaskOperation.swift */,
|
||||||
553F50031B07FB5E005F991E /* LocationOperation.swift */,
|
553F50031B07FB5E005F991E /* LocationOperation.swift */,
|
||||||
|
|
@ -396,8 +396,8 @@
|
||||||
55E7021F1AFD15C80032742F /* SplitViewController.swift in Sources */,
|
55E7021F1AFD15C80032742F /* SplitViewController.swift in Sources */,
|
||||||
55817C4D1B18FDF8001C0CE2 /* PassbookCondition.swift in Sources */,
|
55817C4D1B18FDF8001C0CE2 /* PassbookCondition.swift in Sources */,
|
||||||
55817C561B18FDF8001C0CE2 /* NSOperation+Operations.swift in Sources */,
|
55817C561B18FDF8001C0CE2 /* NSOperation+Operations.swift in Sources */,
|
||||||
55817C3C1B18FDF8001C0CE2 /* Operation.swift in Sources */,
|
55817C3C1B18FDF8001C0CE2 /* EarthquakeOperation.swift in Sources */,
|
||||||
55817C3A1B18FDF8001C0CE2 /* OperationQueue.swift in Sources */,
|
55817C3A1B18FDF8001C0CE2 /* EarthquakeOperationQueue.swift in Sources */,
|
||||||
55817C4A1B18FDF8001C0CE2 /* MutuallyExclusive.swift in Sources */,
|
55817C4A1B18FDF8001C0CE2 /* MutuallyExclusive.swift in Sources */,
|
||||||
55817C531B18FDF8001C0CE2 /* UserNotificationCondition.swift in Sources */,
|
55817C531B18FDF8001C0CE2 /* UserNotificationCondition.swift in Sources */,
|
||||||
55817C461B18FDF8001C0CE2 /* OperationCondition.swift in Sources */,
|
55817C461B18FDF8001C0CE2 /* OperationCondition.swift in Sources */,
|
||||||
|
|
@ -550,6 +550,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
||||||
PRODUCT_NAME = Earthquakes;
|
PRODUCT_NAME = Earthquakes;
|
||||||
PROVISIONING_PROFILE = "";
|
PROVISIONING_PROFILE = "";
|
||||||
|
SWIFT_VERSION = 4.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
|
@ -566,6 +567,7 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
||||||
PRODUCT_NAME = Earthquakes;
|
PRODUCT_NAME = Earthquakes;
|
||||||
PROVISIONING_PROFILE = "";
|
PROVISIONING_PROFILE = "";
|
||||||
|
SWIFT_VERSION = 4.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -8,10 +8,10 @@ This file shows how to present an alert as part of an operation.
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class AlertOperation: Operation {
|
class AlertOperation: EarthquakeOperation {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
private let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .Alert)
|
private let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .alert)
|
||||||
private let presentationContext: UIViewController?
|
private let presentationContext: UIViewController?
|
||||||
|
|
||||||
var title: String? {
|
var title: String? {
|
||||||
|
|
@ -38,21 +38,21 @@ class AlertOperation: Operation {
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init(presentationContext: UIViewController? = nil) {
|
init(presentationContext: UIViewController? = nil) {
|
||||||
self.presentationContext = presentationContext ?? UIApplication.sharedApplication().keyWindow?.rootViewController
|
self.presentationContext = presentationContext ?? UIApplication.shared.keyWindow?.rootViewController
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
addCondition(AlertPresentation())
|
addCondition(condition: AlertPresentation())
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This operation modifies the view controller hierarchy.
|
This operation modifies the view controller hierarchy.
|
||||||
Doing this while other such operations are executing can lead to
|
Doing this while other such operations are executing can lead to
|
||||||
inconsistencies in UIKit. So, let's make them mutally exclusive.
|
inconsistencies in UIKit. So, let's make them mutally exclusive.
|
||||||
*/
|
*/
|
||||||
addCondition(MutuallyExclusive<UIViewController>())
|
addCondition(condition: MutuallyExclusive<UIViewController>())
|
||||||
}
|
}
|
||||||
|
|
||||||
func addAction(title: String, style: UIAlertActionStyle = .Default, handler: AlertOperation -> Void = { _ in }) {
|
func addAction(title: String, style: UIAlertActionStyle = .default, handler: @escaping (AlertOperation) -> Void = { _ in }) {
|
||||||
let action = UIAlertAction(title: title, style: style) { [weak self] _ in
|
let action = UIAlertAction(title: title, style: style) { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
handler(strongSelf)
|
handler(strongSelf)
|
||||||
|
|
@ -71,12 +71,12 @@ class AlertOperation: Operation {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
if self.alertController.actions.isEmpty {
|
if self.alertController.actions.isEmpty {
|
||||||
self.addAction("OK")
|
self.addAction(title: "OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
presentationContext.presentViewController(self.alertController, animated: true, completion: nil)
|
presentationContext.present(self.alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
// MARK: UIApplicationDelegate
|
// MARK: UIApplicationDelegate
|
||||||
|
|
||||||
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
|
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
||||||
RemoteNotificationCondition.didFailToRegister(error)
|
RemoteNotificationCondition.didFailToRegister(error: error as NSError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
|
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||||
RemoteNotificationCondition.didReceiveNotificationToken(deviceToken)
|
RemoteNotificationCondition.didReceiveNotificationToken(token: deviceToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,11 @@ class BackgroundObserver: NSObject, OperationObserver {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
// We need to know when the application moves to/from the background.
|
// We need to know when the application moves to/from the background.
|
||||||
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(BackgroundObserver.didEnterBackground(_:)), name: UIApplicationDidEnterBackgroundNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(BackgroundObserver.didEnterBackground(notification:)), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
|
||||||
|
|
||||||
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(BackgroundObserver.didEnterForeground(_:)), name: UIApplicationDidBecomeActiveNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(BackgroundObserver.didEnterForeground(notification:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
|
||||||
|
|
||||||
isInBackground = UIApplication.sharedApplication().applicationState == .Background
|
isInBackground = UIApplication.shared.applicationState == .background
|
||||||
|
|
||||||
// If we're in the background already, immediately begin the background task.
|
// If we're in the background already, immediately begin the background task.
|
||||||
if isInBackground {
|
if isInBackground {
|
||||||
|
|
@ -38,7 +38,7 @@ class BackgroundObserver: NSObject, OperationObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
NSNotificationCenter.defaultCenter().removeObserver(self)
|
NotificationCenter.default.removeObserver(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func didEnterBackground(notification: NSNotification) {
|
@objc func didEnterBackground(notification: NSNotification) {
|
||||||
|
|
@ -57,7 +57,7 @@ class BackgroundObserver: NSObject, OperationObserver {
|
||||||
|
|
||||||
private func startBackgroundTask() {
|
private func startBackgroundTask() {
|
||||||
if identifier == UIBackgroundTaskInvalid {
|
if identifier == UIBackgroundTaskInvalid {
|
||||||
identifier = UIApplication.sharedApplication().beginBackgroundTaskWithName("BackgroundObserver", expirationHandler: {
|
identifier = UIApplication.shared.beginBackgroundTask(withName: "BackgroundObserver", expirationHandler: {
|
||||||
self.endBackgroundTask()
|
self.endBackgroundTask()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -65,18 +65,18 @@ class BackgroundObserver: NSObject, OperationObserver {
|
||||||
|
|
||||||
private func endBackgroundTask() {
|
private func endBackgroundTask() {
|
||||||
if identifier != UIBackgroundTaskInvalid {
|
if identifier != UIBackgroundTaskInvalid {
|
||||||
UIApplication.sharedApplication().endBackgroundTask(identifier)
|
UIApplication.shared.endBackgroundTask(identifier)
|
||||||
identifier = UIBackgroundTaskInvalid
|
identifier = UIBackgroundTaskInvalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Operation Observer
|
// MARK: Operation Observer
|
||||||
|
|
||||||
func operationDidStart(operation: Operation) { }
|
func operationDidStart(operation: EarthquakeOperation) { }
|
||||||
|
|
||||||
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { }
|
func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) { }
|
||||||
|
|
||||||
func operationDidFinish(operation: Operation, errors: [NSError]) {
|
func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
|
||||||
endBackgroundTask()
|
endBackgroundTask()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ import Foundation
|
||||||
class DownloadEarthquakesOperation: GroupOperation {
|
class DownloadEarthquakesOperation: GroupOperation {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
let cacheFile: NSURL
|
let cacheFile: URL
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
/// - parameter cacheFile: The file `NSURL` to which the earthquake feed will be downloaded.
|
/// - parameter cacheFile: The file `URL` to which the earthquake feed will be downloaded.
|
||||||
init(cacheFile: NSURL) {
|
init(cacheFile: URL) {
|
||||||
self.cacheFile = cacheFile
|
self.cacheFile = cacheFile
|
||||||
super.init(operations: [])
|
super.init(operations: [])
|
||||||
name = "Download Earthquakes"
|
name = "Download Earthquakes"
|
||||||
|
|
@ -29,43 +29,43 @@ class DownloadEarthquakesOperation: GroupOperation {
|
||||||
or when the services you use offer secure communication options, you
|
or when the services you use offer secure communication options, you
|
||||||
should always prefer to use https.
|
should always prefer to use https.
|
||||||
*/
|
*/
|
||||||
let url = NSURL(string: "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson")!
|
let url = URL(string: "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson")!
|
||||||
let task = NSURLSession.sharedSession().downloadTaskWithURL(url) { url, response, error in
|
let task = URLSession.shared.downloadTask(with: url) { url, response, error in
|
||||||
self.downloadFinished(url, response: response as? NSHTTPURLResponse, error: error)
|
self.downloadFinished(url: url, response: response as? HTTPURLResponse, error: error as NSError?)
|
||||||
}
|
}
|
||||||
|
|
||||||
let taskOperation = URLSessionTaskOperation(task: task)
|
let taskOperation = URLSessionTaskOperation(task: task)
|
||||||
|
|
||||||
let reachabilityCondition = ReachabilityCondition(host: url)
|
let reachabilityCondition = ReachabilityCondition(host: url)
|
||||||
taskOperation.addCondition(reachabilityCondition)
|
taskOperation.addCondition(condition: reachabilityCondition)
|
||||||
|
|
||||||
let networkObserver = NetworkObserver()
|
let networkObserver = NetworkObserver()
|
||||||
taskOperation.addObserver(networkObserver)
|
taskOperation.addObserver(observer: networkObserver)
|
||||||
|
|
||||||
addOperation(taskOperation)
|
addOperation(operation: taskOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadFinished(url: NSURL?, response: NSHTTPURLResponse?, error: NSError?) {
|
func downloadFinished(url: URL?, response: HTTPURLResponse?, error: NSError?) {
|
||||||
if let localURL = url {
|
if let localURL = url {
|
||||||
do {
|
do {
|
||||||
/*
|
/*
|
||||||
If we already have a file at this location, just delete it.
|
If we already have a file at this location, just delete it.
|
||||||
Also, swallow the error, because we don't really care about it.
|
Also, swallow the error, because we don't really care about it.
|
||||||
*/
|
*/
|
||||||
try NSFileManager.defaultManager().removeItemAtURL(cacheFile)
|
try FileManager.default.removeItem(at: cacheFile)
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try NSFileManager.defaultManager().moveItemAtURL(localURL, toURL: cacheFile)
|
try FileManager.default.moveItem(at: localURL, to: cacheFile)
|
||||||
}
|
}
|
||||||
catch let error as NSError {
|
catch let error as NSError {
|
||||||
aggregateError(error)
|
aggregateError(error: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else if let error = error {
|
else if let error = error {
|
||||||
aggregateError(error)
|
aggregateError(error: error)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Do nothing, and the operation will automatically finish.
|
// Do nothing, and the operation will automatically finish.
|
||||||
|
|
|
||||||
|
|
@ -21,37 +21,37 @@ class Earthquake: NSManagedObject {
|
||||||
|
|
||||||
// MARK: Formatters
|
// MARK: Formatters
|
||||||
|
|
||||||
static let timestampFormatter: NSDateFormatter = {
|
static let timestampFormatter: DateFormatter = {
|
||||||
let timestampFormatter = NSDateFormatter()
|
let timestampFormatter = DateFormatter()
|
||||||
|
|
||||||
timestampFormatter.dateStyle = .MediumStyle
|
timestampFormatter.dateStyle = .medium
|
||||||
timestampFormatter.timeStyle = .MediumStyle
|
timestampFormatter.timeStyle = .medium
|
||||||
|
|
||||||
return timestampFormatter
|
return timestampFormatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static let magnitudeFormatter: NSNumberFormatter = {
|
static let magnitudeFormatter: NumberFormatter = {
|
||||||
let magnitudeFormatter = NSNumberFormatter()
|
let magnitudeFormatter = NumberFormatter()
|
||||||
|
|
||||||
magnitudeFormatter.numberStyle = .DecimalStyle
|
magnitudeFormatter.numberStyle = .decimal
|
||||||
magnitudeFormatter.maximumFractionDigits = 1
|
magnitudeFormatter.maximumFractionDigits = 1
|
||||||
magnitudeFormatter.minimumFractionDigits = 1
|
magnitudeFormatter.minimumFractionDigits = 1
|
||||||
|
|
||||||
return magnitudeFormatter
|
return magnitudeFormatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static let depthFormatter: NSLengthFormatter = {
|
static let depthFormatter: LengthFormatter = {
|
||||||
|
|
||||||
let depthFormatter = NSLengthFormatter()
|
let depthFormatter = LengthFormatter()
|
||||||
depthFormatter.forPersonHeightUse = false
|
depthFormatter.isForPersonHeightUse = false
|
||||||
|
|
||||||
return depthFormatter
|
return depthFormatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static let distanceFormatter: NSLengthFormatter = {
|
static let distanceFormatter: LengthFormatter = {
|
||||||
let distanceFormatter = NSLengthFormatter()
|
let distanceFormatter = LengthFormatter()
|
||||||
|
|
||||||
distanceFormatter.forPersonHeightUse = false
|
distanceFormatter.isForPersonHeightUse = false
|
||||||
distanceFormatter.numberFormatter.maximumFractionDigits = 2
|
distanceFormatter.numberFormatter.maximumFractionDigits = 2
|
||||||
|
|
||||||
return distanceFormatter
|
return distanceFormatter
|
||||||
|
|
@ -64,7 +64,7 @@ class Earthquake: NSManagedObject {
|
||||||
@NSManaged var longitude: Double
|
@NSManaged var longitude: Double
|
||||||
@NSManaged var name: String
|
@NSManaged var name: String
|
||||||
@NSManaged var magnitude: Double
|
@NSManaged var magnitude: Double
|
||||||
@NSManaged var timestamp: NSDate
|
@NSManaged var timestamp: Date
|
||||||
@NSManaged var depth: Double
|
@NSManaged var depth: Double
|
||||||
@NSManaged var webLink: String
|
@NSManaged var webLink: String
|
||||||
|
|
||||||
|
|
@ -73,6 +73,6 @@ class Earthquake: NSManagedObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
var location: CLLocation {
|
var location: CLLocation {
|
||||||
return CLLocation(coordinate: coordinate, altitude: -depth, horizontalAccuracy: kCLLocationAccuracyBest, verticalAccuracy: kCLLocationAccuracyBest, timestamp: timestamp)
|
return CLLocation(coordinate: coordinate, altitude: -depth, horizontalAccuracy: kCLLocationAccuracyBest, verticalAccuracy: kCLLocationAccuracyBest, timestamp: timestamp as Date)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ class EarthquakeTableViewCell: UITableViewCell {
|
||||||
// MARK: Configuration
|
// MARK: Configuration
|
||||||
|
|
||||||
func configure(earthquake: Earthquake) {
|
func configure(earthquake: Earthquake) {
|
||||||
timestampLabel.text = Earthquake.timestampFormatter.stringFromDate(earthquake.timestamp)
|
timestampLabel.text = Earthquake.timestampFormatter.string(for: earthquake.timestamp)
|
||||||
|
|
||||||
magnitudeLabel.text = Earthquake.magnitudeFormatter.stringFromNumber(earthquake.magnitude)
|
magnitudeLabel.text = Earthquake.magnitudeFormatter.string(for: earthquake.magnitude)
|
||||||
|
|
||||||
locationLabel.text = earthquake.name
|
locationLabel.text = earthquake.name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import MapKit
|
||||||
class EarthquakeTableViewController: UITableViewController {
|
class EarthquakeTableViewController: UITableViewController {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
var queue: OperationQueue?
|
var queue: EarthquakeOperationQueue?
|
||||||
var earthquake: Earthquake?
|
var earthquake: Earthquake?
|
||||||
var locationRequest: LocationOperation?
|
var locationRequest: LocationOperation?
|
||||||
|
|
||||||
|
|
@ -47,9 +47,9 @@ class EarthquakeTableViewController: UITableViewController {
|
||||||
map.addAnnotation(annotation)
|
map.addAnnotation(annotation)
|
||||||
|
|
||||||
nameLabel.text = earthquake.name
|
nameLabel.text = earthquake.name
|
||||||
magnitudeLabel.text = Earthquake.magnitudeFormatter.stringFromNumber(earthquake.magnitude)
|
magnitudeLabel.text = Earthquake.magnitudeFormatter.string(from: NSNumber(value: earthquake.magnitude))
|
||||||
depthLabel.text = Earthquake.depthFormatter.stringFromMeters(earthquake.depth)
|
depthLabel.text = Earthquake.depthFormatter.string(fromMeters: earthquake.depth)
|
||||||
timeLabel.text = Earthquake.timestampFormatter.stringFromDate(earthquake.timestamp)
|
timeLabel.text = Earthquake.timestampFormatter.string(from: earthquake.timestamp as Date)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We can use a `LocationOperation` to retrieve the user's current location.
|
We can use a `LocationOperation` to retrieve the user's current location.
|
||||||
|
|
@ -62,8 +62,8 @@ class EarthquakeTableViewController: UITableViewController {
|
||||||
*/
|
*/
|
||||||
let locationOperation = LocationOperation(accuracy: kCLLocationAccuracyKilometer) { location in
|
let locationOperation = LocationOperation(accuracy: kCLLocationAccuracyKilometer) { location in
|
||||||
if let earthquakeLocation = self.earthquake?.location {
|
if let earthquakeLocation = self.earthquake?.location {
|
||||||
let distance = location.distanceFromLocation(earthquakeLocation)
|
let distance = location.distance(from: earthquakeLocation)
|
||||||
self.distanceLabel.text = Earthquake.distanceFormatter.stringFromMeters(distance)
|
self.distanceLabel.text = Earthquake.distanceFormatter.string(fromMeters: distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.locationRequest = nil
|
self.locationRequest = nil
|
||||||
|
|
@ -73,7 +73,7 @@ class EarthquakeTableViewController: UITableViewController {
|
||||||
locationRequest = locationOperation
|
locationRequest = locationOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillDisappear(animated: Bool) {
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
// If the LocationOperation is still going on, then cancel it.
|
// If the LocationOperation is still going on, then cancel it.
|
||||||
locationRequest?.cancel()
|
locationRequest?.cancel()
|
||||||
|
|
@ -81,29 +81,29 @@ class EarthquakeTableViewController: UITableViewController {
|
||||||
|
|
||||||
@IBAction func shareEarthquake(sender: UIBarButtonItem) {
|
@IBAction func shareEarthquake(sender: UIBarButtonItem) {
|
||||||
guard let earthquake = earthquake else { return }
|
guard let earthquake = earthquake else { return }
|
||||||
guard let url = NSURL(string: earthquake.webLink) else { return }
|
guard let url = URL(string: earthquake.webLink) else { return }
|
||||||
|
|
||||||
let location = earthquake.location
|
let location = earthquake.location
|
||||||
|
|
||||||
let items = [url, location]
|
let items = [url, location] as [Any]
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We could present the share sheet manually, but by putting it inside
|
We could present the share sheet manually, but by putting it inside
|
||||||
an `Operation`, we can make it mutually exclusive with other operations
|
an `Operation`, we can make it mutually exclusive with other operations
|
||||||
that modify the view controller hierarchy.
|
that modify the view controller hierarchy.
|
||||||
*/
|
*/
|
||||||
let shareOperation = EarthquakeBlockOperation { (continuation: Void -> Void) in
|
let shareOperation = EarthquakeBlockOperation { (continuation: @escaping () -> Void) in
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
let shareSheet = UIActivityViewController(activityItems: items, applicationActivities: nil)
|
let shareSheet = UIActivityViewController(activityItems: items, applicationActivities: nil)
|
||||||
|
|
||||||
shareSheet.popoverPresentationController?.barButtonItem = sender
|
shareSheet.popoverPresentationController?.barButtonItem = sender
|
||||||
|
|
||||||
shareSheet.completionWithItemsHandler = { _ in
|
shareSheet.completionWithItemsHandler = { _, _, _, _ in
|
||||||
// End the operation when the share sheet completes.
|
// End the operation when the share sheet completes.
|
||||||
continuation()
|
continuation()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.presentViewController(shareSheet, animated: true, completion: nil)
|
self.present(shareSheet, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,15 +111,15 @@ class EarthquakeTableViewController: UITableViewController {
|
||||||
Indicate that this operation modifies the View Controller hierarchy
|
Indicate that this operation modifies the View Controller hierarchy
|
||||||
and is thus mutually exclusive.
|
and is thus mutually exclusive.
|
||||||
*/
|
*/
|
||||||
shareOperation.addCondition(MutuallyExclusive<UIViewController>())
|
shareOperation.addCondition(condition: MutuallyExclusive<UIViewController>())
|
||||||
|
|
||||||
queue?.addOperation(shareOperation)
|
queue?.addOperation(shareOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
if indexPath.section == 1 && indexPath.row == 0 {
|
if indexPath.section == 1 && indexPath.row == 0 {
|
||||||
// The user has tapped the "More Information" button.
|
// The user has tapped the "More Information" button.
|
||||||
if let link = earthquake?.webLink, url = NSURL(string: link) {
|
if let link = earthquake?.webLink, let url = URL(string: link) {
|
||||||
// If we have a link, present the "More Information" dialog.
|
// If we have a link, present the "More Information" dialog.
|
||||||
let moreInformation = MoreInformationOperation(URL: url)
|
let moreInformation = MoreInformationOperation(URL: url)
|
||||||
|
|
||||||
|
|
@ -134,28 +134,28 @@ class EarthquakeTableViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EarthquakeTableViewController: MKMapViewDelegate {
|
extension EarthquakeTableViewController: MKMapViewDelegate {
|
||||||
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
|
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||||
guard let earthquake = earthquake else { return nil }
|
guard let earthquake = earthquake else { return nil }
|
||||||
|
|
||||||
var view = mapView.dequeueReusableAnnotationViewWithIdentifier("pin") as? MKPinAnnotationView
|
var view = mapView.dequeueReusableAnnotationView(withIdentifier: "pin") as? MKPinAnnotationView
|
||||||
|
|
||||||
view = view ?? MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
|
view = view ?? MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
|
||||||
|
|
||||||
guard let pin = view else { return nil }
|
guard let pin = view else { return nil }
|
||||||
|
|
||||||
switch earthquake.magnitude {
|
switch earthquake.magnitude {
|
||||||
case 0..<3: pin.pinTintColor = UIColor.grayColor()
|
case 0..<3: pin.pinTintColor = UIColor.gray
|
||||||
case 3..<4: pin.pinTintColor = UIColor.blueColor()
|
case 3..<4: pin.pinTintColor = UIColor.blue
|
||||||
case 4..<5: pin.pinTintColor = UIColor.orangeColor()
|
case 4..<5: pin.pinTintColor = UIColor.orange
|
||||||
default: pin.pinTintColor = UIColor.redColor()
|
default: pin.pinTintColor = UIColor.red
|
||||||
}
|
}
|
||||||
|
|
||||||
pin.enabled = false
|
pin.isEnabled = false
|
||||||
|
|
||||||
return pin
|
return pin
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ import CloudKit
|
||||||
class EarthquakesTableViewController: UITableViewController {
|
class EarthquakesTableViewController: UITableViewController {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
var fetchedResultsController: NSFetchedResultsController?
|
var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>?
|
||||||
|
|
||||||
let operationQueue = OperationQueue()
|
let operationQueue = EarthquakeOperationQueue()
|
||||||
|
|
||||||
// MARK: View Controller
|
// MARK: View Controller
|
||||||
|
|
||||||
|
|
@ -24,14 +24,14 @@ class EarthquakesTableViewController: UITableViewController {
|
||||||
|
|
||||||
let operation = LoadModelOperation { context in
|
let operation = LoadModelOperation { context in
|
||||||
// Now that we have a context, build our `FetchedResultsController`.
|
// Now that we have a context, build our `FetchedResultsController`.
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
let request = NSFetchRequest(entityName: Earthquake.entityName)
|
let request = NSFetchRequest<NSFetchRequestResult>(entityName: Earthquake.entityName)
|
||||||
|
|
||||||
request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
|
request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
|
||||||
|
|
||||||
request.fetchLimit = 100
|
request.fetchLimit = 100
|
||||||
|
|
||||||
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
|
let controller = NSFetchedResultsController<NSFetchRequestResult>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
|
||||||
|
|
||||||
self.fetchedResultsController = controller
|
self.fetchedResultsController = controller
|
||||||
|
|
||||||
|
|
@ -42,27 +42,27 @@ class EarthquakesTableViewController: UITableViewController {
|
||||||
operationQueue.addOperation(operation)
|
operationQueue.addOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
return fetchedResultsController?.sections?.count ?? 0
|
return fetchedResultsController?.sections?.count ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
let section = fetchedResultsController?.sections?[section]
|
let section = fetchedResultsController?.sections?[section]
|
||||||
|
|
||||||
return section?.numberOfObjects ?? 0
|
return section?.numberOfObjects ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell = tableView.dequeueReusableCellWithIdentifier("earthquakeCell", forIndexPath: indexPath) as! EarthquakeTableViewCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: "earthquakeCell", for: indexPath as IndexPath) as! EarthquakeTableViewCell
|
||||||
|
|
||||||
if let earthquake = fetchedResultsController?.objectAtIndexPath(indexPath) as? Earthquake {
|
if let earthquake = fetchedResultsController?.object(at: indexPath) as? Earthquake {
|
||||||
cell.configure(earthquake)
|
cell.configure(earthquake: earthquake)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
/*
|
/*
|
||||||
Instead of performing the segue directly, we can wrap it in a
|
Instead of performing the segue directly, we can wrap it in a
|
||||||
`EarthquakeBlockOperation`. This allows us to attach conditions to the
|
`EarthquakeBlockOperation`. This allows us to attach conditions to the
|
||||||
|
|
@ -83,37 +83,37 @@ class EarthquakesTableViewController: UITableViewController {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let operation = EarthquakeBlockOperation {
|
let operation = EarthquakeBlockOperation {
|
||||||
self.performSegueWithIdentifier("showEarthquake", sender: nil)
|
self.performSegue(withIdentifier: "showEarthquake", sender: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
operation.addCondition(MutuallyExclusive<UIViewController>())
|
operation.addCondition(condition: MutuallyExclusive<UIViewController>())
|
||||||
|
|
||||||
let blockObserver = BlockObserver { _, errors in
|
let blockObserver = BlockObserver(finishHandler: { _, errors in
|
||||||
/*
|
/*
|
||||||
If the operation errored (ex: a condition failed) then the segue
|
If the operation errored (ex: a condition failed) then the segue
|
||||||
isn't going to happen. We shouldn't leave the row selected.
|
isn't going to happen. We shouldn't leave the row selected.
|
||||||
*/
|
*/
|
||||||
if !errors.isEmpty {
|
if !errors.isEmpty {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
operation.addObserver(blockObserver)
|
operation.addObserver(observer: blockObserver)
|
||||||
|
|
||||||
operationQueue.addOperation(operation)
|
operationQueue.addOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
guard let navigationVC = segue.destinationViewController as? UINavigationController,
|
guard let navigationVC = segue.destination as? UINavigationController,
|
||||||
detailVC = navigationVC.viewControllers.first as? EarthquakeTableViewController else {
|
let detailVC = navigationVC.viewControllers.first as? EarthquakeTableViewController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
detailVC.queue = operationQueue
|
detailVC.queue = operationQueue
|
||||||
|
|
||||||
if let indexPath = tableView.indexPathForSelectedRow {
|
if let indexPath = tableView.indexPathForSelectedRow {
|
||||||
detailVC.earthquake = fetchedResultsController?.objectAtIndexPath(indexPath) as? Earthquake
|
detailVC.earthquake = fetchedResultsController?.object(at: indexPath) as? Earthquake
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ class EarthquakesTableViewController: UITableViewController {
|
||||||
private func getEarthquakes(userInitiated: Bool = true) {
|
private func getEarthquakes(userInitiated: Bool = true) {
|
||||||
if let context = fetchedResultsController?.managedObjectContext {
|
if let context = fetchedResultsController?.managedObjectContext {
|
||||||
let getEarthquakesOperation = GetEarthquakesOperation(context: context) {
|
let getEarthquakesOperation = GetEarthquakesOperation(context: context) {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
self.refreshControl?.endRefreshing()
|
self.refreshControl?.endRefreshing()
|
||||||
self.updateUI()
|
self.updateUI()
|
||||||
}
|
}
|
||||||
|
|
@ -138,8 +138,7 @@ class EarthquakesTableViewController: UITableViewController {
|
||||||
We don't have a context to operate on, so wait a bit and just make
|
We don't have a context to operate on, so wait a bit and just make
|
||||||
the refresh control end.
|
the refresh control end.
|
||||||
*/
|
*/
|
||||||
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.3 * Double(NSEC_PER_SEC)))
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
|
||||||
dispatch_after(when, dispatch_get_main_queue()) {
|
|
||||||
self.refreshControl?.endRefreshing()
|
self.refreshControl?.endRefreshing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@ class GetEarthquakesOperation: GroupOperation {
|
||||||
parsing are complete. This handler will be
|
parsing are complete. This handler will be
|
||||||
invoked on an arbitrary queue.
|
invoked on an arbitrary queue.
|
||||||
*/
|
*/
|
||||||
init(context: NSManagedObjectContext, completionHandler: Void -> Void) {
|
init(context: NSManagedObjectContext, completionHandler: @escaping () -> Void) {
|
||||||
let cachesFolder = try! NSFileManager.defaultManager().URLForDirectory(.CachesDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
|
let cachesFolder = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||||
|
|
||||||
let cacheFile = cachesFolder.URLByAppendingPathComponent("earthquakes.json")
|
let cacheFile = cachesFolder.appendingPathComponent("earthquakes.json")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This operation is made of three child operations:
|
This operation is made of three child operations:
|
||||||
|
|
@ -39,7 +39,7 @@ class GetEarthquakesOperation: GroupOperation {
|
||||||
downloadOperation = DownloadEarthquakesOperation(cacheFile: cacheFile)
|
downloadOperation = DownloadEarthquakesOperation(cacheFile: cacheFile)
|
||||||
parseOperation = ParseEarthquakesOperation(cacheFile: cacheFile, context: context)
|
parseOperation = ParseEarthquakesOperation(cacheFile: cacheFile, context: context)
|
||||||
|
|
||||||
let finishOperation = NSBlockOperation(block: completionHandler)
|
let finishOperation = EarthquakeBlockOperation(block: { _ in completionHandler() })
|
||||||
|
|
||||||
// These operations must be executed in order
|
// These operations must be executed in order
|
||||||
parseOperation.addDependency(downloadOperation)
|
parseOperation.addDependency(downloadOperation)
|
||||||
|
|
@ -50,9 +50,9 @@ class GetEarthquakesOperation: GroupOperation {
|
||||||
name = "Get Earthquakes"
|
name = "Get Earthquakes"
|
||||||
}
|
}
|
||||||
|
|
||||||
override func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) {
|
override func operationDidFinish(operation: Operation, withErrors errors: [NSError]) {
|
||||||
if let firstError = errors.first where (operation === downloadOperation || operation === parseOperation) {
|
if let firstError = errors.first, (operation === downloadOperation || operation === parseOperation) {
|
||||||
produceAlert(firstError)
|
produceAlert(error: firstError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ class GetEarthquakesOperation: GroupOperation {
|
||||||
switch errorReason {
|
switch errorReason {
|
||||||
case failedReachability:
|
case failedReachability:
|
||||||
// We failed because the network isn't reachable.
|
// We failed because the network isn't reachable.
|
||||||
let hostURL = error.userInfo[ReachabilityCondition.hostKey] as! NSURL
|
let hostURL = error.userInfo[ReachabilityCondition.hostKey] as! URL
|
||||||
|
|
||||||
alert.title = "Unable to Connect"
|
alert.title = "Unable to Connect"
|
||||||
alert.message = "Cannot connect to \(hostURL.host!). Make sure your device is connected to the internet and try again."
|
alert.message = "Cannot connect to \(hostURL.host!). Make sure your device is connected to the internet and try again."
|
||||||
|
|
@ -89,7 +89,7 @@ class GetEarthquakesOperation: GroupOperation {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
produceOperation(alert)
|
produceOperation(operation: alert)
|
||||||
hasProducedAlert = true
|
hasProducedAlert = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,86 +1,111 @@
|
||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
"filename" : "29@2x-2.png",
|
"filename" : "29@2x-2.png",
|
||||||
"scale" : "2x"
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "29@3x.png",
|
"filename" : "29@3x.png",
|
||||||
"scale" : "3x"
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "40@2x-2.png",
|
"filename" : "40@2x-2.png",
|
||||||
"scale" : "2x"
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "40@3x.png",
|
"filename" : "40@3x.png",
|
||||||
"scale" : "3x"
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "60x60",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "60@2x.png",
|
"filename" : "60@2x.png",
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "60x60",
|
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "60x60"
|
||||||
|
},
|
||||||
|
{
|
||||||
"filename" : "60@3x.png",
|
"filename" : "60@3x.png",
|
||||||
"scale" : "3x"
|
"idiom" : "iphone",
|
||||||
|
"scale" : "3x",
|
||||||
|
"size" : "60x60"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "ipad",
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "20x20"
|
||||||
|
},
|
||||||
|
{
|
||||||
"filename" : "29.png",
|
"filename" : "29.png",
|
||||||
"scale" : "1x"
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "29@2x-1.png",
|
"filename" : "29@2x-1.png",
|
||||||
"scale" : "2x"
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "29x29"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "40.png",
|
"filename" : "40.png",
|
||||||
"scale" : "1x"
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "40@2x-1.png",
|
"filename" : "40@2x-1.png",
|
||||||
"scale" : "2x"
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "40x40"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "76x76",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "76.png",
|
"filename" : "76.png",
|
||||||
"scale" : "1x"
|
"idiom" : "ipad",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "76x76"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "76x76",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "76@2x.png",
|
"filename" : "76@2x.png",
|
||||||
"scale" : "2x"
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "76x76"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "83.5x83.5",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "83.5@2x.png",
|
"filename" : "83.5@2x.png",
|
||||||
"scale" : "2x"
|
"idiom" : "ipad",
|
||||||
|
"scale" : "2x",
|
||||||
|
"size" : "83.5x83.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"scale" : "1x",
|
||||||
|
"size" : "1024x1024"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,20 +12,20 @@ import CoreData
|
||||||
An `Operation` subclass that loads the Core Data stack. If this operation fails,
|
An `Operation` subclass that loads the Core Data stack. If this operation fails,
|
||||||
it will produce an `AlertOperation` that will offer to retry the operation.
|
it will produce an `AlertOperation` that will offer to retry the operation.
|
||||||
*/
|
*/
|
||||||
class LoadModelOperation: Operation {
|
class LoadModelOperation: EarthquakeOperation {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
let loadHandler: NSManagedObjectContext -> Void
|
let loadHandler: (NSManagedObjectContext) -> Void
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init(loadHandler: NSManagedObjectContext -> Void) {
|
init(loadHandler: @escaping (NSManagedObjectContext) -> Void) {
|
||||||
self.loadHandler = loadHandler
|
self.loadHandler = loadHandler
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
// We only want one of these going at a time.
|
// We only want one of these going at a time.
|
||||||
addCondition(MutuallyExclusive<LoadModelOperation>())
|
addCondition(condition: MutuallyExclusive<LoadModelOperation>())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
|
|
@ -34,9 +34,9 @@ class LoadModelOperation: Operation {
|
||||||
get the Caches directory, then your entire sandbox is broken and
|
get the Caches directory, then your entire sandbox is broken and
|
||||||
there's nothing we can possibly do to fix it.
|
there's nothing we can possibly do to fix it.
|
||||||
*/
|
*/
|
||||||
let cachesFolder = try! NSFileManager.defaultManager().URLForDirectory(.CachesDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
|
let cachesFolder = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||||
|
|
||||||
let storeURL = cachesFolder.URLByAppendingPathComponent("earthquakes.sqlite")
|
let storeURL = cachesFolder.appendingPathComponent("earthquakes.sqlite")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Force unwrap this model, because this would only fail if we haven't
|
Force unwrap this model, because this would only fail if we haven't
|
||||||
|
|
@ -44,14 +44,14 @@ class LoadModelOperation: Operation {
|
||||||
we deserve to crash. Plus, there's really no easy way to recover from
|
we deserve to crash. Plus, there's really no easy way to recover from
|
||||||
a missing model without reconstructing it programmatically
|
a missing model without reconstructing it programmatically
|
||||||
*/
|
*/
|
||||||
let model = NSManagedObjectModel.mergedModelFromBundles(nil)!
|
let model = NSManagedObjectModel.mergedModel(from: nil)!
|
||||||
|
|
||||||
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
|
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
|
||||||
|
|
||||||
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
|
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
|
||||||
context.persistentStoreCoordinator = persistentStoreCoordinator
|
context.persistentStoreCoordinator = persistentStoreCoordinator
|
||||||
|
|
||||||
var error = createStore(persistentStoreCoordinator, atURL: storeURL)
|
var error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
|
||||||
|
|
||||||
if persistentStoreCoordinator.persistentStores.isEmpty {
|
if persistentStoreCoordinator.persistentStores.isEmpty {
|
||||||
/*
|
/*
|
||||||
|
|
@ -59,14 +59,14 @@ class LoadModelOperation: Operation {
|
||||||
is why it's in the Caches folder). If we fail to add it, we can
|
is why it's in the Caches folder). If we fail to add it, we can
|
||||||
delete it and try again.
|
delete it and try again.
|
||||||
*/
|
*/
|
||||||
destroyStore(persistentStoreCoordinator, atURL: storeURL)
|
destroyStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
|
||||||
error = createStore(persistentStoreCoordinator, atURL: storeURL)
|
error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
if persistentStoreCoordinator.persistentStores.isEmpty {
|
if persistentStoreCoordinator.persistentStores.isEmpty {
|
||||||
print("Error creating SQLite store: \(error).")
|
print("Error creating SQLite store: \(String(describing: error)).")
|
||||||
print("Falling back to `.InMemory` store.")
|
print("Falling back to `.InMemory` store.")
|
||||||
error = createStore(persistentStoreCoordinator, atURL: nil, type: NSInMemoryStoreType)
|
error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: nil, type: NSInMemoryStoreType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !persistentStoreCoordinator.persistentStores.isEmpty {
|
if !persistentStoreCoordinator.persistentStores.isEmpty {
|
||||||
|
|
@ -74,13 +74,13 @@ class LoadModelOperation: Operation {
|
||||||
error = nil
|
error = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
finishWithError(error)
|
finishWithError(error: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createStore(persistentStoreCoordinator: NSPersistentStoreCoordinator, atURL URL: NSURL?, type: String = NSSQLiteStoreType) -> NSError? {
|
private func createStore(persistentStoreCoordinator: NSPersistentStoreCoordinator, atURL URL: URL?, type: String = NSSQLiteStoreType) -> NSError? {
|
||||||
var error: NSError?
|
var error: NSError?
|
||||||
do {
|
do {
|
||||||
let _ = try persistentStoreCoordinator.addPersistentStoreWithType(type, configuration: nil, URL: URL, options: nil)
|
let _ = try persistentStoreCoordinator.addPersistentStore(ofType: type, configurationName: nil, at: URL as URL?, options: nil)
|
||||||
}
|
}
|
||||||
catch let storeError as NSError {
|
catch let storeError as NSError {
|
||||||
error = storeError
|
error = storeError
|
||||||
|
|
@ -89,15 +89,15 @@ class LoadModelOperation: Operation {
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
|
||||||
private func destroyStore(persistentStoreCoordinator: NSPersistentStoreCoordinator, atURL URL: NSURL, type: String = NSSQLiteStoreType) {
|
private func destroyStore(persistentStoreCoordinator: NSPersistentStoreCoordinator, atURL URL: URL, type: String = NSSQLiteStoreType) {
|
||||||
do {
|
do {
|
||||||
let _ = try persistentStoreCoordinator.destroyPersistentStoreAtURL(URL, withType: type, options: nil)
|
let _ = try persistentStoreCoordinator.destroyPersistentStore(at: URL as URL, ofType: type, options: nil)
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
override func finished(errors: [NSError]) {
|
override func finished(errors: [NSError]) {
|
||||||
guard let firstError = errors.first where userInitiated else { return }
|
guard let firstError = errors.first, userInitiated else { return }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We failed to load the model on a user initiated operation try and present
|
We failed to load the model on a user initiated operation try and present
|
||||||
|
|
@ -111,7 +111,7 @@ class LoadModelOperation: Operation {
|
||||||
alert.message = "An error occurred while loading the database. \(firstError.localizedDescription). Please try again later."
|
alert.message = "An error occurred while loading the database. \(firstError.localizedDescription). Please try again later."
|
||||||
|
|
||||||
// No custom action for this button.
|
// No custom action for this button.
|
||||||
alert.addAction("Retry Later", style: .Cancel)
|
alert.addAction(title: "Retry Later", style: .cancel)
|
||||||
|
|
||||||
// Declare this as a local variable to avoid capturing self in the closure below.
|
// Declare this as a local variable to avoid capturing self in the closure below.
|
||||||
let handler = loadHandler
|
let handler = loadHandler
|
||||||
|
|
@ -123,14 +123,14 @@ class LoadModelOperation: Operation {
|
||||||
simply by creating a new copy of the operation and giving it the same
|
simply by creating a new copy of the operation and giving it the same
|
||||||
loadHandler.
|
loadHandler.
|
||||||
*/
|
*/
|
||||||
alert.addAction("Retry Now") { alertOperation in
|
alert.addAction(title: "Retry Now") { alertOperation in
|
||||||
let retryOperation = LoadModelOperation(loadHandler: handler)
|
let retryOperation = LoadModelOperation(loadHandler: handler)
|
||||||
|
|
||||||
retryOperation.userInitiated = true
|
retryOperation.userInitiated = true
|
||||||
|
|
||||||
alertOperation.produceOperation(retryOperation)
|
alertOperation.produceOperation(operation: retryOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
produceOperation(alert)
|
produceOperation(operation: alert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,35 +9,35 @@ This file contains the code to present more information about an earthquake as a
|
||||||
import Foundation
|
import Foundation
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
|
||||||
/// An `Operation` to display an `NSURL` in an app-modal `SFSafariViewController`.
|
/// An `Operation` to display an `URL` in an app-modal `SFSafariViewController`.
|
||||||
class MoreInformationOperation: Operation {
|
class MoreInformationOperation: EarthquakeOperation {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
let URL: NSURL
|
let URL: URL
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init(URL: NSURL) {
|
init(URL: URL) {
|
||||||
self.URL = URL
|
self.URL = URL
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
addCondition(MutuallyExclusive<UIViewController>())
|
addCondition(condition: MutuallyExclusive<UIViewController>())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Overrides
|
// MARK: Overrides
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
self.showSafariViewController()
|
self.showSafariViewController()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showSafariViewController() {
|
private func showSafariViewController() {
|
||||||
if let context = UIApplication.sharedApplication().keyWindow?.rootViewController {
|
if let context = UIApplication.shared.keyWindow?.rootViewController {
|
||||||
let safari = SFSafariViewController(URL: URL, entersReaderIfAvailable: false)
|
let safari = SFSafariViewController(url: URL, entersReaderIfAvailable: false)
|
||||||
safari.delegate = self
|
safari.delegate = self
|
||||||
context.presentViewController(safari, animated: true, completion: nil)
|
context.present(safari, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
finish()
|
finish()
|
||||||
|
|
@ -46,8 +46,8 @@ class MoreInformationOperation: Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MoreInformationOperation: SFSafariViewControllerDelegate {
|
extension MoreInformationOperation: SFSafariViewControllerDelegate {
|
||||||
func safariViewControllerDidFinish(controller: SFSafariViewController) {
|
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
||||||
controller.dismissViewControllerAnimated(true) {
|
controller.dismiss(animated: true) {
|
||||||
self.finish()
|
self.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,17 @@ struct NetworkObserver: OperationObserver {
|
||||||
|
|
||||||
init() { }
|
init() { }
|
||||||
|
|
||||||
func operationDidStart(operation: Operation) {
|
func operationDidStart(operation: EarthquakeOperation) {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
// Increment the network indicator's "reference count"
|
// Increment the network indicator's "reference count"
|
||||||
NetworkIndicatorController.sharedIndicatorController.networkActivityDidStart()
|
NetworkIndicatorController.sharedIndicatorController.networkActivityDidStart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { }
|
func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) { }
|
||||||
|
|
||||||
func operationDidFinish(operation: Operation, errors: [NSError]) {
|
func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
// Decrement the network indicator's "reference count".
|
// Decrement the network indicator's "reference count".
|
||||||
NetworkIndicatorController.sharedIndicatorController.networkActivityDidEnd()
|
NetworkIndicatorController.sharedIndicatorController.networkActivityDidEnd()
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ private class NetworkIndicatorController {
|
||||||
// MARK: Methods
|
// MARK: Methods
|
||||||
|
|
||||||
func networkActivityDidStart() {
|
func networkActivityDidStart() {
|
||||||
assert(NSThread.isMainThread(), "Altering network activity indicator state can only be done on the main thread.")
|
assert(Thread.isMainThread, "Altering network activity indicator state can only be done on the main thread.")
|
||||||
|
|
||||||
activityCount += 1
|
activityCount += 1
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ private class NetworkIndicatorController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func networkActivityDidEnd() {
|
func networkActivityDidEnd() {
|
||||||
assert(NSThread.isMainThread(), "Altering network activity indicator state can only be done on the main thread.")
|
assert(Thread.isMainThread, "Altering network activity indicator state can only be done on the main thread.")
|
||||||
|
|
||||||
activityCount -= 1
|
activityCount -= 1
|
||||||
|
|
||||||
|
|
@ -73,7 +73,7 @@ private class NetworkIndicatorController {
|
||||||
hiding of the indicator by one second. This provides the chance
|
hiding of the indicator by one second. This provides the chance
|
||||||
to come in and invalidate the timer before it fires.
|
to come in and invalidate the timer before it fires.
|
||||||
*/
|
*/
|
||||||
visibilityTimer = Timer(interval: 1.0) {
|
visibilityTimer = Timer(interval: 1) {
|
||||||
self.hideIndicator()
|
self.hideIndicator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,13 +82,13 @@ private class NetworkIndicatorController {
|
||||||
private func showIndicator() {
|
private func showIndicator() {
|
||||||
visibilityTimer?.cancel()
|
visibilityTimer?.cancel()
|
||||||
visibilityTimer = nil
|
visibilityTimer = nil
|
||||||
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
|
UIApplication.shared.isNetworkActivityIndicatorVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func hideIndicator() {
|
private func hideIndicator() {
|
||||||
visibilityTimer?.cancel()
|
visibilityTimer?.cancel()
|
||||||
visibilityTimer = nil
|
visibilityTimer = nil
|
||||||
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
|
UIApplication.shared.isNetworkActivityIndicatorVisible = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,10 +100,8 @@ class Timer {
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init(interval: NSTimeInterval, handler: dispatch_block_t) {
|
init(interval: Int, handler: @escaping () -> Void) {
|
||||||
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC)))
|
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(interval)) { [weak self] in
|
||||||
|
|
||||||
dispatch_after(when, dispatch_get_main_queue()) { [weak self] in
|
|
||||||
if self?.isCancelled == false {
|
if self?.isCancelled == false {
|
||||||
handler()
|
handler()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ import Foundation
|
||||||
struct BlockObserver: OperationObserver {
|
struct BlockObserver: OperationObserver {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
private let startHandler: (Operation -> Void)?
|
private let startHandler: ((EarthquakeOperation) -> Void)?
|
||||||
private let produceHandler: ((Operation, NSOperation) -> Void)?
|
private let produceHandler: ((EarthquakeOperation, Operation) -> Void)?
|
||||||
private let finishHandler: ((Operation, [NSError]) -> Void)?
|
private let finishHandler: ((EarthquakeOperation, [NSError]) -> Void)?
|
||||||
|
|
||||||
init(startHandler: (Operation -> Void)? = nil, produceHandler: ((Operation, NSOperation) -> Void)? = nil, finishHandler: ((Operation, [NSError]) -> Void)? = nil) {
|
init(startHandler: ((EarthquakeOperation) -> Void)? = nil, produceHandler: ((EarthquakeOperation, Operation) -> Void)? = nil, finishHandler: ((EarthquakeOperation, [NSError]) -> Void)? = nil) {
|
||||||
self.startHandler = startHandler
|
self.startHandler = startHandler
|
||||||
self.produceHandler = produceHandler
|
self.produceHandler = produceHandler
|
||||||
self.finishHandler = finishHandler
|
self.finishHandler = finishHandler
|
||||||
|
|
@ -27,15 +27,15 @@ struct BlockObserver: OperationObserver {
|
||||||
|
|
||||||
// MARK: OperationObserver
|
// MARK: OperationObserver
|
||||||
|
|
||||||
func operationDidStart(operation: Operation) {
|
func operationDidStart(operation: EarthquakeOperation) {
|
||||||
startHandler?(operation)
|
startHandler?(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) {
|
func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) {
|
||||||
produceHandler?(operation, newOperation)
|
produceHandler?(operation, newOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func operationDidFinish(operation: Operation, errors: [NSError]) {
|
func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
|
||||||
finishHandler?(operation, errors)
|
finishHandler?(operation, errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ extension CKContainer {
|
||||||
operation fails. If the verification was successful, this value will
|
operation fails. If the verification was successful, this value will
|
||||||
be `nil`.
|
be `nil`.
|
||||||
*/
|
*/
|
||||||
func verifyPermission(permission: CKApplicationPermissions, requestingIfNecessary shouldRequest: Bool = false, completion: NSError? -> Void) {
|
func verifyPermission(permission: CKApplicationPermissions, requestingIfNecessary shouldRequest: Bool = false, completion: @escaping (Error?) -> Void) {
|
||||||
verifyAccountStatus(self, permission: permission, shouldRequest: shouldRequest, completion: completion)
|
verifyAccountStatus(container: self, permission: permission, shouldRequest: shouldRequest, completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,46 +33,46 @@ extension CKContainer {
|
||||||
Make these helper functions instead of helper methods, so we don't pollute
|
Make these helper functions instead of helper methods, so we don't pollute
|
||||||
`CKContainer`.
|
`CKContainer`.
|
||||||
*/
|
*/
|
||||||
private func verifyAccountStatus(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: NSError? -> Void) {
|
private func verifyAccountStatus(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: @escaping (Error?) -> Void) {
|
||||||
container.accountStatusWithCompletionHandler { accountStatus, accountError in
|
container.accountStatus { accountStatus, accountError in
|
||||||
if accountStatus == .Available {
|
if accountStatus == .available {
|
||||||
if permission != [] {
|
if permission != [] {
|
||||||
verifyPermission(container, permission: permission, shouldRequest: shouldRequest, completion: completion)
|
verifyPermission(container: container, permission: permission, shouldRequest: shouldRequest, completion: completion)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
completion(nil)
|
completion(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let error = accountError ?? NSError(domain: CKErrorDomain, code: CKErrorCode.NotAuthenticated.rawValue, userInfo: nil)
|
let error = accountError ?? NSError(domain: CKErrorDomain, code: CKError.Code.notAuthenticated.rawValue, userInfo: nil)
|
||||||
completion(error)
|
completion(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func verifyPermission(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: NSError? -> Void) {
|
private func verifyPermission(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: @escaping (Error?) -> Void) {
|
||||||
container.statusForApplicationPermission(permission) { permissionStatus, permissionError in
|
container.status(forApplicationPermission: permission) { permissionStatus, permissionError in
|
||||||
if permissionStatus == .Granted {
|
if permissionStatus == .granted {
|
||||||
completion(nil)
|
completion(nil)
|
||||||
}
|
}
|
||||||
else if permissionStatus == .InitialState && shouldRequest {
|
else if permissionStatus == .initialState && shouldRequest {
|
||||||
requestPermission(container, permission: permission, completion: completion)
|
requestPermission(container: container, permission: permission, completion: completion)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let error = permissionError ?? NSError(domain: CKErrorDomain, code: CKErrorCode.PermissionFailure.rawValue, userInfo: nil)
|
let error = permissionError ?? NSError(domain: CKErrorDomain, code: CKError.Code.permissionFailure.rawValue, userInfo: nil)
|
||||||
completion(error)
|
completion(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestPermission(container: CKContainer, permission: CKApplicationPermissions, completion: NSError? -> Void) {
|
private func requestPermission(container: CKContainer, permission: CKApplicationPermissions, completion: @escaping (Error?) -> Void) {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
container.requestApplicationPermission(permission) { requestStatus, requestError in
|
container.requestApplicationPermission(permission) { requestStatus, requestError in
|
||||||
if requestStatus == .Granted {
|
if requestStatus == .granted {
|
||||||
completion(nil)
|
completion(nil)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let error = requestError ?? NSError(domain: CKErrorDomain, code: CKErrorCode.PermissionFailure.rawValue, userInfo: nil)
|
let error = requestError ?? NSError(domain: CKErrorDomain, code: CKError.Code.permissionFailure.rawValue, userInfo: nil)
|
||||||
completion(error)
|
completion(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,20 +21,20 @@ struct CalendarCondition: OperationCondition {
|
||||||
self.entityType = entityType
|
self.entityType = entityType
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
return CalendarPermissionOperation(entityType: entityType)
|
return CalendarPermissionOperation(entityType: entityType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||||
switch EKEventStore.authorizationStatusForEntityType(entityType) {
|
switch EKEventStore.authorizationStatus(for: entityType) {
|
||||||
case .Authorized:
|
case .authorized:
|
||||||
completion(.Satisfied)
|
completion(.Satisfied)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// We are not authorized to access entities of this type.
|
// We are not authorized to access entities of this type.
|
||||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name,
|
OperationConditionKey: type(of: self).name,
|
||||||
self.dynamicType.entityTypeKey: entityType.rawValue
|
type(of: self).entityTypeKey: entityType.rawValue,
|
||||||
])
|
])
|
||||||
|
|
||||||
completion(.Failed(error))
|
completion(.Failed(error))
|
||||||
|
|
@ -53,22 +53,22 @@ private let SharedEventStore = EKEventStore()
|
||||||
A private `Operation` that will request access to the user's Calendar/Reminders,
|
A private `Operation` that will request access to the user's Calendar/Reminders,
|
||||||
if it has not already been granted.
|
if it has not already been granted.
|
||||||
*/
|
*/
|
||||||
private class CalendarPermissionOperation: Operation {
|
private class CalendarPermissionOperation: EarthquakeOperation {
|
||||||
let entityType: EKEntityType
|
let entityType: EKEntityType
|
||||||
|
|
||||||
init(entityType: EKEntityType) {
|
init(entityType: EKEntityType) {
|
||||||
self.entityType = entityType
|
self.entityType = entityType
|
||||||
super.init()
|
super.init()
|
||||||
addCondition(AlertPresentation())
|
addCondition(condition: AlertPresentation())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
let status = EKEventStore.authorizationStatusForEntityType(entityType)
|
let status = EKEventStore.authorizationStatus(for: entityType)
|
||||||
|
|
||||||
switch status {
|
switch status {
|
||||||
case .NotDetermined:
|
case .notDetermined:
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
SharedEventStore.requestAccessToEntityType(self.entityType) { granted, error in
|
SharedEventStore.requestAccess(to: self.entityType) { granted, error in
|
||||||
self.finish()
|
self.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,16 +36,16 @@ struct CloudContainerCondition: OperationCondition {
|
||||||
self.permission = permission
|
self.permission = permission
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
return CloudKitPermissionOperation(container: container, permission: permission)
|
return CloudKitPermissionOperation(container: container, permission: permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
|
||||||
container.verifyPermission(permission, requestingIfNecessary: false) { error in
|
container.verifyPermission(permission: permission, requestingIfNecessary: false) { error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
let conditionError = NSError(code: .ConditionFailed, userInfo: [
|
let conditionError = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name,
|
OperationConditionKey: type(of: self).name,
|
||||||
self.dynamicType.containerKey: self.container,
|
type(of: self).containerKey: self.container,
|
||||||
NSUnderlyingErrorKey: error
|
NSUnderlyingErrorKey: error
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ struct CloudContainerCondition: OperationCondition {
|
||||||
This operation asks the user for permission to use CloudKit, if necessary.
|
This operation asks the user for permission to use CloudKit, if necessary.
|
||||||
If permission has already been granted, this operation will quickly finish.
|
If permission has already been granted, this operation will quickly finish.
|
||||||
*/
|
*/
|
||||||
private class CloudKitPermissionOperation: Operation {
|
private class CloudKitPermissionOperation: EarthquakeOperation {
|
||||||
let container: CKContainer
|
let container: CKContainer
|
||||||
let permission: CKApplicationPermissions
|
let permission: CKApplicationPermissions
|
||||||
|
|
||||||
|
|
@ -77,13 +77,13 @@ private class CloudKitPermissionOperation: Operation {
|
||||||
an alert, so it should not run at the same time as anything else
|
an alert, so it should not run at the same time as anything else
|
||||||
that presents an alert.
|
that presents an alert.
|
||||||
*/
|
*/
|
||||||
addCondition(AlertPresentation())
|
addCondition(condition: AlertPresentation())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
container.verifyPermission(permission, requestingIfNecessary: true) { error in
|
container.verifyPermission(permission: permission, requestingIfNecessary: true) { error in
|
||||||
self.finishWithError(error)
|
self.finishWithError(error: error as NSError?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@ import Foundation
|
||||||
If the interval is negative, or the `NSDate` is in the past, then this operation
|
If the interval is negative, or the `NSDate` is in the past, then this operation
|
||||||
immediately finishes.
|
immediately finishes.
|
||||||
*/
|
*/
|
||||||
class DelayOperation: Operation {
|
class DelayOperation: EarthquakeOperation {
|
||||||
// MARK: Types
|
// MARK: Types
|
||||||
|
|
||||||
private enum Delay {
|
private enum Delay {
|
||||||
case Interval(NSTimeInterval)
|
case Interval(Int)
|
||||||
case Date(NSDate)
|
case Date(Date)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
@ -34,18 +34,18 @@ class DelayOperation: Operation {
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init(interval: NSTimeInterval) {
|
init(interval: TimeInterval) {
|
||||||
delay = .Interval(interval)
|
delay = .Interval(Int(interval))
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(until date: NSDate) {
|
init(until date: NSDate) {
|
||||||
delay = .Date(date)
|
delay = .Date(date as Date)
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
let interval: NSTimeInterval
|
let interval: Int
|
||||||
|
|
||||||
// Figure out how long we should wait for.
|
// Figure out how long we should wait for.
|
||||||
switch delay {
|
switch delay {
|
||||||
|
|
@ -53,7 +53,7 @@ class DelayOperation: Operation {
|
||||||
interval = theInterval
|
interval = theInterval
|
||||||
|
|
||||||
case .Date(let date):
|
case .Date(let date):
|
||||||
interval = date.timeIntervalSinceNow
|
interval = Int(date.timeIntervalSinceNow)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard interval > 0 else {
|
guard interval > 0 else {
|
||||||
|
|
@ -61,10 +61,9 @@ class DelayOperation: Operation {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC)))
|
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + .seconds(interval)) {
|
||||||
dispatch_after(when, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
|
|
||||||
// If we were cancelled, then finish() has already been called.
|
// If we were cancelled, then finish() has already been called.
|
||||||
if !self.cancelled {
|
if !self.isCancelled {
|
||||||
self.finish()
|
self.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,8 @@ extension Dictionary {
|
||||||
be used as the key for the value in the `Dictionary`. If the closure
|
be used as the key for the value in the `Dictionary`. If the closure
|
||||||
returns `nil`, then the value will be omitted from the `Dictionary`.
|
returns `nil`, then the value will be omitted from the `Dictionary`.
|
||||||
*/
|
*/
|
||||||
init<Sequence: SequenceType where Sequence.Generator.Element == Value>(sequence: Sequence, @noescape keyMapper: Value -> Key?) {
|
init<S: Sequence>(sequence: S, keyMapper: (Value) -> Key?) where S.Element == Value {
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
for item in sequence {
|
for item in sequence {
|
||||||
if let key = keyMapper(item) {
|
if let key = keyMapper(item) {
|
||||||
self[key] = item
|
self[key] = item
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,13 @@ This code shows how to create a simple subclass of Operation.
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// A closure type that takes a closure as its parameter.
|
/// A closure type that takes a closure as its parameter.
|
||||||
typealias OperationBlock = (Void -> Void) -> Void
|
typealias OperationBlock = (@escaping () -> Void) -> Void
|
||||||
|
|
||||||
/// A sublcass of `Operation` to execute a closure.
|
@available(*, deprecated, message: "Use () -> Void directly, it's shorter")
|
||||||
class EarthquakeBlockOperation: Operation {
|
typealias dispatch_block_t = () -> Void
|
||||||
|
|
||||||
|
/// A sublcass of `EarthquakeOperation` to execute a closure.
|
||||||
|
class EarthquakeBlockOperation: EarthquakeOperation {
|
||||||
private let block: OperationBlock?
|
private let block: OperationBlock?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -37,9 +40,9 @@ class EarthquakeBlockOperation: Operation {
|
||||||
the designated initializer). The operation will be automatically ended
|
the designated initializer). The operation will be automatically ended
|
||||||
after the `mainQueueBlock` is executed.
|
after the `mainQueueBlock` is executed.
|
||||||
*/
|
*/
|
||||||
convenience init(mainQueueBlock: dispatch_block_t) {
|
convenience init(mainQueueBlock: @escaping () -> Void) {
|
||||||
self.init(block: { continuation in
|
self.init(block: { continuation in
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
mainQueueBlock()
|
mainQueueBlock()
|
||||||
continuation()
|
continuation()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,24 +14,21 @@ import Foundation
|
||||||
extended readiness requirements, as well as notify many interested parties
|
extended readiness requirements, as well as notify many interested parties
|
||||||
about interesting operation state changes
|
about interesting operation state changes
|
||||||
*/
|
*/
|
||||||
class Operation: NSOperation {
|
class EarthquakeOperation: Operation {
|
||||||
|
|
||||||
// use the KVO mechanism to indicate that changes to "state" affect other properties as well
|
// use the KVO mechanism to indicate that changes to "state" affect other properties as well
|
||||||
class func keyPathsForValuesAffectingIsReady() -> Set<NSObject> {
|
override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
|
||||||
|
switch key {
|
||||||
|
case "isExecuting", "isFinished", "isReady":
|
||||||
return ["state"]
|
return ["state"]
|
||||||
|
default:
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
class func keyPathsForValuesAffectingIsExecuting() -> Set<NSObject> {
|
|
||||||
return ["state"]
|
|
||||||
}
|
|
||||||
|
|
||||||
class func keyPathsForValuesAffectingIsFinished() -> Set<NSObject> {
|
|
||||||
return ["state"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: State Management
|
// MARK: State Management
|
||||||
|
|
||||||
private enum State: Int, Comparable {
|
fileprivate enum State: Int, Comparable {
|
||||||
/// The initial state of an `Operation`.
|
/// The initial state of an `Operation`.
|
||||||
case Initialized
|
case Initialized
|
||||||
|
|
||||||
|
|
@ -111,37 +108,37 @@ class Operation: NSOperation {
|
||||||
acquire the lock, then we'd be stuck waiting on our own lock. It's the
|
acquire the lock, then we'd be stuck waiting on our own lock. It's the
|
||||||
classic definition of deadlock.
|
classic definition of deadlock.
|
||||||
*/
|
*/
|
||||||
willChangeValueForKey("state")
|
willChangeValue(forKey: "state")
|
||||||
|
|
||||||
stateLock.withCriticalScope { Void -> Void in
|
stateLock.withCriticalScope { () -> Void in
|
||||||
guard _state != .Finished else {
|
guard _state != .Finished else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(_state.canTransitionToState(newState), "Performing invalid state transition.")
|
assert(_state.canTransitionToState(target: newState), "Performing invalid state transition.")
|
||||||
_state = newState
|
_state = newState
|
||||||
}
|
}
|
||||||
|
|
||||||
didChangeValueForKey("state")
|
didChangeValue(forKey: "state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here is where we extend our definition of "readiness".
|
// Here is where we extend our definition of "readiness".
|
||||||
override var ready: Bool {
|
override var isReady: Bool {
|
||||||
switch state {
|
switch state {
|
||||||
|
|
||||||
case .Initialized:
|
case .Initialized:
|
||||||
// If the operation has been cancelled, "isReady" should return true
|
// If the operation has been cancelled, "isReady" should return true
|
||||||
return cancelled
|
return isCancelled
|
||||||
|
|
||||||
case .Pending:
|
case .Pending:
|
||||||
// If the operation has been cancelled, "isReady" should return true
|
// If the operation has been cancelled, "isReady" should return true
|
||||||
guard !cancelled else {
|
guard !isCancelled else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// If super isReady, conditions can be evaluated
|
// If super isReady, conditions can be evaluated
|
||||||
if super.ready {
|
if super.isReady {
|
||||||
evaluateConditions()
|
evaluateConditions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,7 +146,7 @@ class Operation: NSOperation {
|
||||||
return false
|
return false
|
||||||
|
|
||||||
case .Ready:
|
case .Ready:
|
||||||
return super.ready || cancelled
|
return super.isReady || isCancelled
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|
@ -158,31 +155,31 @@ class Operation: NSOperation {
|
||||||
|
|
||||||
var userInitiated: Bool {
|
var userInitiated: Bool {
|
||||||
get {
|
get {
|
||||||
return qualityOfService == .UserInitiated
|
return qualityOfService == .userInitiated
|
||||||
}
|
}
|
||||||
|
|
||||||
set {
|
set {
|
||||||
assert(state < .Executing, "Cannot modify userInitiated after execution has begun.")
|
assert(state < .Executing, "Cannot modify userInitiated after execution has begun.")
|
||||||
|
|
||||||
qualityOfService = newValue ? .UserInitiated : .Default
|
qualityOfService = newValue ? .userInitiated : .default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override var executing: Bool {
|
override var isExecuting: Bool {
|
||||||
return state == .Executing
|
return state == .Executing
|
||||||
}
|
}
|
||||||
|
|
||||||
override var finished: Bool {
|
override var isFinished: Bool {
|
||||||
return state == .Finished
|
return state == .Finished
|
||||||
}
|
}
|
||||||
|
|
||||||
private func evaluateConditions() {
|
private func evaluateConditions() {
|
||||||
assert(state == .Pending && !cancelled, "evaluateConditions() was called out-of-order")
|
assert(state == .Pending && !isCancelled, "evaluateConditions() was called out-of-order")
|
||||||
|
|
||||||
state = .EvaluatingConditions
|
state = .EvaluatingConditions
|
||||||
|
|
||||||
OperationConditionEvaluator.evaluate(conditions, operation: self) { failures in
|
OperationConditionEvaluator.evaluate(conditions: conditions, operation: self) { failures in
|
||||||
self._internalErrors.appendContentsOf(failures)
|
self._internalErrors.append(contentsOf: failures)
|
||||||
self.state = .Ready
|
self.state = .Ready
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +202,7 @@ class Operation: NSOperation {
|
||||||
observers.append(observer)
|
observers.append(observer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func addDependency(operation: NSOperation) {
|
override func addDependency(_ operation: Operation) {
|
||||||
assert(state < .Executing, "Dependencies cannot be modified after execution has begun.")
|
assert(state < .Executing, "Dependencies cannot be modified after execution has begun.")
|
||||||
|
|
||||||
super.addDependency(operation)
|
super.addDependency(operation)
|
||||||
|
|
@ -218,7 +215,7 @@ class Operation: NSOperation {
|
||||||
super.start()
|
super.start()
|
||||||
|
|
||||||
// If the operation has been cancelled, we still need to enter the "Finished" state.
|
// If the operation has been cancelled, we still need to enter the "Finished" state.
|
||||||
if cancelled {
|
if isCancelled {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,11 +223,11 @@ class Operation: NSOperation {
|
||||||
override final func main() {
|
override final func main() {
|
||||||
assert(state == .Ready, "This operation must be performed on an operation queue.")
|
assert(state == .Ready, "This operation must be performed on an operation queue.")
|
||||||
|
|
||||||
if _internalErrors.isEmpty && !cancelled {
|
if _internalErrors.isEmpty && !isCancelled {
|
||||||
state = .Executing
|
state = .Executing
|
||||||
|
|
||||||
for observer in observers {
|
for observer in observers {
|
||||||
observer.operationDidStart(self)
|
observer.operationDidStart(operation: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
execute()
|
execute()
|
||||||
|
|
@ -251,7 +248,7 @@ class Operation: NSOperation {
|
||||||
their readiness state.
|
their readiness state.
|
||||||
*/
|
*/
|
||||||
func execute() {
|
func execute() {
|
||||||
print("\(self.dynamicType) must override `execute()`.")
|
print("\(type(of: self)) must override `execute()`.")
|
||||||
|
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
@ -265,9 +262,9 @@ class Operation: NSOperation {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
final func produceOperation(operation: NSOperation) {
|
final func produceOperation(operation: Operation) {
|
||||||
for observer in observers {
|
for observer in observers {
|
||||||
observer.operation(self, didProduceOperation: operation)
|
observer.operation(operation: self, didProduceOperation: operation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,12 +275,12 @@ class Operation: NSOperation {
|
||||||
This is a convenience method to simplify calling the actual `finish()`
|
This is a convenience method to simplify calling the actual `finish()`
|
||||||
method. This is also useful if you wish to finish with an error provided
|
method. This is also useful if you wish to finish with an error provided
|
||||||
by the system frameworks. As an example, see `DownloadEarthquakesOperation`
|
by the system frameworks. As an example, see `DownloadEarthquakesOperation`
|
||||||
for how an error from an `NSURLSession` is passed along via the
|
for how an error from an `URLSession` is passed along via the
|
||||||
`finishWithError()` method.
|
`finishWithError()` method.
|
||||||
*/
|
*/
|
||||||
final func finishWithError(error: NSError?) {
|
final func finishWithError(error: NSError?) {
|
||||||
if let error = error {
|
if let error = error {
|
||||||
finish([error])
|
finish(errors: [error])
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
finish()
|
finish()
|
||||||
|
|
@ -301,10 +298,10 @@ class Operation: NSOperation {
|
||||||
state = .Finishing
|
state = .Finishing
|
||||||
|
|
||||||
let combinedErrors = _internalErrors + errors
|
let combinedErrors = _internalErrors + errors
|
||||||
finished(combinedErrors)
|
finished(errors: combinedErrors)
|
||||||
|
|
||||||
for observer in observers {
|
for observer in observers {
|
||||||
observer.operationDidFinish(self, errors: combinedErrors)
|
observer.operationDidFinish(operation: self, errors: combinedErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
state = .Finished
|
state = .Finished
|
||||||
|
|
@ -339,10 +336,10 @@ class Operation: NSOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple operator functions to simplify the assertions used above.
|
// Simple operator functions to simplify the assertions used above.
|
||||||
private func <(lhs: Operation.State, rhs: Operation.State) -> Bool {
|
private func <(lhs: EarthquakeOperation.State, rhs: EarthquakeOperation.State) -> Bool {
|
||||||
return lhs.rawValue < rhs.rawValue
|
return lhs.rawValue < rhs.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
private func ==(lhs: Operation.State, rhs: Operation.State) -> Bool {
|
private func ==(lhs: EarthquakeOperation.State, rhs: EarthquakeOperation.State) -> Bool {
|
||||||
return lhs.rawValue == rhs.rawValue
|
return lhs.rawValue == rhs.rawValue
|
||||||
}
|
}
|
||||||
|
|
@ -18,9 +18,9 @@ import Foundation
|
||||||
For example, `GroupOperation` is the delegate of its own internal
|
For example, `GroupOperation` is the delegate of its own internal
|
||||||
`OperationQueue` and uses it to manage dependencies.
|
`OperationQueue` and uses it to manage dependencies.
|
||||||
*/
|
*/
|
||||||
@objc protocol OperationQueueDelegate: NSObjectProtocol {
|
@objc protocol EarthquakeOperationQueueDelegate: NSObjectProtocol {
|
||||||
optional func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation)
|
@objc optional func operationQueue(operationQueue: EarthquakeOperationQueue, willAddOperation operation: Operation)
|
||||||
optional func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError])
|
@objc optional func operationQueue(operationQueue: EarthquakeOperationQueue, operationDidFinish operation: Operation, withErrors errors: [NSError])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -31,11 +31,11 @@ import Foundation
|
||||||
- Extracting generated dependencies from operation conditions
|
- Extracting generated dependencies from operation conditions
|
||||||
- Setting up dependencies to enforce mutual exclusivity
|
- Setting up dependencies to enforce mutual exclusivity
|
||||||
*/
|
*/
|
||||||
class OperationQueue: NSOperationQueue {
|
class EarthquakeOperationQueue: OperationQueue {
|
||||||
weak var delegate: OperationQueueDelegate?
|
weak var delegate: EarthquakeOperationQueueDelegate?
|
||||||
|
|
||||||
override func addOperation(operation: NSOperation) {
|
override func addOperation(_ operation: Operation) {
|
||||||
if let op = operation as? Operation {
|
if let op = operation as? EarthquakeOperation {
|
||||||
// Set up a `BlockObserver` to invoke the `OperationQueueDelegate` method.
|
// Set up a `BlockObserver` to invoke the `OperationQueueDelegate` method.
|
||||||
let delegate = BlockObserver(
|
let delegate = BlockObserver(
|
||||||
startHandler: nil,
|
startHandler: nil,
|
||||||
|
|
@ -44,15 +44,15 @@ class OperationQueue: NSOperationQueue {
|
||||||
},
|
},
|
||||||
finishHandler: { [weak self] in
|
finishHandler: { [weak self] in
|
||||||
if let q = self {
|
if let q = self {
|
||||||
q.delegate?.operationQueue?(q, operationDidFinish: $0, withErrors: $1)
|
q.delegate?.operationQueue?(operationQueue: q, operationDidFinish: $0, withErrors: $1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
op.addObserver(delegate)
|
op.addObserver(observer: delegate)
|
||||||
|
|
||||||
// Extract any dependencies needed by this operation.
|
// Extract any dependencies needed by this operation.
|
||||||
let dependencies = op.conditions.flatMap {
|
let dependencies = op.conditions.compactMap {
|
||||||
$0.dependencyForOperation(op)
|
$0.dependencyForOperation(operation: op)
|
||||||
}
|
}
|
||||||
|
|
||||||
for dependency in dependencies {
|
for dependency in dependencies {
|
||||||
|
|
@ -65,21 +65,21 @@ class OperationQueue: NSOperationQueue {
|
||||||
With condition dependencies added, we can now see if this needs
|
With condition dependencies added, we can now see if this needs
|
||||||
dependencies to enforce mutual exclusivity.
|
dependencies to enforce mutual exclusivity.
|
||||||
*/
|
*/
|
||||||
let concurrencyCategories: [String] = op.conditions.flatMap { condition in
|
let concurrencyCategories: [String] = op.conditions.compactMap { condition in
|
||||||
if !condition.dynamicType.isMutuallyExclusive { return nil }
|
if !type(of: condition).isMutuallyExclusive { return nil }
|
||||||
|
|
||||||
return "\(condition.dynamicType)"
|
return "\(type(of: condition))"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !concurrencyCategories.isEmpty {
|
if !concurrencyCategories.isEmpty {
|
||||||
// Set up the mutual exclusivity dependencies.
|
// Set up the mutual exclusivity dependencies.
|
||||||
let exclusivityController = ExclusivityController.sharedExclusivityController
|
let exclusivityController = ExclusivityController.sharedExclusivityController
|
||||||
|
|
||||||
exclusivityController.addOperation(op, categories: concurrencyCategories)
|
exclusivityController.addOperation(operation: op, categories: concurrencyCategories)
|
||||||
|
|
||||||
op.addObserver(BlockObserver { operation, _ in
|
op.addObserver(observer: BlockObserver(finishHandler: { operation, _ in
|
||||||
exclusivityController.removeOperation(operation, categories: concurrencyCategories)
|
exclusivityController.removeOperation(operation: operation, categories: concurrencyCategories)
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -98,15 +98,15 @@ class OperationQueue: NSOperationQueue {
|
||||||
*/
|
*/
|
||||||
operation.addCompletionBlock { [weak self, weak operation] in
|
operation.addCompletionBlock { [weak self, weak operation] in
|
||||||
guard let queue = self, let operation = operation else { return }
|
guard let queue = self, let operation = operation else { return }
|
||||||
queue.delegate?.operationQueue?(queue, operationDidFinish: operation, withErrors: [])
|
queue.delegate?.operationQueue?(operationQueue: queue, operationDidFinish: operation, withErrors: [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate?.operationQueue?(self, willAddOperation: operation)
|
delegate?.operationQueue?(operationQueue: self, willAddOperation: operation)
|
||||||
super.addOperation(operation)
|
super.addOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func addOperations(operations: [NSOperation], waitUntilFinished wait: Bool) {
|
override func addOperations(_ operations: [Operation], waitUntilFinished wait: Bool) {
|
||||||
/*
|
/*
|
||||||
The base implementation of this method does not call `addOperation()`,
|
The base implementation of this method does not call `addOperation()`,
|
||||||
so we'll call it ourselves.
|
so we'll call it ourselves.
|
||||||
|
|
@ -17,8 +17,8 @@ import Foundation
|
||||||
class ExclusivityController {
|
class ExclusivityController {
|
||||||
static let sharedExclusivityController = ExclusivityController()
|
static let sharedExclusivityController = ExclusivityController()
|
||||||
|
|
||||||
private let serialQueue = dispatch_queue_create("Operations.ExclusivityController", DISPATCH_QUEUE_SERIAL)
|
private let serialQueue = DispatchQueue(label: "Operations.ExclusivityController")
|
||||||
private var operations: [String: [Operation]] = [:]
|
private var operations: [String: [EarthquakeOperation]] = [:]
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
/*
|
/*
|
||||||
|
|
@ -28,24 +28,24 @@ class ExclusivityController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers an operation as being mutually exclusive
|
/// Registers an operation as being mutually exclusive
|
||||||
func addOperation(operation: Operation, categories: [String]) {
|
func addOperation(operation: EarthquakeOperation, categories: [String]) {
|
||||||
/*
|
/*
|
||||||
This needs to be a synchronous operation.
|
This needs to be a synchronous operation.
|
||||||
If this were async, then we might not get around to adding dependencies
|
If this were async, then we might not get around to adding dependencies
|
||||||
until after the operation had already begun, which would be incorrect.
|
until after the operation had already begun, which would be incorrect.
|
||||||
*/
|
*/
|
||||||
dispatch_sync(serialQueue) {
|
serialQueue.sync {
|
||||||
for category in categories {
|
for category in categories {
|
||||||
self.noqueue_addOperation(operation, category: category)
|
self.noqueue_addOperation(operation: operation, category: category)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unregisters an operation from being mutually exclusive.
|
/// Unregisters an operation from being mutually exclusive.
|
||||||
func removeOperation(operation: Operation, categories: [String]) {
|
func removeOperation(operation: EarthquakeOperation, categories: [String]) {
|
||||||
dispatch_async(serialQueue) {
|
serialQueue.async {
|
||||||
for category in categories {
|
for category in categories {
|
||||||
self.noqueue_removeOperation(operation, category: category)
|
self.noqueue_removeOperation(operation: operation, category: category)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +53,7 @@ class ExclusivityController {
|
||||||
|
|
||||||
// MARK: Operation Management
|
// MARK: Operation Management
|
||||||
|
|
||||||
private func noqueue_addOperation(operation: Operation, category: String) {
|
private func noqueue_addOperation(operation: EarthquakeOperation, category: String) {
|
||||||
var operationsWithThisCategory = operations[category] ?? []
|
var operationsWithThisCategory = operations[category] ?? []
|
||||||
|
|
||||||
if let last = operationsWithThisCategory.last {
|
if let last = operationsWithThisCategory.last {
|
||||||
|
|
@ -65,13 +65,13 @@ class ExclusivityController {
|
||||||
operations[category] = operationsWithThisCategory
|
operations[category] = operationsWithThisCategory
|
||||||
}
|
}
|
||||||
|
|
||||||
private func noqueue_removeOperation(operation: Operation, category: String) {
|
private func noqueue_removeOperation(operation: EarthquakeOperation, category: String) {
|
||||||
let matchingOperations = operations[category]
|
let matchingOperations = operations[category]
|
||||||
|
|
||||||
if var operationsWithThisCategory = matchingOperations,
|
if var operationsWithThisCategory = matchingOperations,
|
||||||
let index = operationsWithThisCategory.indexOf(operation) {
|
let index = operationsWithThisCategory.firstIndex(of: operation) {
|
||||||
|
|
||||||
operationsWithThisCategory.removeAtIndex(index)
|
operationsWithThisCategory.remove(at: index)
|
||||||
operations[category] = operationsWithThisCategory
|
operations[category] = operationsWithThisCategory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,21 @@ import Foundation
|
||||||
subsequent operations (still within the outer `GroupOperation`) that will all
|
subsequent operations (still within the outer `GroupOperation`) that will all
|
||||||
be executed before the rest of the operations in the initial chain of operations.
|
be executed before the rest of the operations in the initial chain of operations.
|
||||||
*/
|
*/
|
||||||
class GroupOperation: Operation {
|
class GroupOperation: EarthquakeOperation {
|
||||||
private let internalQueue = OperationQueue()
|
private let internalQueue = EarthquakeOperationQueue()
|
||||||
private let startingOperation = NSBlockOperation(block: {})
|
private let startingOperation = BlockOperation(block: {})
|
||||||
private let finishingOperation = NSBlockOperation(block: {})
|
private let finishingOperation = BlockOperation(block: {})
|
||||||
|
|
||||||
private var aggregatedErrors = [NSError]()
|
private var aggregatedErrors = [NSError]()
|
||||||
|
|
||||||
convenience init(operations: NSOperation...) {
|
convenience init(operations: Operation...) {
|
||||||
self.init(operations: operations)
|
self.init(operations: operations)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(operations: [NSOperation]) {
|
init(operations: [Operation]) {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
internalQueue.suspended = true
|
internalQueue.isSuspended = true
|
||||||
internalQueue.delegate = self
|
internalQueue.delegate = self
|
||||||
internalQueue.addOperation(startingOperation)
|
internalQueue.addOperation(startingOperation)
|
||||||
|
|
||||||
|
|
@ -50,11 +50,11 @@ class GroupOperation: Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
internalQueue.suspended = false
|
internalQueue.isSuspended = false
|
||||||
internalQueue.addOperation(finishingOperation)
|
internalQueue.addOperation(finishingOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addOperation(operation: NSOperation) {
|
func addOperation(operation: Operation) {
|
||||||
internalQueue.addOperation(operation)
|
internalQueue.addOperation(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,14 +67,14 @@ class GroupOperation: Operation {
|
||||||
aggregatedErrors.append(error)
|
aggregatedErrors.append(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) {
|
func operationDidFinish(operation: Operation, withErrors errors: [NSError]) {
|
||||||
// For use by subclassers.
|
// For use by subclassers.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension GroupOperation: OperationQueueDelegate {
|
extension GroupOperation: EarthquakeOperationQueueDelegate {
|
||||||
final func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation) {
|
final func operationQueue(operationQueue: EarthquakeOperationQueue, willAddOperation operation: Operation) {
|
||||||
assert(!finishingOperation.finished && !finishingOperation.executing, "cannot add new operations to a group after the group has completed")
|
assert(!finishingOperation.isFinished && !finishingOperation.isExecuting, "cannot add new operations to a group after the group has completed")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Some operation in this group has produced a new operation to execute.
|
Some operation in this group has produced a new operation to execute.
|
||||||
|
|
@ -97,15 +97,15 @@ extension GroupOperation: OperationQueueDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError]) {
|
final func operationQueue(operationQueue: EarthquakeOperationQueue, operationDidFinish operation: Operation, withErrors errors: [NSError]) {
|
||||||
aggregatedErrors.appendContentsOf(errors)
|
aggregatedErrors.append(contentsOf: errors)
|
||||||
|
|
||||||
if operation === finishingOperation {
|
if operation === finishingOperation {
|
||||||
internalQueue.suspended = true
|
internalQueue.isSuspended = true
|
||||||
finish(aggregatedErrors)
|
finish(errors: aggregatedErrors)
|
||||||
}
|
}
|
||||||
else if operation !== startingOperation {
|
else if operation !== startingOperation {
|
||||||
operationDidFinish(operation, withErrors: errors)
|
operationDidFinish(operation: operation, withErrors: errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ struct HealthCondition: OperationCondition {
|
||||||
readTypes = typesToRead
|
readTypes = typesToRead
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
guard HKHealthStore.isHealthDataAvailable() else {
|
guard HKHealthStore.isHealthDataAvailable() else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -50,9 +50,9 @@ struct HealthCondition: OperationCondition {
|
||||||
return HealthPermissionOperation(shareTypes: shareTypes, readTypes: readTypes)
|
return HealthPermissionOperation(shareTypes: shareTypes, readTypes: readTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||||
guard HKHealthStore.isHealthDataAvailable() else {
|
guard HKHealthStore.isHealthDataAvailable() else {
|
||||||
failed(shareTypes, completion: completion)
|
failed(unauthorizedShareTypes: shareTypes, completion: completion)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,11 +68,11 @@ struct HealthCondition: OperationCondition {
|
||||||
write data to HealthKit.
|
write data to HealthKit.
|
||||||
*/
|
*/
|
||||||
let unauthorizedShareTypes = shareTypes.filter { shareType in
|
let unauthorizedShareTypes = shareTypes.filter { shareType in
|
||||||
return store.authorizationStatusForType(shareType) != .SharingAuthorized
|
return store.authorizationStatus(for: shareType) != .sharingAuthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
if !unauthorizedShareTypes.isEmpty {
|
if !unauthorizedShareTypes.isEmpty {
|
||||||
failed(Set(unauthorizedShareTypes), completion: completion)
|
failed(unauthorizedShareTypes: Set(unauthorizedShareTypes), completion: completion)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
completion(.Satisfied)
|
completion(.Satisfied)
|
||||||
|
|
@ -80,11 +80,11 @@ struct HealthCondition: OperationCondition {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Break this out in to its own method so we don't clutter up the evaluate... method.
|
// Break this out in to its own method so we don't clutter up the evaluate... method.
|
||||||
private func failed(unauthorizedShareTypes: Set<HKSampleType>, completion: OperationConditionResult -> Void) {
|
private func failed(unauthorizedShareTypes: Set<HKSampleType>, completion: (OperationConditionResult) -> Void) {
|
||||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name,
|
OperationConditionKey: type(of: self).name,
|
||||||
self.dynamicType.healthDataAvailable: HKHealthStore.isHealthDataAvailable(),
|
type(of: self).healthDataAvailable: HKHealthStore.isHealthDataAvailable(),
|
||||||
self.dynamicType.unauthorizedShareTypesKey: unauthorizedShareTypes
|
type(of: self).unauthorizedShareTypesKey: unauthorizedShareTypes
|
||||||
])
|
])
|
||||||
|
|
||||||
completion(.Failed(error))
|
completion(.Failed(error))
|
||||||
|
|
@ -95,7 +95,7 @@ struct HealthCondition: OperationCondition {
|
||||||
A private `Operation` that will request access to the user's health data, if
|
A private `Operation` that will request access to the user's health data, if
|
||||||
it has not already been granted.
|
it has not already been granted.
|
||||||
*/
|
*/
|
||||||
private class HealthPermissionOperation: Operation {
|
private class HealthPermissionOperation: EarthquakeOperation {
|
||||||
let shareTypes: Set<HKSampleType>
|
let shareTypes: Set<HKSampleType>
|
||||||
let readTypes: Set<HKSampleType>
|
let readTypes: Set<HKSampleType>
|
||||||
|
|
||||||
|
|
@ -105,20 +105,20 @@ private class HealthPermissionOperation: Operation {
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
addCondition(MutuallyExclusive<HealthPermissionOperation>())
|
addCondition(condition: MutuallyExclusive<HealthPermissionOperation>())
|
||||||
addCondition(MutuallyExclusive<UIViewController>())
|
addCondition(condition: MutuallyExclusive<UIViewController>())
|
||||||
addCondition(AlertPresentation())
|
addCondition(condition: AlertPresentation())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
let store = HKHealthStore()
|
let store = HKHealthStore()
|
||||||
/*
|
/*
|
||||||
This method is smart enough to not re-prompt for access if access
|
This method is smart enough to not re-prompt for access if access
|
||||||
has already been granted.
|
has already been granted.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
store.requestAuthorizationToShareTypes(self.shareTypes, readTypes: self.readTypes) { completed, error in
|
store.requestAuthorization(toShare: self.shareTypes, read: self.readTypes) { completed, error in
|
||||||
self.finish()
|
self.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,11 @@ struct LocationCondition: OperationCondition {
|
||||||
self.usage = usage
|
self.usage = usage
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
return LocationPermissionOperation(usage: usage)
|
return LocationPermissionOperation(usage: usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||||
let enabled = CLLocationManager.locationServicesEnabled()
|
let enabled = CLLocationManager.locationServicesEnabled()
|
||||||
let actual = CLLocationManager.authorizationStatus()
|
let actual = CLLocationManager.authorizationStatus()
|
||||||
|
|
||||||
|
|
@ -42,11 +42,11 @@ struct LocationCondition: OperationCondition {
|
||||||
|
|
||||||
// There are several factors to consider when evaluating this condition
|
// There are several factors to consider when evaluating this condition
|
||||||
switch (enabled, usage, actual) {
|
switch (enabled, usage, actual) {
|
||||||
case (true, _, .AuthorizedAlways):
|
case (true, _, .authorizedAlways):
|
||||||
// The service is enabled, and we have "Always" permission -> condition satisfied.
|
// The service is enabled, and we have "Always" permission -> condition satisfied.
|
||||||
break
|
break
|
||||||
|
|
||||||
case (true, .WhenInUse, .AuthorizedWhenInUse):
|
case (true, .WhenInUse, .authorizedWhenInUse):
|
||||||
/*
|
/*
|
||||||
The service is enabled, and we have and need "WhenInUse"
|
The service is enabled, and we have and need "WhenInUse"
|
||||||
permission -> condition satisfied.
|
permission -> condition satisfied.
|
||||||
|
|
@ -63,9 +63,9 @@ struct LocationCondition: OperationCondition {
|
||||||
The last case would happen if this condition were wrapped in a `SilentCondition`.
|
The last case would happen if this condition were wrapped in a `SilentCondition`.
|
||||||
*/
|
*/
|
||||||
error = NSError(code: .ConditionFailed, userInfo: [
|
error = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name,
|
OperationConditionKey: type(of: self).name,
|
||||||
self.dynamicType.locationServicesEnabledKey: enabled,
|
type(of: self).locationServicesEnabledKey: enabled,
|
||||||
self.dynamicType.authorizationStatusKey: Int(actual.rawValue)
|
type(of: self).authorizationStatusKey: Int(actual.rawValue)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ struct LocationCondition: OperationCondition {
|
||||||
A private `Operation` that will request permission to access the user's location,
|
A private `Operation` that will request permission to access the user's location,
|
||||||
if permission has not already been granted.
|
if permission has not already been granted.
|
||||||
*/
|
*/
|
||||||
private class LocationPermissionOperation: Operation {
|
private class LocationPermissionOperation: EarthquakeOperation {
|
||||||
let usage: LocationCondition.Usage
|
let usage: LocationCondition.Usage
|
||||||
var manager: CLLocationManager?
|
var manager: CLLocationManager?
|
||||||
|
|
||||||
|
|
@ -93,7 +93,7 @@ private class LocationPermissionOperation: Operation {
|
||||||
This is an operation that potentially presents an alert so it should
|
This is an operation that potentially presents an alert so it should
|
||||||
be mutually exclusive with anything else that presents an alert.
|
be mutually exclusive with anything else that presents an alert.
|
||||||
*/
|
*/
|
||||||
addCondition(AlertPresentation())
|
addCondition(condition: AlertPresentation())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
|
|
@ -102,8 +102,8 @@ private class LocationPermissionOperation: Operation {
|
||||||
need to handle the "upgrade" (.WhenInUse -> .Always) case.
|
need to handle the "upgrade" (.WhenInUse -> .Always) case.
|
||||||
*/
|
*/
|
||||||
switch (CLLocationManager.authorizationStatus(), usage) {
|
switch (CLLocationManager.authorizationStatus(), usage) {
|
||||||
case (.NotDetermined, _), (.AuthorizedWhenInUse, .Always):
|
case (.notDetermined, _), (.authorizedWhenInUse, .Always):
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
self.requestPermission()
|
self.requestPermission()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,14 +129,15 @@ private class LocationPermissionOperation: Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is helpful when developing the app.
|
// This is helpful when developing the app.
|
||||||
assert(NSBundle.mainBundle().objectForInfoDictionaryKey(key) != nil, "Requesting location permission requires the \(key) key in your Info.plist")
|
assert(Bundle.main.object(forInfoDictionaryKey: key) != nil, "Requesting location permission requires the \(key) key in your Info.plist")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LocationPermissionOperation: CLLocationManagerDelegate {
|
extension LocationPermissionOperation: CLLocationManagerDelegate {
|
||||||
@objc func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
|
@available(iOS 14.0, *)
|
||||||
if manager == self.manager && executing && status != .NotDetermined {
|
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
||||||
|
if manager == self.manager && isExecuting && manager.authorizationStatus != .notDetermined {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,25 +15,25 @@ import CoreLocation
|
||||||
prompt for `WhenInUse` location authorization, if the app does not already
|
prompt for `WhenInUse` location authorization, if the app does not already
|
||||||
have it.
|
have it.
|
||||||
*/
|
*/
|
||||||
class LocationOperation: Operation, CLLocationManagerDelegate {
|
class LocationOperation: EarthquakeOperation, CLLocationManagerDelegate {
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
private let accuracy: CLLocationAccuracy
|
private let accuracy: CLLocationAccuracy
|
||||||
private var manager: CLLocationManager?
|
private var manager: CLLocationManager?
|
||||||
private let handler: CLLocation -> Void
|
private let handler: (CLLocation) -> Void
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init(accuracy: CLLocationAccuracy, locationHandler: CLLocation -> Void) {
|
init(accuracy: CLLocationAccuracy, locationHandler: @escaping (CLLocation) -> Void) {
|
||||||
self.accuracy = accuracy
|
self.accuracy = accuracy
|
||||||
self.handler = locationHandler
|
self.handler = locationHandler
|
||||||
super.init()
|
super.init()
|
||||||
addCondition(LocationCondition(usage: .WhenInUse))
|
addCondition(condition: LocationCondition(usage: .WhenInUse))
|
||||||
addCondition(MutuallyExclusive<CLLocationManager>())
|
addCondition(condition: MutuallyExclusive<CLLocationManager>())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
/*
|
/*
|
||||||
`CLLocationManager` needs to be created on a thread with an active
|
`CLLocationManager` needs to be created on a thread with an active
|
||||||
run loop, so for simplicity we do this on the main queue.
|
run loop, so for simplicity we do this on the main queue.
|
||||||
|
|
@ -48,7 +48,7 @@ class LocationOperation: Operation, CLLocationManagerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func cancel() {
|
override func cancel() {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
self.stopLocationUpdates()
|
self.stopLocationUpdates()
|
||||||
super.cancel()
|
super.cancel()
|
||||||
}
|
}
|
||||||
|
|
@ -61,8 +61,8 @@ class LocationOperation: Operation, CLLocationManagerDelegate {
|
||||||
|
|
||||||
// MARK: CLLocationManagerDelegate
|
// MARK: CLLocationManagerDelegate
|
||||||
|
|
||||||
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||||
guard let location = locations.last where location.horizontalAccuracy <= accuracy else {
|
guard let location = locations.last, location.horizontalAccuracy <= accuracy else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,8 +71,8 @@ class LocationOperation: Operation, CLLocationManagerDelegate {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
|
private func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
|
||||||
stopLocationUpdates()
|
stopLocationUpdates()
|
||||||
finishWithError(error)
|
finishWithError(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,11 @@ struct MutuallyExclusive<T>: OperationCondition {
|
||||||
|
|
||||||
init() { }
|
init() { }
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||||
completion(.Satisfied)
|
completion(.Satisfied)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension NSLock {
|
extension NSLock {
|
||||||
func withCriticalScope<T>(@noescape block: Void -> T) -> T {
|
func withCriticalScope<T>(block: () -> T) -> T {
|
||||||
lock()
|
lock()
|
||||||
let value = block()
|
let value = block()
|
||||||
unlock()
|
unlock()
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@ A convenient extension to Foundation.NSOperation.
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension NSOperation {
|
extension Operation {
|
||||||
/**
|
/**
|
||||||
Add a completion block to be executed after the `NSOperation` enters the
|
Add a completion block to be executed after the `NSOperation` enters the
|
||||||
"finished" state.
|
"finished" state.
|
||||||
*/
|
*/
|
||||||
func addCompletionBlock(block: Void -> Void) {
|
func addCompletionBlock(block: @escaping () -> Void) {
|
||||||
if let existing = completionBlock {
|
if let existing = completionBlock {
|
||||||
/*
|
/*
|
||||||
If we already have a completion block, we construct a new one by
|
If we already have a completion block, we construct a new one by
|
||||||
|
|
@ -30,7 +30,7 @@ extension NSOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add multiple depdendencies to the operation.
|
/// Add multiple depdendencies to the operation.
|
||||||
func addDependencies(dependencies: [NSOperation]) {
|
func addDependencies(dependencies: [Operation]) {
|
||||||
for dependency in dependencies {
|
for dependency in dependencies {
|
||||||
addDependency(dependency)
|
addDependency(dependency)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,17 +32,17 @@ struct NegatedCondition<T: OperationCondition>: OperationCondition {
|
||||||
self.condition = condition
|
self.condition = condition
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
return condition.dependencyForOperation(operation)
|
return condition.dependencyForOperation(operation: operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
|
||||||
condition.evaluateForOperation(operation) { result in
|
condition.evaluateForOperation(operation: operation) { result in
|
||||||
if result == .Satisfied {
|
if result == .Satisfied {
|
||||||
// If the composed condition succeeded, then this one failed.
|
// If the composed condition succeeded, then this one failed.
|
||||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name,
|
OperationConditionKey: type(of: self).name,
|
||||||
self.dynamicType.negatedConditionKey: self.condition.dynamicType.name
|
type(of: self).negatedConditionKey: type(of: self.condition).name
|
||||||
])
|
])
|
||||||
|
|
||||||
completion(.Failed(error))
|
completion(.Failed(error))
|
||||||
|
|
|
||||||
|
|
@ -22,19 +22,19 @@ struct NoCancelledDependencies: OperationCondition {
|
||||||
// No op.
|
// No op.
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||||
// Verify that all of the dependencies executed.
|
// Verify that all of the dependencies executed.
|
||||||
let cancelled = operation.dependencies.filter { $0.cancelled }
|
let cancelled = operation.dependencies.filter { $0.isCancelled }
|
||||||
|
|
||||||
if !cancelled.isEmpty {
|
if !cancelled.isEmpty {
|
||||||
// At least one dependency was cancelled; the condition was not satisfied.
|
// At least one dependency was cancelled; the condition was not satisfied.
|
||||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name,
|
OperationConditionKey: type(of: self).name,
|
||||||
self.dynamicType.cancelledDependenciesKey: cancelled
|
type(of: self).cancelledDependenciesKey: cancelled
|
||||||
])
|
])
|
||||||
|
|
||||||
completion(.Failed(error))
|
completion(.Failed(error))
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,10 @@ protocol OperationCondition {
|
||||||
expressing that as multiple conditions. Alternatively, you could return
|
expressing that as multiple conditions. Alternatively, you could return
|
||||||
a single `GroupOperation` that executes multiple operations internally.
|
a single `GroupOperation` that executes multiple operations internally.
|
||||||
*/
|
*/
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation?
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation?
|
||||||
|
|
||||||
/// Evaluate the condition, to see if it has been satisfied or not.
|
/// Evaluate the condition, to see if it has been satisfied or not.
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void)
|
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -76,31 +76,31 @@ func ==(lhs: OperationConditionResult, rhs: OperationConditionResult) -> Bool {
|
||||||
// MARK: Evaluate Conditions
|
// MARK: Evaluate Conditions
|
||||||
|
|
||||||
struct OperationConditionEvaluator {
|
struct OperationConditionEvaluator {
|
||||||
static func evaluate(conditions: [OperationCondition], operation: Operation, completion: [NSError] -> Void) {
|
static func evaluate(conditions: [OperationCondition], operation: EarthquakeOperation, completion: @escaping ([NSError]) -> Void) {
|
||||||
// Check conditions.
|
// Check conditions.
|
||||||
let conditionGroup = dispatch_group_create()
|
let conditionGroup = DispatchGroup()
|
||||||
|
|
||||||
var results = [OperationConditionResult?](count: conditions.count, repeatedValue: nil)
|
var results: [OperationConditionResult?] = Array(repeating: nil, count: conditions.count)
|
||||||
|
|
||||||
// Ask each condition to evaluate and store its result in the "results" array.
|
// Ask each condition to evaluate and store its result in the "results" array.
|
||||||
for (index, condition) in conditions.enumerate() {
|
for (index, condition) in conditions.enumerated() {
|
||||||
dispatch_group_enter(conditionGroup)
|
conditionGroup.enter()
|
||||||
condition.evaluateForOperation(operation) { result in
|
condition.evaluateForOperation(operation: operation) { result in
|
||||||
results[index] = result
|
results[index] = result
|
||||||
dispatch_group_leave(conditionGroup)
|
conditionGroup.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After all the conditions have evaluated, this block will execute.
|
// After all the conditions have evaluated, this block will execute.
|
||||||
dispatch_group_notify(conditionGroup, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
|
conditionGroup.notify(queue: DispatchQueue.global(qos: .default)) {
|
||||||
// Aggregate the errors that occurred, in order.
|
// Aggregate the errors that occurred, in order.
|
||||||
var failures = results.flatMap { $0?.error }
|
var failures = results.compactMap { $0?.error }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If any of the conditions caused this operation to be cancelled,
|
If any of the conditions caused this operation to be cancelled,
|
||||||
check for that.
|
check for that.
|
||||||
*/
|
*/
|
||||||
if operation.cancelled {
|
if operation.isCancelled {
|
||||||
failures.append(NSError(code: .ConditionFailed))
|
failures.append(NSError(code: .ConditionFailed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ enum OperationErrorCode: Int {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSError {
|
extension NSError {
|
||||||
convenience init(code: OperationErrorCode, userInfo: [NSObject: AnyObject]? = nil) {
|
convenience init(code: OperationErrorCode, userInfo: [String: Any]? = nil) {
|
||||||
self.init(domain: OperationErrorDomain, code: code.rawValue, userInfo: userInfo)
|
self.init(domain: OperationErrorDomain, code: code.rawValue, userInfo: userInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,15 @@ import Foundation
|
||||||
protocol OperationObserver {
|
protocol OperationObserver {
|
||||||
|
|
||||||
/// Invoked immediately prior to the `Operation`'s `execute()` method.
|
/// Invoked immediately prior to the `Operation`'s `execute()` method.
|
||||||
func operationDidStart(operation: Operation)
|
func operationDidStart(operation: EarthquakeOperation)
|
||||||
|
|
||||||
/// Invoked when `Operation.produceOperation(_:)` is executed.
|
/// Invoked when `Operation.produceOperation(_:)` is executed.
|
||||||
func operation(operation: Operation, didProduceOperation newOperation: NSOperation)
|
func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Invoked as an `Operation` finishes, along with any errors produced during
|
Invoked as an `Operation` finishes, along with any errors produced during
|
||||||
execution (or readiness evaluation).
|
execution (or readiness evaluation).
|
||||||
*/
|
*/
|
||||||
func operationDidFinish(operation: Operation, errors: [NSError])
|
func operationDidFinish(operation: EarthquakeOperation, errors: [NSError])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ struct PassbookCondition: OperationCondition {
|
||||||
|
|
||||||
init() { }
|
init() { }
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
/*
|
/*
|
||||||
There's nothing you can do to make Passbook available if it's not
|
There's nothing you can do to make Passbook available if it's not
|
||||||
on your device.
|
on your device.
|
||||||
|
|
@ -26,13 +26,13 @@ struct PassbookCondition: OperationCondition {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||||
if PKPassLibrary.isPassLibraryAvailable() {
|
if PKPassLibrary.isPassLibraryAvailable() {
|
||||||
completion(.Satisfied)
|
completion(.Satisfied)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name
|
OperationConditionKey: type(of: self).name
|
||||||
])
|
])
|
||||||
|
|
||||||
completion(.Failed(error))
|
completion(.Failed(error))
|
||||||
|
|
|
||||||
|
|
@ -18,18 +18,18 @@ struct PhotosCondition: OperationCondition {
|
||||||
|
|
||||||
init() { }
|
init() { }
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
return PhotosPermissionOperation()
|
return PhotosPermissionOperation()
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||||
switch PHPhotoLibrary.authorizationStatus() {
|
switch PHPhotoLibrary.authorizationStatus() {
|
||||||
case .Authorized:
|
case .authorized:
|
||||||
completion(.Satisfied)
|
completion(.Satisfied)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name
|
OperationConditionKey: type(of: self).name
|
||||||
])
|
])
|
||||||
|
|
||||||
completion(.Failed(error))
|
completion(.Failed(error))
|
||||||
|
|
@ -41,17 +41,17 @@ struct PhotosCondition: OperationCondition {
|
||||||
A private `Operation` that will request access to the user's Photos, if it
|
A private `Operation` that will request access to the user's Photos, if it
|
||||||
has not already been granted.
|
has not already been granted.
|
||||||
*/
|
*/
|
||||||
private class PhotosPermissionOperation: Operation {
|
private class PhotosPermissionOperation: EarthquakeOperation {
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
addCondition(AlertPresentation())
|
addCondition(condition: AlertPresentation())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
switch PHPhotoLibrary.authorizationStatus() {
|
switch PHPhotoLibrary.authorizationStatus() {
|
||||||
case .NotDetermined:
|
case .notDetermined:
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
PHPhotoLibrary.requestAuthorization { status in
|
PHPhotoLibrary.requestAuthorization { status in
|
||||||
self.finish()
|
self.finish()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,26 +19,26 @@ struct ReachabilityCondition: OperationCondition {
|
||||||
static let name = "Reachability"
|
static let name = "Reachability"
|
||||||
static let isMutuallyExclusive = false
|
static let isMutuallyExclusive = false
|
||||||
|
|
||||||
let host: NSURL
|
let host: URL
|
||||||
|
|
||||||
|
|
||||||
init(host: NSURL) {
|
init(host: URL) {
|
||||||
self.host = host
|
self.host = host
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
|
||||||
ReachabilityController.requestReachability(host) { reachable in
|
ReachabilityController.requestReachability(url: host) { reachable in
|
||||||
if reachable {
|
if reachable {
|
||||||
completion(.Satisfied)
|
completion(.Satisfied)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name,
|
OperationConditionKey: type(of: self).name,
|
||||||
self.dynamicType.hostKey: self.host
|
type(of: self).hostKey: self.host
|
||||||
])
|
])
|
||||||
|
|
||||||
completion(.Failed(error))
|
completion(.Failed(error))
|
||||||
|
|
@ -52,16 +52,16 @@ struct ReachabilityCondition: OperationCondition {
|
||||||
private class ReachabilityController {
|
private class ReachabilityController {
|
||||||
static var reachabilityRefs = [String: SCNetworkReachability]()
|
static var reachabilityRefs = [String: SCNetworkReachability]()
|
||||||
|
|
||||||
static let reachabilityQueue = dispatch_queue_create("Operations.Reachability", DISPATCH_QUEUE_SERIAL)
|
static let reachabilityQueue = DispatchQueue(label: "Operations.Reachability")
|
||||||
|
|
||||||
static func requestReachability(url: NSURL, completionHandler: (Bool) -> Void) {
|
static func requestReachability(url: URL, completionHandler: @escaping (Bool) -> Void) {
|
||||||
if let host = url.host {
|
if let host = url.host {
|
||||||
dispatch_async(reachabilityQueue) {
|
reachabilityQueue.async {
|
||||||
var ref = self.reachabilityRefs[host]
|
var ref = self.reachabilityRefs[host]
|
||||||
|
|
||||||
if ref == nil {
|
if ref == nil {
|
||||||
let hostString = host as NSString
|
let hostString = host as NSString
|
||||||
ref = SCNetworkReachabilityCreateWithName(nil, hostString.UTF8String)
|
ref = SCNetworkReachabilityCreateWithName(nil, hostString.utf8String!)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ref = ref {
|
if let ref = ref {
|
||||||
|
|
@ -76,7 +76,7 @@ private class ReachabilityController {
|
||||||
such as whether or not the connection would require
|
such as whether or not the connection would require
|
||||||
VPN, a cellular connection, etc.
|
VPN, a cellular connection, etc.
|
||||||
*/
|
*/
|
||||||
reachable = flags.contains(.Reachable)
|
reachable = flags.contains(.reachable)
|
||||||
}
|
}
|
||||||
completionHandler(reachable)
|
completionHandler(reachable)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ This file shows an example of implementing the OperationCondition protocol.
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
private let RemoteNotificationQueue = OperationQueue()
|
private let RemoteNotificationQueue = EarthquakeOperationQueue()
|
||||||
private let RemoteNotificationName = "RemoteNotificationPermissionNotification"
|
private let RemoteNotificationName = Notification.Name("RemoteNotificationPermissionNotification")
|
||||||
|
|
||||||
private enum RemoteRegistrationResult {
|
private enum RemoteRegistrationResult {
|
||||||
case Token(NSData)
|
case Token(NSData)
|
||||||
|
|
@ -23,14 +23,14 @@ struct RemoteNotificationCondition: OperationCondition {
|
||||||
static let name = "RemoteNotification"
|
static let name = "RemoteNotification"
|
||||||
static let isMutuallyExclusive = false
|
static let isMutuallyExclusive = false
|
||||||
|
|
||||||
static func didReceiveNotificationToken(token: NSData) {
|
static func didReceiveNotificationToken(token: Data) {
|
||||||
NSNotificationCenter.defaultCenter().postNotificationName(RemoteNotificationName, object: nil, userInfo: [
|
NotificationCenter.default.post(name: RemoteNotificationName, object: nil, userInfo: [
|
||||||
"token": token
|
"token": token
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
static func didFailToRegister(error: NSError) {
|
static func didFailToRegister(error: NSError) {
|
||||||
NSNotificationCenter.defaultCenter().postNotificationName(RemoteNotificationName, object: nil, userInfo: [
|
NotificationCenter.default.post(name: RemoteNotificationName, object: nil, userInfo: [
|
||||||
"error": error
|
"error": error
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
@ -41,11 +41,11 @@ struct RemoteNotificationCondition: OperationCondition {
|
||||||
self.application = application
|
self.application = application
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
return RemoteNotificationPermissionOperation(application: application, handler: { _ in })
|
return RemoteNotificationPermissionOperation(application: application, handler: { _ in })
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
|
||||||
/*
|
/*
|
||||||
Since evaluation requires executing an operation, use a private operation
|
Since evaluation requires executing an operation, use a private operation
|
||||||
queue.
|
queue.
|
||||||
|
|
@ -57,7 +57,7 @@ struct RemoteNotificationCondition: OperationCondition {
|
||||||
|
|
||||||
case .Error(let underlyingError):
|
case .Error(let underlyingError):
|
||||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name,
|
OperationConditionKey: type(of: self).name,
|
||||||
NSUnderlyingErrorKey: underlyingError
|
NSUnderlyingErrorKey: underlyingError
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
@ -78,11 +78,11 @@ struct RemoteNotificationCondition: OperationCondition {
|
||||||
`RemoteNotificationCondition.didFailToRegister(_:)` in the appropriate
|
`RemoteNotificationCondition.didFailToRegister(_:)` in the appropriate
|
||||||
`UIApplicationDelegate` method, as shown in the `AppDelegate.swift` file.
|
`UIApplicationDelegate` method, as shown in the `AppDelegate.swift` file.
|
||||||
*/
|
*/
|
||||||
private class RemoteNotificationPermissionOperation: Operation {
|
private class RemoteNotificationPermissionOperation: EarthquakeOperation {
|
||||||
let application: UIApplication
|
let application: UIApplication
|
||||||
private let handler: RemoteRegistrationResult -> Void
|
private let handler: (RemoteRegistrationResult) -> Void
|
||||||
|
|
||||||
private init(application: UIApplication, handler: RemoteRegistrationResult -> Void) {
|
init(application: UIApplication, handler: @escaping (RemoteRegistrationResult) -> Void) {
|
||||||
self.application = application
|
self.application = application
|
||||||
self.handler = handler
|
self.handler = handler
|
||||||
|
|
||||||
|
|
@ -92,21 +92,21 @@ private class RemoteNotificationPermissionOperation: Operation {
|
||||||
This operation cannot run at the same time as any other remote notification
|
This operation cannot run at the same time as any other remote notification
|
||||||
permission operation.
|
permission operation.
|
||||||
*/
|
*/
|
||||||
addCondition(MutuallyExclusive<RemoteNotificationPermissionOperation>())
|
addCondition(condition: MutuallyExclusive<RemoteNotificationPermissionOperation>())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
let notificationCenter = NSNotificationCenter.defaultCenter()
|
let notificationCenter = NotificationCenter.default
|
||||||
|
|
||||||
notificationCenter.addObserver(self, selector: #selector(RemoteNotificationPermissionOperation.didReceiveResponse(_:)), name: RemoteNotificationName, object: nil)
|
notificationCenter.addObserver(self, selector: #selector(RemoteNotificationPermissionOperation.didReceiveResponse(notification:)), name: RemoteNotificationName, object: nil)
|
||||||
|
|
||||||
self.application.registerForRemoteNotifications()
|
self.application.registerForRemoteNotifications()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func didReceiveResponse(notification: NSNotification) {
|
@objc func didReceiveResponse(notification: NSNotification) {
|
||||||
NSNotificationCenter.defaultCenter().removeObserver(self)
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
|
||||||
let userInfo = notification.userInfo
|
let userInfo = notification.userInfo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,12 @@ struct SilentCondition<T: OperationCondition>: OperationCondition {
|
||||||
self.condition = condition
|
self.condition = condition
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
// Returning nil means we will never a dependency to be generated.
|
// Returning nil means we will never a dependency to be generated.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
|
||||||
condition.evaluateForOperation(operation, completion: completion)
|
condition.evaluateForOperation(operation: operation, completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,40 +17,38 @@ struct TimeoutObserver: OperationObserver {
|
||||||
|
|
||||||
static let timeoutKey = "Timeout"
|
static let timeoutKey = "Timeout"
|
||||||
|
|
||||||
private let timeout: NSTimeInterval
|
private let timeout: Int
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init(timeout: NSTimeInterval) {
|
init(timeout: Int) {
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: OperationObserver
|
// MARK: OperationObserver
|
||||||
|
|
||||||
func operationDidStart(operation: Operation) {
|
func operationDidStart(operation: EarthquakeOperation) {
|
||||||
// When the operation starts, queue up a block to cause it to time out.
|
// When the operation starts, queue up a block to cause it to time out.
|
||||||
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * Double(NSEC_PER_SEC)))
|
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + .seconds(timeout)) {
|
||||||
|
|
||||||
dispatch_after(when, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
|
|
||||||
/*
|
/*
|
||||||
Cancel the operation if it hasn't finished and hasn't already
|
Cancel the operation if it hasn't finished and hasn't already
|
||||||
been cancelled.
|
been cancelled.
|
||||||
*/
|
*/
|
||||||
if !operation.finished && !operation.cancelled {
|
if !operation.isFinished && !operation.isCancelled {
|
||||||
let error = NSError(code: .ExecutionFailed, userInfo: [
|
let error = NSError(code: .ExecutionFailed, userInfo: [
|
||||||
self.dynamicType.timeoutKey: self.timeout
|
type(of: self).timeoutKey: self.timeout
|
||||||
])
|
])
|
||||||
|
|
||||||
operation.cancelWithError(error)
|
operation.cancelWithError(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) {
|
func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) {
|
||||||
// No op.
|
// No op.
|
||||||
}
|
}
|
||||||
|
|
||||||
func operationDidFinish(operation: Operation, errors: [NSError]) {
|
func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
|
||||||
// No op.
|
// No op.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ extension UIUserNotificationSettings {
|
||||||
let otherCategories = settings.categories ?? []
|
let otherCategories = settings.categories ?? []
|
||||||
let myCategories = categories ?? []
|
let myCategories = categories ?? []
|
||||||
|
|
||||||
return myCategories.isSupersetOf(otherCategories)
|
return myCategories.isSuperset(of: otherCategories)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,17 +32,17 @@ extension UIUserNotificationSettings {
|
||||||
let mergedTypes = types.union(settings.types)
|
let mergedTypes = types.union(settings.types)
|
||||||
|
|
||||||
let myCategories = categories ?? []
|
let myCategories = categories ?? []
|
||||||
var existingCategoriesByIdentifier = Dictionary(sequence: myCategories) { $0.identifier }
|
var existingCategoriesByIdentifier: [String: UIUserNotificationCategory] = Dictionary(sequence: myCategories, keyMapper: \.identifier)
|
||||||
|
|
||||||
let newCategories = settings.categories ?? []
|
let newCategories = settings.categories ?? []
|
||||||
let newCategoriesByIdentifier = Dictionary(sequence: newCategories) { $0.identifier }
|
let newCategoriesByIdentifier: [String: UIUserNotificationCategory] = Dictionary(sequence: newCategories, keyMapper: \.identifier)
|
||||||
|
|
||||||
for (newIdentifier, newCategory) in newCategoriesByIdentifier {
|
for (newIdentifier, newCategory) in newCategoriesByIdentifier {
|
||||||
existingCategoriesByIdentifier[newIdentifier] = newCategory
|
existingCategoriesByIdentifier[newIdentifier] = newCategory
|
||||||
}
|
}
|
||||||
|
|
||||||
let mergedCategories = Set(existingCategoriesByIdentifier.values)
|
let mergedCategories = Set(existingCategoriesByIdentifier.values)
|
||||||
return UIUserNotificationSettings(forTypes: mergedTypes, categories: mergedCategories)
|
return UIUserNotificationSettings(types: mergedTypes, categories: mergedCategories)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,37 +11,37 @@ import Foundation
|
||||||
private var URLSessionTaksOperationKVOContext = 0
|
private var URLSessionTaksOperationKVOContext = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
`URLSessionTaskOperation` is an `Operation` that lifts an `NSURLSessionTask`
|
`URLSessionTaskOperation` is an `Operation` that lifts an `URLSessionTask`
|
||||||
into an operation.
|
into an operation.
|
||||||
|
|
||||||
Note that this operation does not participate in any of the delegate callbacks \
|
Note that this operation does not participate in any of the delegate callbacks \
|
||||||
of an `NSURLSession`, but instead uses Key-Value-Observing to know when the
|
of an `URLSession`, but instead uses Key-Value-Observing to know when the
|
||||||
task has been completed. It also does not get notified about any errors that
|
task has been completed. It also does not get notified about any errors that
|
||||||
occurred during execution of the task.
|
occurred during execution of the task.
|
||||||
|
|
||||||
An example usage of `URLSessionTaskOperation` can be seen in the `DownloadEarthquakesOperation`.
|
An example usage of `URLSessionTaskOperation` can be seen in the `DownloadEarthquakesOperation`.
|
||||||
*/
|
*/
|
||||||
class URLSessionTaskOperation: Operation {
|
class URLSessionTaskOperation: EarthquakeOperation {
|
||||||
let task: NSURLSessionTask
|
let task: URLSessionTask
|
||||||
|
|
||||||
init(task: NSURLSessionTask) {
|
init(task: URLSessionTask) {
|
||||||
assert(task.state == .Suspended, "Tasks must be suspended.")
|
assert(task.state == .suspended, "Tasks must be suspended.")
|
||||||
self.task = task
|
self.task = task
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
assert(task.state == .Suspended, "Task was resumed by something other than \(self).")
|
assert(task.state == .suspended, "Task was resumed by something other than \(self).")
|
||||||
|
|
||||||
task.addObserver(self, forKeyPath: "state", options: [], context: &URLSessionTaksOperationKVOContext)
|
task.addObserver(self, forKeyPath: "state", options: [], context: &URLSessionTaksOperationKVOContext)
|
||||||
|
|
||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
guard context == &URLSessionTaksOperationKVOContext else { return }
|
guard context == &URLSessionTaksOperationKVOContext else { return }
|
||||||
|
|
||||||
if object === task && keyPath == "state" && task.state == .Completed {
|
if object as? URLSessionTask === task && keyPath == "state" && task.state == .completed {
|
||||||
task.removeObserver(self, forKeyPath: "state")
|
task.removeObserver(self, forKeyPath: "state")
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,24 +54,24 @@ struct UserNotificationCondition: OperationCondition {
|
||||||
self.behavior = behavior
|
self.behavior = behavior
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||||
return UserNotificationPermissionOperation(settings: settings, application: application, behavior: behavior)
|
return UserNotificationPermissionOperation(settings: settings, application: application, behavior: behavior)
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||||
let result: OperationConditionResult
|
let result: OperationConditionResult
|
||||||
|
|
||||||
let current = application.currentUserNotificationSettings()
|
let current = application.currentUserNotificationSettings
|
||||||
|
|
||||||
switch (current, settings) {
|
switch (current, settings) {
|
||||||
case (let current?, let settings) where current.contains(settings):
|
case (let current?, let settings) where current.contains(settings: settings):
|
||||||
result = .Satisfied
|
result = .Satisfied
|
||||||
|
|
||||||
default:
|
default:
|
||||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||||
OperationConditionKey: self.dynamicType.name,
|
OperationConditionKey: type(of: self).name,
|
||||||
self.dynamicType.currentSettings: current ?? NSNull(),
|
type(of: self).currentSettings: current ?? NSNull(),
|
||||||
self.dynamicType.desiredSettings: settings
|
type(of: self).desiredSettings: settings
|
||||||
])
|
])
|
||||||
|
|
||||||
result = .Failed(error)
|
result = .Failed(error)
|
||||||
|
|
@ -85,7 +85,7 @@ struct UserNotificationCondition: OperationCondition {
|
||||||
A private `Operation` subclass to register a `UIUserNotificationSettings`
|
A private `Operation` subclass to register a `UIUserNotificationSettings`
|
||||||
object with a `UIApplication`, prompting the user for permission if necessary.
|
object with a `UIApplication`, prompting the user for permission if necessary.
|
||||||
*/
|
*/
|
||||||
private class UserNotificationPermissionOperation: Operation {
|
private class UserNotificationPermissionOperation: EarthquakeOperation {
|
||||||
let settings: UIUserNotificationSettings
|
let settings: UIUserNotificationSettings
|
||||||
let application: UIApplication
|
let application: UIApplication
|
||||||
let behavior: UserNotificationCondition.Behavior
|
let behavior: UserNotificationCondition.Behavior
|
||||||
|
|
@ -97,18 +97,18 @@ private class UserNotificationPermissionOperation: Operation {
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
addCondition(AlertPresentation())
|
addCondition(condition: AlertPresentation())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
dispatch_async(dispatch_get_main_queue()) {
|
DispatchQueue.main.async {
|
||||||
let current = self.application.currentUserNotificationSettings()
|
let current = self.application.currentUserNotificationSettings
|
||||||
|
|
||||||
let settingsToRegister: UIUserNotificationSettings
|
let settingsToRegister: UIUserNotificationSettings
|
||||||
|
|
||||||
switch (current, self.behavior) {
|
switch (current, self.behavior) {
|
||||||
case (let currentSettings?, .Merge):
|
case (let currentSettings?, .Merge):
|
||||||
settingsToRegister = currentSettings.settingsByMerging(self.settings)
|
settingsToRegister = currentSettings.settingsByMerging(settings: self.settings)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
settingsToRegister = self.settings
|
settingsToRegister = self.settings
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import CoreData
|
||||||
private struct ParsedEarthquake {
|
private struct ParsedEarthquake {
|
||||||
// MARK: Properties.
|
// MARK: Properties.
|
||||||
|
|
||||||
let date: NSDate
|
let date: Date
|
||||||
|
|
||||||
let identifier, name, link: String
|
let identifier, name, link: String
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ private struct ParsedEarthquake {
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
init?(feature: [String: AnyObject]) {
|
init?(feature: [String: AnyObject]) {
|
||||||
guard let earthquakeID = feature["id"] as? String where !earthquakeID.isEmpty else { return nil }
|
guard let earthquakeID = feature["id"] as? String, !earthquakeID.isEmpty else { return nil }
|
||||||
identifier = earthquakeID
|
identifier = earthquakeID
|
||||||
|
|
||||||
let properties = feature["properties"] as? [String: AnyObject] ?? [:]
|
let properties = feature["properties"] as? [String: AnyObject] ?? [:]
|
||||||
|
|
@ -34,16 +34,16 @@ private struct ParsedEarthquake {
|
||||||
magnitude = properties["mag"] as? Double ?? 0.0
|
magnitude = properties["mag"] as? Double ?? 0.0
|
||||||
|
|
||||||
if let offset = properties["time"] as? Double {
|
if let offset = properties["time"] as? Double {
|
||||||
date = NSDate(timeIntervalSince1970: offset / 1000)
|
date = Date(timeIntervalSince1970: offset / 1000)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
date = NSDate.distantFuture()
|
date = Date.distantFuture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let geometry = feature["geometry"] as? [String: AnyObject] ?? [:]
|
let geometry = feature["geometry"] as? [String: AnyObject] ?? [:]
|
||||||
|
|
||||||
if let coordinates = geometry["coordinates"] as? [Double] where coordinates.count == 3 {
|
if let coordinates = geometry["coordinates"] as? [Double], coordinates.count == 3 {
|
||||||
longitude = coordinates[0]
|
longitude = coordinates[0]
|
||||||
latitude = coordinates[1]
|
latitude = coordinates[1]
|
||||||
|
|
||||||
|
|
@ -59,20 +59,20 @@ private struct ParsedEarthquake {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An `Operation` to parse earthquakes out of a downloaded feed from the USGS.
|
/// An `Operation` to parse earthquakes out of a downloaded feed from the USGS.
|
||||||
class ParseEarthquakesOperation: Operation {
|
class ParseEarthquakesOperation: EarthquakeOperation {
|
||||||
let cacheFile: NSURL
|
let cacheFile: URL
|
||||||
let context: NSManagedObjectContext
|
let context: NSManagedObjectContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
- parameter cacheFile: The file `NSURL` from which to load earthquake data.
|
- parameter cacheFile: The file `URL` from which to load earthquake data.
|
||||||
- parameter context: The `NSManagedObjectContext` that will be used as the
|
- parameter context: The `NSManagedObjectContext` that will be used as the
|
||||||
basis for importing data. The operation will internally
|
basis for importing data. The operation will internally
|
||||||
construct a new `NSManagedObjectContext` that points
|
construct a new `NSManagedObjectContext` that points
|
||||||
to the same `NSPersistentStoreCoordinator` as the
|
to the same `NSPersistentStoreCoordinator` as the
|
||||||
passed-in context.
|
passed-in context.
|
||||||
*/
|
*/
|
||||||
init(cacheFile: NSURL, context: NSManagedObjectContext) {
|
init(cacheFile: URL, context: NSManagedObjectContext) {
|
||||||
let importContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
|
let importContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
||||||
importContext.persistentStoreCoordinator = context.persistentStoreCoordinator
|
importContext.persistentStoreCoordinator = context.persistentStoreCoordinator
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -90,7 +90,7 @@ class ParseEarthquakesOperation: Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func execute() {
|
override func execute() {
|
||||||
guard let stream = NSInputStream(URL: cacheFile) else {
|
guard let stream = InputStream(url: cacheFile) else {
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -102,35 +102,35 @@ class ParseEarthquakesOperation: Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let json = try NSJSONSerialization.JSONObjectWithStream(stream, options: []) as? [String: AnyObject]
|
let json = try JSONSerialization.jsonObject(with: stream, options: []) as? [String: AnyObject]
|
||||||
|
|
||||||
if let features = json?["features"] as? [[String: AnyObject]] {
|
if let features = json?["features"] as? [[String: AnyObject]] {
|
||||||
parse(features)
|
parse(features: features)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch let jsonError as NSError {
|
catch let jsonError as NSError {
|
||||||
finishWithError(jsonError)
|
finishWithError(error: jsonError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func parse(features: [[String: AnyObject]]) {
|
private func parse(features: [[String: AnyObject]]) {
|
||||||
let parsedEarthquakes = features.flatMap { ParsedEarthquake(feature: $0) }
|
let parsedEarthquakes = features.compactMap { ParsedEarthquake(feature: $0) }
|
||||||
|
|
||||||
context.performBlock {
|
context.perform {
|
||||||
for newEarthquake in parsedEarthquakes {
|
for newEarthquake in parsedEarthquakes {
|
||||||
self.insert(newEarthquake)
|
self.insert(parsed: newEarthquake)
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = self.saveContext()
|
let error = self.saveContext()
|
||||||
self.finishWithError(error)
|
self.finishWithError(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func insert(parsed: ParsedEarthquake) {
|
private func insert(parsed: ParsedEarthquake) {
|
||||||
let earthquake = NSEntityDescription.insertNewObjectForEntityForName(Earthquake.entityName, inManagedObjectContext: context) as! Earthquake
|
let earthquake = NSEntityDescription.insertNewObject(forEntityName: Earthquake.entityName, into: context) as! Earthquake
|
||||||
|
|
||||||
earthquake.identifier = parsed.identifier
|
earthquake.identifier = parsed.identifier
|
||||||
earthquake.timestamp = parsed.date
|
earthquake.timestamp = parsed.date
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class SplitViewController: UISplitViewController {
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
preferredDisplayMode = .AllVisible
|
preferredDisplayMode = .allVisible
|
||||||
|
|
||||||
delegate = self
|
delegate = self
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue