mirror of
https://github.com/samsonjs/Advanced-NSOperations.git
synced 2026-03-25 08:25:47 +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 */; };
|
||||
553F50111B082BCF005F991E /* BackgroundObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50101B082BCF005F991E /* BackgroundObserver.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 */; };
|
||||
55817C3C1B18FDF8001C0CE2 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551B9C021AEB1CA900302388 /* Operation.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 */; };
|
||||
55817C3F1B18FDF8001C0CE2 /* URLSessionTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55727FB91AF2849E00EC6916 /* URLSessionTaskOperation.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
|
@ -207,7 +207,7 @@
|
|||
553F2D621AFFED5300BF4093 /* Operation Queue */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
551B9C061AEB2D7800302388 /* OperationQueue.swift */,
|
||||
551B9C061AEB2D7800302388 /* EarthquakeOperationQueue.swift */,
|
||||
55CD4D201AF5C05300E3A9E3 /* ExclusivityController.swift */,
|
||||
);
|
||||
name = "Operation Queue";
|
||||
|
|
@ -275,8 +275,8 @@
|
|||
55857B301AF1E59900219D5A /* Operations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
551B9C021AEB1CA900302388 /* Operation.swift */,
|
||||
55857B3A1AF20DE800219D5A /* EarthquakeBlockOperation.swift */,
|
||||
551B9C021AEB1CA900302388 /* EarthquakeOperation.swift */,
|
||||
55727FB11AF2798C00EC6916 /* GroupOperation.swift */,
|
||||
55727FB91AF2849E00EC6916 /* URLSessionTaskOperation.swift */,
|
||||
553F50031B07FB5E005F991E /* LocationOperation.swift */,
|
||||
|
|
@ -396,8 +396,8 @@
|
|||
55E7021F1AFD15C80032742F /* SplitViewController.swift in Sources */,
|
||||
55817C4D1B18FDF8001C0CE2 /* PassbookCondition.swift in Sources */,
|
||||
55817C561B18FDF8001C0CE2 /* NSOperation+Operations.swift in Sources */,
|
||||
55817C3C1B18FDF8001C0CE2 /* Operation.swift in Sources */,
|
||||
55817C3A1B18FDF8001C0CE2 /* OperationQueue.swift in Sources */,
|
||||
55817C3C1B18FDF8001C0CE2 /* EarthquakeOperation.swift in Sources */,
|
||||
55817C3A1B18FDF8001C0CE2 /* EarthquakeOperationQueue.swift in Sources */,
|
||||
55817C4A1B18FDF8001C0CE2 /* MutuallyExclusive.swift in Sources */,
|
||||
55817C531B18FDF8001C0CE2 /* UserNotificationCondition.swift in Sources */,
|
||||
55817C461B18FDF8001C0CE2 /* OperationCondition.swift in Sources */,
|
||||
|
|
@ -550,6 +550,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = Earthquakes;
|
||||
PROVISIONING_PROFILE = "";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
|
|
@ -566,6 +567,7 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = Earthquakes;
|
||||
PROVISIONING_PROFILE = "";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
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
|
||||
|
||||
class AlertOperation: Operation {
|
||||
class AlertOperation: EarthquakeOperation {
|
||||
// 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?
|
||||
|
||||
var title: String? {
|
||||
|
|
@ -38,21 +38,21 @@ class AlertOperation: Operation {
|
|||
// MARK: Initialization
|
||||
|
||||
init(presentationContext: UIViewController? = nil) {
|
||||
self.presentationContext = presentationContext ?? UIApplication.sharedApplication().keyWindow?.rootViewController
|
||||
self.presentationContext = presentationContext ?? UIApplication.shared.keyWindow?.rootViewController
|
||||
|
||||
super.init()
|
||||
|
||||
addCondition(AlertPresentation())
|
||||
addCondition(condition: AlertPresentation())
|
||||
|
||||
/*
|
||||
This operation modifies the view controller hierarchy.
|
||||
Doing this while other such operations are executing can lead to
|
||||
inconsistencies in UIKit. So, let's make them mutally exclusive.
|
||||
*/
|
||||
addCondition(MutuallyExclusive<UIViewController>())
|
||||
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
|
||||
if let strongSelf = self {
|
||||
handler(strongSelf)
|
||||
|
|
@ -71,12 +71,12 @@ class AlertOperation: Operation {
|
|||
return
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
DispatchQueue.main.async {
|
||||
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
|
||||
|
||||
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
|
||||
RemoteNotificationCondition.didFailToRegister(error)
|
||||
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
||||
RemoteNotificationCondition.didFailToRegister(error: error as NSError)
|
||||
}
|
||||
|
||||
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
|
||||
RemoteNotificationCondition.didReceiveNotificationToken(deviceToken)
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
RemoteNotificationCondition.didReceiveNotificationToken(token: deviceToken)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ class BackgroundObserver: NSObject, OperationObserver {
|
|||
super.init()
|
||||
|
||||
// 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 isInBackground {
|
||||
|
|
@ -38,7 +38,7 @@ class BackgroundObserver: NSObject, OperationObserver {
|
|||
}
|
||||
|
||||
deinit {
|
||||
NSNotificationCenter.defaultCenter().removeObserver(self)
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
@objc func didEnterBackground(notification: NSNotification) {
|
||||
|
|
@ -57,7 +57,7 @@ class BackgroundObserver: NSObject, OperationObserver {
|
|||
|
||||
private func startBackgroundTask() {
|
||||
if identifier == UIBackgroundTaskInvalid {
|
||||
identifier = UIApplication.sharedApplication().beginBackgroundTaskWithName("BackgroundObserver", expirationHandler: {
|
||||
identifier = UIApplication.shared.beginBackgroundTask(withName: "BackgroundObserver", expirationHandler: {
|
||||
self.endBackgroundTask()
|
||||
})
|
||||
}
|
||||
|
|
@ -65,18 +65,18 @@ class BackgroundObserver: NSObject, OperationObserver {
|
|||
|
||||
private func endBackgroundTask() {
|
||||
if identifier != UIBackgroundTaskInvalid {
|
||||
UIApplication.sharedApplication().endBackgroundTask(identifier)
|
||||
UIApplication.shared.endBackgroundTask(identifier)
|
||||
identifier = UIBackgroundTaskInvalid
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ import Foundation
|
|||
class DownloadEarthquakesOperation: GroupOperation {
|
||||
// MARK: Properties
|
||||
|
||||
let cacheFile: NSURL
|
||||
let cacheFile: URL
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
/// - parameter cacheFile: The file `NSURL` to which the earthquake feed will be downloaded.
|
||||
init(cacheFile: NSURL) {
|
||||
/// - parameter cacheFile: The file `URL` to which the earthquake feed will be downloaded.
|
||||
init(cacheFile: URL) {
|
||||
self.cacheFile = cacheFile
|
||||
super.init(operations: [])
|
||||
name = "Download Earthquakes"
|
||||
|
|
@ -29,43 +29,43 @@ class DownloadEarthquakesOperation: GroupOperation {
|
|||
or when the services you use offer secure communication options, you
|
||||
should always prefer to use https.
|
||||
*/
|
||||
let url = NSURL(string: "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson")!
|
||||
let task = NSURLSession.sharedSession().downloadTaskWithURL(url) { url, response, error in
|
||||
self.downloadFinished(url, response: response as? NSHTTPURLResponse, error: error)
|
||||
let url = URL(string: "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson")!
|
||||
let task = URLSession.shared.downloadTask(with: url) { url, response, error in
|
||||
self.downloadFinished(url: url, response: response as? HTTPURLResponse, error: error as NSError?)
|
||||
}
|
||||
|
||||
let taskOperation = URLSessionTaskOperation(task: task)
|
||||
|
||||
let reachabilityCondition = ReachabilityCondition(host: url)
|
||||
taskOperation.addCondition(reachabilityCondition)
|
||||
taskOperation.addCondition(condition: reachabilityCondition)
|
||||
|
||||
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 {
|
||||
do {
|
||||
/*
|
||||
If we already have a file at this location, just delete 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 { }
|
||||
|
||||
do {
|
||||
try NSFileManager.defaultManager().moveItemAtURL(localURL, toURL: cacheFile)
|
||||
try FileManager.default.moveItem(at: localURL, to: cacheFile)
|
||||
}
|
||||
catch let error as NSError {
|
||||
aggregateError(error)
|
||||
aggregateError(error: error)
|
||||
}
|
||||
|
||||
}
|
||||
else if let error = error {
|
||||
aggregateError(error)
|
||||
aggregateError(error: error)
|
||||
}
|
||||
else {
|
||||
// Do nothing, and the operation will automatically finish.
|
||||
|
|
|
|||
|
|
@ -21,37 +21,37 @@ class Earthquake: NSManagedObject {
|
|||
|
||||
// MARK: Formatters
|
||||
|
||||
static let timestampFormatter: NSDateFormatter = {
|
||||
let timestampFormatter = NSDateFormatter()
|
||||
static let timestampFormatter: DateFormatter = {
|
||||
let timestampFormatter = DateFormatter()
|
||||
|
||||
timestampFormatter.dateStyle = .MediumStyle
|
||||
timestampFormatter.timeStyle = .MediumStyle
|
||||
timestampFormatter.dateStyle = .medium
|
||||
timestampFormatter.timeStyle = .medium
|
||||
|
||||
return timestampFormatter
|
||||
}()
|
||||
|
||||
static let magnitudeFormatter: NSNumberFormatter = {
|
||||
let magnitudeFormatter = NSNumberFormatter()
|
||||
static let magnitudeFormatter: NumberFormatter = {
|
||||
let magnitudeFormatter = NumberFormatter()
|
||||
|
||||
magnitudeFormatter.numberStyle = .DecimalStyle
|
||||
magnitudeFormatter.numberStyle = .decimal
|
||||
magnitudeFormatter.maximumFractionDigits = 1
|
||||
magnitudeFormatter.minimumFractionDigits = 1
|
||||
|
||||
return magnitudeFormatter
|
||||
}()
|
||||
|
||||
static let depthFormatter: NSLengthFormatter = {
|
||||
static let depthFormatter: LengthFormatter = {
|
||||
|
||||
let depthFormatter = NSLengthFormatter()
|
||||
depthFormatter.forPersonHeightUse = false
|
||||
let depthFormatter = LengthFormatter()
|
||||
depthFormatter.isForPersonHeightUse = false
|
||||
|
||||
return depthFormatter
|
||||
}()
|
||||
|
||||
static let distanceFormatter: NSLengthFormatter = {
|
||||
let distanceFormatter = NSLengthFormatter()
|
||||
static let distanceFormatter: LengthFormatter = {
|
||||
let distanceFormatter = LengthFormatter()
|
||||
|
||||
distanceFormatter.forPersonHeightUse = false
|
||||
distanceFormatter.isForPersonHeightUse = false
|
||||
distanceFormatter.numberFormatter.maximumFractionDigits = 2
|
||||
|
||||
return distanceFormatter
|
||||
|
|
@ -64,7 +64,7 @@ class Earthquake: NSManagedObject {
|
|||
@NSManaged var longitude: Double
|
||||
@NSManaged var name: String
|
||||
@NSManaged var magnitude: Double
|
||||
@NSManaged var timestamp: NSDate
|
||||
@NSManaged var timestamp: Date
|
||||
@NSManaged var depth: Double
|
||||
@NSManaged var webLink: String
|
||||
|
||||
|
|
@ -73,6 +73,6 @@ class Earthquake: NSManagedObject {
|
|||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -12,19 +12,19 @@ import MapKit
|
|||
class EarthquakeTableViewController: UITableViewController {
|
||||
// MARK: Properties
|
||||
|
||||
var queue: OperationQueue?
|
||||
var queue: EarthquakeOperationQueue?
|
||||
var earthquake: Earthquake?
|
||||
var locationRequest: LocationOperation?
|
||||
|
||||
|
||||
@IBOutlet var map: MKMapView!
|
||||
@IBOutlet var nameLabel: UILabel!
|
||||
@IBOutlet var magnitudeLabel: UILabel!
|
||||
@IBOutlet var depthLabel: UILabel!
|
||||
@IBOutlet var timeLabel: UILabel!
|
||||
@IBOutlet var distanceLabel: UILabel!
|
||||
|
||||
|
||||
// MARKL View Controller
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
|
@ -38,88 +38,88 @@ class EarthquakeTableViewController: UITableViewController {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let span = MKCoordinateSpan(latitudeDelta: 15, longitudeDelta: 15)
|
||||
map.region = MKCoordinateRegion(center: earthquake.coordinate, span: span)
|
||||
|
||||
|
||||
let annotation = MKPointAnnotation()
|
||||
annotation.coordinate = earthquake.coordinate
|
||||
map.addAnnotation(annotation)
|
||||
|
||||
|
||||
nameLabel.text = earthquake.name
|
||||
magnitudeLabel.text = Earthquake.magnitudeFormatter.stringFromNumber(earthquake.magnitude)
|
||||
depthLabel.text = Earthquake.depthFormatter.stringFromMeters(earthquake.depth)
|
||||
timeLabel.text = Earthquake.timestampFormatter.stringFromDate(earthquake.timestamp)
|
||||
|
||||
magnitudeLabel.text = Earthquake.magnitudeFormatter.string(from: NSNumber(value: earthquake.magnitude))
|
||||
depthLabel.text = Earthquake.depthFormatter.string(fromMeters: earthquake.depth)
|
||||
timeLabel.text = Earthquake.timestampFormatter.string(from: earthquake.timestamp as Date)
|
||||
|
||||
/*
|
||||
We can use a `LocationOperation` to retrieve the user's current location.
|
||||
Once we have the location, we can compute how far they currently are
|
||||
from the epicenter of the earthquake.
|
||||
|
||||
|
||||
If this operation fails (ie, we are denied access to their location),
|
||||
then the text in the `UILabel` will remain as what it is defined to
|
||||
be in the storyboard.
|
||||
*/
|
||||
let locationOperation = LocationOperation(accuracy: kCLLocationAccuracyKilometer) { location in
|
||||
if let earthquakeLocation = self.earthquake?.location {
|
||||
let distance = location.distanceFromLocation(earthquakeLocation)
|
||||
self.distanceLabel.text = Earthquake.distanceFormatter.stringFromMeters(distance)
|
||||
let distance = location.distance(from: earthquakeLocation)
|
||||
self.distanceLabel.text = Earthquake.distanceFormatter.string(fromMeters: distance)
|
||||
}
|
||||
|
||||
self.locationRequest = nil
|
||||
}
|
||||
|
||||
|
||||
queue?.addOperation(locationOperation)
|
||||
locationRequest = locationOperation
|
||||
}
|
||||
|
||||
override func viewWillDisappear(animated: Bool) {
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
// If the LocationOperation is still going on, then cancel it.
|
||||
locationRequest?.cancel()
|
||||
}
|
||||
|
||||
|
||||
@IBAction func shareEarthquake(sender: UIBarButtonItem) {
|
||||
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 items = [url, location]
|
||||
|
||||
|
||||
let items = [url, location] as [Any]
|
||||
|
||||
/*
|
||||
We could present the share sheet manually, but by putting it inside
|
||||
an `Operation`, we can make it mutually exclusive with other operations
|
||||
that modify the view controller hierarchy.
|
||||
*/
|
||||
let shareOperation = EarthquakeBlockOperation { (continuation: Void -> Void) in
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
let shareOperation = EarthquakeBlockOperation { (continuation: @escaping () -> Void) in
|
||||
DispatchQueue.main.async {
|
||||
let shareSheet = UIActivityViewController(activityItems: items, applicationActivities: nil)
|
||||
|
||||
|
||||
shareSheet.popoverPresentationController?.barButtonItem = sender
|
||||
|
||||
shareSheet.completionWithItemsHandler = { _ in
|
||||
shareSheet.completionWithItemsHandler = { _, _, _, _ in
|
||||
// End the operation when the share sheet completes.
|
||||
continuation()
|
||||
}
|
||||
|
||||
self.presentViewController(shareSheet, animated: true, completion: nil)
|
||||
|
||||
self.present(shareSheet, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Indicate that this operation modifies the View Controller hierarchy
|
||||
and is thus mutually exclusive.
|
||||
*/
|
||||
shareOperation.addCondition(MutuallyExclusive<UIViewController>())
|
||||
shareOperation.addCondition(condition: MutuallyExclusive<UIViewController>())
|
||||
|
||||
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 {
|
||||
// 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.
|
||||
let moreInformation = MoreInformationOperation(URL: url)
|
||||
|
||||
|
|
@ -133,29 +133,29 @@ class EarthquakeTableViewController: UITableViewController {
|
|||
queue?.addOperation(alert)
|
||||
}
|
||||
}
|
||||
|
||||
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
||||
|
||||
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
var view = mapView.dequeueReusableAnnotationViewWithIdentifier("pin") as? MKPinAnnotationView
|
||||
|
||||
|
||||
var view = mapView.dequeueReusableAnnotationView(withIdentifier: "pin") as? MKPinAnnotationView
|
||||
|
||||
view = view ?? MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
|
||||
|
||||
|
||||
guard let pin = view else { return nil }
|
||||
|
||||
|
||||
switch earthquake.magnitude {
|
||||
case 0..<3: pin.pinTintColor = UIColor.grayColor()
|
||||
case 3..<4: pin.pinTintColor = UIColor.blueColor()
|
||||
case 4..<5: pin.pinTintColor = UIColor.orangeColor()
|
||||
default: pin.pinTintColor = UIColor.redColor()
|
||||
case 0..<3: pin.pinTintColor = UIColor.gray
|
||||
case 3..<4: pin.pinTintColor = UIColor.blue
|
||||
case 4..<5: pin.pinTintColor = UIColor.orange
|
||||
default: pin.pinTintColor = UIColor.red
|
||||
}
|
||||
|
||||
pin.enabled = false
|
||||
|
||||
pin.isEnabled = false
|
||||
|
||||
return pin
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,28 +13,28 @@ import CloudKit
|
|||
class EarthquakesTableViewController: UITableViewController {
|
||||
// MARK: Properties
|
||||
|
||||
var fetchedResultsController: NSFetchedResultsController?
|
||||
|
||||
let operationQueue = OperationQueue()
|
||||
|
||||
var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>?
|
||||
|
||||
let operationQueue = EarthquakeOperationQueue()
|
||||
|
||||
// MARK: View Controller
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let operation = LoadModelOperation { context in
|
||||
// Now that we have a context, build our `FetchedResultsController`.
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
let request = NSFetchRequest(entityName: Earthquake.entityName)
|
||||
DispatchQueue.main.async {
|
||||
let request = NSFetchRequest<NSFetchRequestResult>(entityName: Earthquake.entityName)
|
||||
|
||||
request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
|
||||
|
||||
|
||||
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.updateUI()
|
||||
}
|
||||
}
|
||||
|
|
@ -42,89 +42,89 @@ class EarthquakesTableViewController: UITableViewController {
|
|||
operationQueue.addOperation(operation)
|
||||
}
|
||||
|
||||
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
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]
|
||||
|
||||
return section?.numberOfObjects ?? 0
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("earthquakeCell", forIndexPath: indexPath) as! EarthquakeTableViewCell
|
||||
|
||||
if let earthquake = fetchedResultsController?.objectAtIndexPath(indexPath) as? Earthquake {
|
||||
cell.configure(earthquake)
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "earthquakeCell", for: indexPath as IndexPath) as! EarthquakeTableViewCell
|
||||
|
||||
if let earthquake = fetchedResultsController?.object(at: indexPath) as? Earthquake {
|
||||
cell.configure(earthquake: earthquake)
|
||||
}
|
||||
|
||||
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
|
||||
`EarthquakeBlockOperation`. This allows us to attach conditions to the
|
||||
operation. For example, you could make it so that you could only perform
|
||||
the segue if the network is reachable and you have access to the user's
|
||||
Photos library.
|
||||
|
||||
|
||||
If you decide to use this pattern in your apps, choose conditions that
|
||||
are sensible and do not place onerous requirements on the user.
|
||||
|
||||
|
||||
It's also worth noting that the Observer attached to the
|
||||
`EarthquakeBlockOperation` will cause the tableview row to be deselected
|
||||
automatically if the `EarthquakeOperation` fails.
|
||||
|
||||
|
||||
You may choose to add your own observer to introspect the errors reported
|
||||
as the operation finishes. Doing so would allow you to present a message
|
||||
to the user about why you were unable to perform the requested action.
|
||||
*/
|
||||
|
||||
|
||||
let operation = EarthquakeBlockOperation {
|
||||
self.performSegueWithIdentifier("showEarthquake", sender: nil)
|
||||
self.performSegue(withIdentifier: "showEarthquake", sender: nil)
|
||||
}
|
||||
|
||||
operation.addCondition(MutuallyExclusive<UIViewController>())
|
||||
|
||||
let blockObserver = BlockObserver { _, errors in
|
||||
|
||||
operation.addCondition(condition: MutuallyExclusive<UIViewController>())
|
||||
|
||||
let blockObserver = BlockObserver(finishHandler: { _, errors in
|
||||
/*
|
||||
If the operation errored (ex: a condition failed) then the segue
|
||||
isn't going to happen. We shouldn't leave the row selected.
|
||||
*/
|
||||
If the operation errored (ex: a condition failed) then the segue
|
||||
isn't going to happen. We shouldn't leave the row selected.
|
||||
*/
|
||||
if !errors.isEmpty {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
||||
DispatchQueue.main.async {
|
||||
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operation.addObserver(blockObserver)
|
||||
|
||||
})
|
||||
|
||||
operation.addObserver(observer: blockObserver)
|
||||
|
||||
operationQueue.addOperation(operation)
|
||||
}
|
||||
|
||||
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
|
||||
guard let navigationVC = segue.destinationViewController as? UINavigationController,
|
||||
detailVC = navigationVC.viewControllers.first as? EarthquakeTableViewController else {
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
guard let navigationVC = segue.destination as? UINavigationController,
|
||||
let detailVC = navigationVC.viewControllers.first as? EarthquakeTableViewController else {
|
||||
return
|
||||
}
|
||||
detailVC.queue = operationQueue
|
||||
|
||||
if let indexPath = tableView.indexPathForSelectedRow {
|
||||
detailVC.earthquake = fetchedResultsController?.objectAtIndexPath(indexPath) as? Earthquake
|
||||
detailVC.earthquake = fetchedResultsController?.object(at: indexPath) as? Earthquake
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@IBAction func startRefreshing(sender: UIRefreshControl) {
|
||||
getEarthquakes()
|
||||
}
|
||||
|
||||
|
||||
private func getEarthquakes(userInitiated: Bool = true) {
|
||||
if let context = fetchedResultsController?.managedObjectContext {
|
||||
let getEarthquakesOperation = GetEarthquakesOperation(context: context) {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
DispatchQueue.main.async {
|
||||
self.refreshControl?.endRefreshing()
|
||||
self.updateUI()
|
||||
}
|
||||
|
|
@ -138,13 +138,12 @@ class EarthquakesTableViewController: UITableViewController {
|
|||
We don't have a context to operate on, so wait a bit and just make
|
||||
the refresh control end.
|
||||
*/
|
||||
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.3 * Double(NSEC_PER_SEC)))
|
||||
dispatch_after(when, dispatch_get_main_queue()) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
|
||||
self.refreshControl?.endRefreshing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func updateUI() {
|
||||
do {
|
||||
try fetchedResultsController?.performFetch()
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ class GetEarthquakesOperation: GroupOperation {
|
|||
parsing are complete. This handler will be
|
||||
invoked on an arbitrary queue.
|
||||
*/
|
||||
init(context: NSManagedObjectContext, completionHandler: Void -> Void) {
|
||||
let cachesFolder = try! NSFileManager.defaultManager().URLForDirectory(.CachesDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
|
||||
init(context: NSManagedObjectContext, completionHandler: @escaping () -> Void) {
|
||||
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:
|
||||
|
|
@ -39,7 +39,7 @@ class GetEarthquakesOperation: GroupOperation {
|
|||
downloadOperation = DownloadEarthquakesOperation(cacheFile: cacheFile)
|
||||
parseOperation = ParseEarthquakesOperation(cacheFile: cacheFile, context: context)
|
||||
|
||||
let finishOperation = NSBlockOperation(block: completionHandler)
|
||||
let finishOperation = EarthquakeBlockOperation(block: { _ in completionHandler() })
|
||||
|
||||
// These operations must be executed in order
|
||||
parseOperation.addDependency(downloadOperation)
|
||||
|
|
@ -50,9 +50,9 @@ class GetEarthquakesOperation: GroupOperation {
|
|||
name = "Get Earthquakes"
|
||||
}
|
||||
|
||||
override func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) {
|
||||
if let firstError = errors.first where (operation === downloadOperation || operation === parseOperation) {
|
||||
produceAlert(firstError)
|
||||
override func operationDidFinish(operation: Operation, withErrors errors: [NSError]) {
|
||||
if let firstError = errors.first, (operation === downloadOperation || operation === parseOperation) {
|
||||
produceAlert(error: firstError)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ class GetEarthquakesOperation: GroupOperation {
|
|||
switch errorReason {
|
||||
case failedReachability:
|
||||
// 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.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
|
||||
}
|
||||
|
||||
produceOperation(alert)
|
||||
produceOperation(operation: alert)
|
||||
hasProducedAlert = true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +1,111 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "29@2x-2.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "29@3x.png",
|
||||
"scale" : "3x"
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "40@2x-2.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "40@3x.png",
|
||||
"scale" : "3x"
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "60@3x.png",
|
||||
"scale" : "3x"
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "29.png",
|
||||
"scale" : "1x"
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "29@2x-1.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "40.png",
|
||||
"scale" : "1x"
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "40@2x-1.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "76.png",
|
||||
"scale" : "1x"
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "76@2x.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"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,
|
||||
it will produce an `AlertOperation` that will offer to retry the operation.
|
||||
*/
|
||||
class LoadModelOperation: Operation {
|
||||
class LoadModelOperation: EarthquakeOperation {
|
||||
// MARK: Properties
|
||||
|
||||
let loadHandler: NSManagedObjectContext -> Void
|
||||
let loadHandler: (NSManagedObjectContext) -> Void
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(loadHandler: NSManagedObjectContext -> Void) {
|
||||
init(loadHandler: @escaping (NSManagedObjectContext) -> Void) {
|
||||
self.loadHandler = loadHandler
|
||||
|
||||
super.init()
|
||||
|
||||
// We only want one of these going at a time.
|
||||
addCondition(MutuallyExclusive<LoadModelOperation>())
|
||||
addCondition(condition: MutuallyExclusive<LoadModelOperation>())
|
||||
}
|
||||
|
||||
override func execute() {
|
||||
|
|
@ -34,9 +34,9 @@ class LoadModelOperation: Operation {
|
|||
get the Caches directory, then your entire sandbox is broken and
|
||||
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
|
||||
|
|
@ -44,14 +44,14 @@ class LoadModelOperation: Operation {
|
|||
we deserve to crash. Plus, there's really no easy way to recover from
|
||||
a missing model without reconstructing it programmatically
|
||||
*/
|
||||
let model = NSManagedObjectModel.mergedModelFromBundles(nil)!
|
||||
let model = NSManagedObjectModel.mergedModel(from: nil)!
|
||||
|
||||
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
|
||||
|
||||
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
|
||||
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
|
||||
context.persistentStoreCoordinator = persistentStoreCoordinator
|
||||
|
||||
var error = createStore(persistentStoreCoordinator, atURL: storeURL)
|
||||
var error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
|
||||
|
||||
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
|
||||
delete it and try again.
|
||||
*/
|
||||
destroyStore(persistentStoreCoordinator, atURL: storeURL)
|
||||
error = createStore(persistentStoreCoordinator, atURL: storeURL)
|
||||
destroyStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
|
||||
error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
|
||||
}
|
||||
|
||||
if persistentStoreCoordinator.persistentStores.isEmpty {
|
||||
print("Error creating SQLite store: \(error).")
|
||||
print("Error creating SQLite store: \(String(describing: error)).")
|
||||
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 {
|
||||
|
|
@ -74,13 +74,13 @@ class LoadModelOperation: Operation {
|
|||
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?
|
||||
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 {
|
||||
error = storeError
|
||||
|
|
@ -89,15 +89,15 @@ class LoadModelOperation: Operation {
|
|||
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 {
|
||||
let _ = try persistentStoreCoordinator.destroyPersistentStoreAtURL(URL, withType: type, options: nil)
|
||||
let _ = try persistentStoreCoordinator.destroyPersistentStore(at: URL as URL, ofType: type, options: nil)
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -111,7 +111,7 @@ class LoadModelOperation: Operation {
|
|||
alert.message = "An error occurred while loading the database. \(firstError.localizedDescription). Please try again later."
|
||||
|
||||
// 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.
|
||||
let handler = loadHandler
|
||||
|
|
@ -123,14 +123,14 @@ class LoadModelOperation: Operation {
|
|||
simply by creating a new copy of the operation and giving it the same
|
||||
loadHandler.
|
||||
*/
|
||||
alert.addAction("Retry Now") { alertOperation in
|
||||
alert.addAction(title: "Retry Now") { alertOperation in
|
||||
let retryOperation = LoadModelOperation(loadHandler: handler)
|
||||
|
||||
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 SafariServices
|
||||
|
||||
/// An `Operation` to display an `NSURL` in an app-modal `SFSafariViewController`.
|
||||
class MoreInformationOperation: Operation {
|
||||
/// An `Operation` to display an `URL` in an app-modal `SFSafariViewController`.
|
||||
class MoreInformationOperation: EarthquakeOperation {
|
||||
// MARK: Properties
|
||||
|
||||
let URL: NSURL
|
||||
let URL: URL
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(URL: NSURL) {
|
||||
init(URL: URL) {
|
||||
self.URL = URL
|
||||
|
||||
super.init()
|
||||
|
||||
addCondition(MutuallyExclusive<UIViewController>())
|
||||
addCondition(condition: MutuallyExclusive<UIViewController>())
|
||||
}
|
||||
|
||||
// MARK: Overrides
|
||||
|
||||
override func execute() {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
DispatchQueue.main.async {
|
||||
self.showSafariViewController()
|
||||
}
|
||||
}
|
||||
|
||||
private func showSafariViewController() {
|
||||
if let context = UIApplication.sharedApplication().keyWindow?.rootViewController {
|
||||
let safari = SFSafariViewController(URL: URL, entersReaderIfAvailable: false)
|
||||
if let context = UIApplication.shared.keyWindow?.rootViewController {
|
||||
let safari = SFSafariViewController(url: URL, entersReaderIfAvailable: false)
|
||||
safari.delegate = self
|
||||
context.presentViewController(safari, animated: true, completion: nil)
|
||||
context.present(safari, animated: true, completion: nil)
|
||||
}
|
||||
else {
|
||||
finish()
|
||||
|
|
@ -46,8 +46,8 @@ class MoreInformationOperation: Operation {
|
|||
}
|
||||
|
||||
extension MoreInformationOperation: SFSafariViewControllerDelegate {
|
||||
func safariViewControllerDidFinish(controller: SFSafariViewController) {
|
||||
controller.dismissViewControllerAnimated(true) {
|
||||
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
||||
controller.dismiss(animated: true) {
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,17 +17,17 @@ struct NetworkObserver: OperationObserver {
|
|||
|
||||
init() { }
|
||||
|
||||
func operationDidStart(operation: Operation) {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
func operationDidStart(operation: EarthquakeOperation) {
|
||||
DispatchQueue.main.async {
|
||||
// Increment the network indicator's "reference count"
|
||||
NetworkIndicatorController.sharedIndicatorController.networkActivityDidStart()
|
||||
}
|
||||
}
|
||||
|
||||
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { }
|
||||
func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) { }
|
||||
|
||||
func operationDidFinish(operation: Operation, errors: [NSError]) {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
|
||||
DispatchQueue.main.async {
|
||||
// Decrement the network indicator's "reference count".
|
||||
NetworkIndicatorController.sharedIndicatorController.networkActivityDidEnd()
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ private class NetworkIndicatorController {
|
|||
// MARK: Methods
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ private class NetworkIndicatorController {
|
|||
}
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ private class NetworkIndicatorController {
|
|||
hiding of the indicator by one second. This provides the chance
|
||||
to come in and invalidate the timer before it fires.
|
||||
*/
|
||||
visibilityTimer = Timer(interval: 1.0) {
|
||||
visibilityTimer = Timer(interval: 1) {
|
||||
self.hideIndicator()
|
||||
}
|
||||
}
|
||||
|
|
@ -82,13 +82,13 @@ private class NetworkIndicatorController {
|
|||
private func showIndicator() {
|
||||
visibilityTimer?.cancel()
|
||||
visibilityTimer = nil
|
||||
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
|
||||
UIApplication.shared.isNetworkActivityIndicatorVisible = true
|
||||
}
|
||||
|
||||
private func hideIndicator() {
|
||||
visibilityTimer?.cancel()
|
||||
visibilityTimer = nil
|
||||
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
|
||||
UIApplication.shared.isNetworkActivityIndicatorVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,10 +100,8 @@ class Timer {
|
|||
|
||||
// MARK: Initialization
|
||||
|
||||
init(interval: NSTimeInterval, handler: dispatch_block_t) {
|
||||
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC)))
|
||||
|
||||
dispatch_after(when, dispatch_get_main_queue()) { [weak self] in
|
||||
init(interval: Int, handler: @escaping () -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(interval)) { [weak self] in
|
||||
if self?.isCancelled == false {
|
||||
handler()
|
||||
}
|
||||
|
|
@ -113,4 +111,4 @@ class Timer {
|
|||
func cancel() {
|
||||
isCancelled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ import Foundation
|
|||
struct BlockObserver: OperationObserver {
|
||||
// MARK: Properties
|
||||
|
||||
private let startHandler: (Operation -> Void)?
|
||||
private let produceHandler: ((Operation, NSOperation) -> Void)?
|
||||
private let finishHandler: ((Operation, [NSError]) -> Void)?
|
||||
private let startHandler: ((EarthquakeOperation) -> Void)?
|
||||
private let produceHandler: ((EarthquakeOperation, Operation) -> 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.produceHandler = produceHandler
|
||||
self.finishHandler = finishHandler
|
||||
|
|
@ -27,15 +27,15 @@ struct BlockObserver: OperationObserver {
|
|||
|
||||
// MARK: OperationObserver
|
||||
|
||||
func operationDidStart(operation: Operation) {
|
||||
func operationDidStart(operation: EarthquakeOperation) {
|
||||
startHandler?(operation)
|
||||
}
|
||||
|
||||
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) {
|
||||
func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) {
|
||||
produceHandler?(operation, newOperation)
|
||||
}
|
||||
|
||||
func operationDidFinish(operation: Operation, errors: [NSError]) {
|
||||
func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
|
||||
finishHandler?(operation, errors)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ extension CKContainer {
|
|||
operation fails. If the verification was successful, this value will
|
||||
be `nil`.
|
||||
*/
|
||||
func verifyPermission(permission: CKApplicationPermissions, requestingIfNecessary shouldRequest: Bool = false, completion: NSError? -> Void) {
|
||||
verifyAccountStatus(self, permission: permission, shouldRequest: shouldRequest, completion: completion)
|
||||
func verifyPermission(permission: CKApplicationPermissions, requestingIfNecessary shouldRequest: Bool = false, completion: @escaping (Error?) -> Void) {
|
||||
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
|
||||
`CKContainer`.
|
||||
*/
|
||||
private func verifyAccountStatus(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: NSError? -> Void) {
|
||||
container.accountStatusWithCompletionHandler { accountStatus, accountError in
|
||||
if accountStatus == .Available {
|
||||
private func verifyAccountStatus(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: @escaping (Error?) -> Void) {
|
||||
container.accountStatus { accountStatus, accountError in
|
||||
if accountStatus == .available {
|
||||
if permission != [] {
|
||||
verifyPermission(container, permission: permission, shouldRequest: shouldRequest, completion: completion)
|
||||
verifyPermission(container: container, permission: permission, shouldRequest: shouldRequest, completion: completion)
|
||||
}
|
||||
else {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func verifyPermission(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: NSError? -> Void) {
|
||||
container.statusForApplicationPermission(permission) { permissionStatus, permissionError in
|
||||
if permissionStatus == .Granted {
|
||||
private func verifyPermission(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: @escaping (Error?) -> Void) {
|
||||
container.status(forApplicationPermission: permission) { permissionStatus, permissionError in
|
||||
if permissionStatus == .granted {
|
||||
completion(nil)
|
||||
}
|
||||
else if permissionStatus == .InitialState && shouldRequest {
|
||||
requestPermission(container, permission: permission, completion: completion)
|
||||
else if permissionStatus == .initialState && shouldRequest {
|
||||
requestPermission(container: container, permission: permission, completion: completion)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func requestPermission(container: CKContainer, permission: CKApplicationPermissions, completion: NSError? -> Void) {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
private func requestPermission(container: CKContainer, permission: CKApplicationPermissions, completion: @escaping (Error?) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
container.requestApplicationPermission(permission) { requestStatus, requestError in
|
||||
if requestStatus == .Granted {
|
||||
if requestStatus == .granted {
|
||||
completion(nil)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,23 +21,23 @@ struct CalendarCondition: OperationCondition {
|
|||
self.entityType = entityType
|
||||
}
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
return CalendarPermissionOperation(entityType: entityType)
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
switch EKEventStore.authorizationStatusForEntityType(entityType) {
|
||||
case .Authorized:
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||
switch EKEventStore.authorizationStatus(for: entityType) {
|
||||
case .authorized:
|
||||
completion(.Satisfied)
|
||||
|
||||
default:
|
||||
// We are not authorized to access entities of this type.
|
||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name,
|
||||
self.dynamicType.entityTypeKey: entityType.rawValue
|
||||
])
|
||||
|
||||
completion(.Failed(error))
|
||||
default:
|
||||
// We are not authorized to access entities of this type.
|
||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: type(of: self).name,
|
||||
type(of: self).entityTypeKey: entityType.rawValue,
|
||||
])
|
||||
|
||||
completion(.Failed(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,22 +53,22 @@ private let SharedEventStore = EKEventStore()
|
|||
A private `Operation` that will request access to the user's Calendar/Reminders,
|
||||
if it has not already been granted.
|
||||
*/
|
||||
private class CalendarPermissionOperation: Operation {
|
||||
private class CalendarPermissionOperation: EarthquakeOperation {
|
||||
let entityType: EKEntityType
|
||||
|
||||
init(entityType: EKEntityType) {
|
||||
self.entityType = entityType
|
||||
super.init()
|
||||
addCondition(AlertPresentation())
|
||||
addCondition(condition: AlertPresentation())
|
||||
}
|
||||
|
||||
override func execute() {
|
||||
let status = EKEventStore.authorizationStatusForEntityType(entityType)
|
||||
let status = EKEventStore.authorizationStatus(for: entityType)
|
||||
|
||||
switch status {
|
||||
case .NotDetermined:
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
SharedEventStore.requestAccessToEntityType(self.entityType) { granted, error in
|
||||
case .notDetermined:
|
||||
DispatchQueue.main.async {
|
||||
SharedEventStore.requestAccess(to: self.entityType) { granted, error in
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,16 +36,16 @@ struct CloudContainerCondition: OperationCondition {
|
|||
self.permission = permission
|
||||
}
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
return CloudKitPermissionOperation(container: container, permission: permission)
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
container.verifyPermission(permission, requestingIfNecessary: false) { error in
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
|
||||
container.verifyPermission(permission: permission, requestingIfNecessary: false) { error in
|
||||
if let error = error {
|
||||
let conditionError = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name,
|
||||
self.dynamicType.containerKey: self.container,
|
||||
OperationConditionKey: type(of: self).name,
|
||||
type(of: self).containerKey: self.container,
|
||||
NSUnderlyingErrorKey: error
|
||||
])
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ struct CloudContainerCondition: OperationCondition {
|
|||
This operation asks the user for permission to use CloudKit, if necessary.
|
||||
If permission has already been granted, this operation will quickly finish.
|
||||
*/
|
||||
private class CloudKitPermissionOperation: Operation {
|
||||
private class CloudKitPermissionOperation: EarthquakeOperation {
|
||||
let container: CKContainer
|
||||
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
|
||||
that presents an alert.
|
||||
*/
|
||||
addCondition(AlertPresentation())
|
||||
addCondition(condition: AlertPresentation())
|
||||
}
|
||||
}
|
||||
|
||||
override func execute() {
|
||||
container.verifyPermission(permission, requestingIfNecessary: true) { error in
|
||||
self.finishWithError(error)
|
||||
container.verifyPermission(permission: permission, requestingIfNecessary: true) { error in
|
||||
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
|
||||
immediately finishes.
|
||||
*/
|
||||
class DelayOperation: Operation {
|
||||
class DelayOperation: EarthquakeOperation {
|
||||
// MARK: Types
|
||||
|
||||
private enum Delay {
|
||||
case Interval(NSTimeInterval)
|
||||
case Date(NSDate)
|
||||
case Interval(Int)
|
||||
case Date(Date)
|
||||
}
|
||||
|
||||
// MARK: Properties
|
||||
|
|
@ -34,18 +34,18 @@ class DelayOperation: Operation {
|
|||
|
||||
// MARK: Initialization
|
||||
|
||||
init(interval: NSTimeInterval) {
|
||||
delay = .Interval(interval)
|
||||
init(interval: TimeInterval) {
|
||||
delay = .Interval(Int(interval))
|
||||
super.init()
|
||||
}
|
||||
|
||||
init(until date: NSDate) {
|
||||
delay = .Date(date)
|
||||
delay = .Date(date as Date)
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func execute() {
|
||||
let interval: NSTimeInterval
|
||||
let interval: Int
|
||||
|
||||
// Figure out how long we should wait for.
|
||||
switch delay {
|
||||
|
|
@ -53,7 +53,7 @@ class DelayOperation: Operation {
|
|||
interval = theInterval
|
||||
|
||||
case .Date(let date):
|
||||
interval = date.timeIntervalSinceNow
|
||||
interval = Int(date.timeIntervalSinceNow)
|
||||
}
|
||||
|
||||
guard interval > 0 else {
|
||||
|
|
@ -61,10 +61,9 @@ class DelayOperation: Operation {
|
|||
return
|
||||
}
|
||||
|
||||
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC)))
|
||||
dispatch_after(when, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
|
||||
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + .seconds(interval)) {
|
||||
// If we were cancelled, then finish() has already been called.
|
||||
if !self.cancelled {
|
||||
if !self.isCancelled {
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,8 @@ extension Dictionary {
|
|||
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`.
|
||||
*/
|
||||
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()
|
||||
|
||||
for item in sequence {
|
||||
if let key = keyMapper(item) {
|
||||
self[key] = item
|
||||
|
|
|
|||
|
|
@ -9,15 +9,18 @@ This code shows how to create a simple subclass of Operation.
|
|||
import Foundation
|
||||
|
||||
/// 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.
|
||||
class EarthquakeBlockOperation: Operation {
|
||||
@available(*, deprecated, message: "Use () -> Void directly, it's shorter")
|
||||
typealias dispatch_block_t = () -> Void
|
||||
|
||||
/// A sublcass of `EarthquakeOperation` to execute a closure.
|
||||
class EarthquakeBlockOperation: EarthquakeOperation {
|
||||
private let block: OperationBlock?
|
||||
|
||||
|
||||
/**
|
||||
The designated initializer.
|
||||
|
||||
|
||||
- parameter block: The closure to run when the operation executes. This
|
||||
closure will be run on an arbitrary queue. The parameter passed to the
|
||||
block **MUST** be invoked by your code, or else the `BlockOperation`
|
||||
|
|
@ -28,30 +31,30 @@ class EarthquakeBlockOperation: Operation {
|
|||
self.block = block
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
A convenience initializer to execute a block on the main queue.
|
||||
|
||||
|
||||
- parameter mainQueueBlock: The block to execute on the main queue. Note
|
||||
that this block does not have a "continuation" block to execute (unlike
|
||||
the designated initializer). The operation will be automatically ended
|
||||
after the `mainQueueBlock` is executed.
|
||||
*/
|
||||
convenience init(mainQueueBlock: dispatch_block_t) {
|
||||
convenience init(mainQueueBlock: @escaping () -> Void) {
|
||||
self.init(block: { continuation in
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
DispatchQueue.main.async {
|
||||
mainQueueBlock()
|
||||
continuation()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
override func execute() {
|
||||
guard let block = block else {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
block {
|
||||
self.finish()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,24 +14,21 @@ import Foundation
|
|||
extended readiness requirements, as well as notify many interested parties
|
||||
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
|
||||
class func keyPathsForValuesAffectingIsReady() -> Set<NSObject> {
|
||||
return ["state"]
|
||||
override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
|
||||
switch key {
|
||||
case "isExecuting", "isFinished", "isReady":
|
||||
return ["state"]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
class func keyPathsForValuesAffectingIsExecuting() -> Set<NSObject> {
|
||||
return ["state"]
|
||||
}
|
||||
|
||||
class func keyPathsForValuesAffectingIsFinished() -> Set<NSObject> {
|
||||
return ["state"]
|
||||
}
|
||||
|
||||
|
||||
// MARK: State Management
|
||||
|
||||
private enum State: Int, Comparable {
|
||||
fileprivate enum State: Int, Comparable {
|
||||
/// The initial state of an `Operation`.
|
||||
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
|
||||
classic definition of deadlock.
|
||||
*/
|
||||
willChangeValueForKey("state")
|
||||
willChangeValue(forKey: "state")
|
||||
|
||||
stateLock.withCriticalScope { Void -> Void in
|
||||
stateLock.withCriticalScope { () -> Void in
|
||||
guard _state != .Finished else {
|
||||
return
|
||||
}
|
||||
|
||||
assert(_state.canTransitionToState(newState), "Performing invalid state transition.")
|
||||
assert(_state.canTransitionToState(target: newState), "Performing invalid state transition.")
|
||||
_state = newState
|
||||
}
|
||||
|
||||
didChangeValueForKey("state")
|
||||
didChangeValue(forKey: "state")
|
||||
}
|
||||
}
|
||||
|
||||
// Here is where we extend our definition of "readiness".
|
||||
override var ready: Bool {
|
||||
override var isReady: Bool {
|
||||
switch state {
|
||||
|
||||
case .Initialized:
|
||||
// If the operation has been cancelled, "isReady" should return true
|
||||
return cancelled
|
||||
return isCancelled
|
||||
|
||||
case .Pending:
|
||||
// If the operation has been cancelled, "isReady" should return true
|
||||
guard !cancelled else {
|
||||
guard !isCancelled else {
|
||||
return true
|
||||
}
|
||||
|
||||
// If super isReady, conditions can be evaluated
|
||||
if super.ready {
|
||||
if super.isReady {
|
||||
evaluateConditions()
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +146,7 @@ class Operation: NSOperation {
|
|||
return false
|
||||
|
||||
case .Ready:
|
||||
return super.ready || cancelled
|
||||
return super.isReady || isCancelled
|
||||
|
||||
default:
|
||||
return false
|
||||
|
|
@ -158,31 +155,31 @@ class Operation: NSOperation {
|
|||
|
||||
var userInitiated: Bool {
|
||||
get {
|
||||
return qualityOfService == .UserInitiated
|
||||
return qualityOfService == .userInitiated
|
||||
}
|
||||
|
||||
set {
|
||||
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
|
||||
}
|
||||
|
||||
override var finished: Bool {
|
||||
override var isFinished: Bool {
|
||||
return state == .Finished
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
OperationConditionEvaluator.evaluate(conditions, operation: self) { failures in
|
||||
self._internalErrors.appendContentsOf(failures)
|
||||
OperationConditionEvaluator.evaluate(conditions: conditions, operation: self) { failures in
|
||||
self._internalErrors.append(contentsOf: failures)
|
||||
self.state = .Ready
|
||||
}
|
||||
}
|
||||
|
|
@ -205,7 +202,7 @@ class Operation: NSOperation {
|
|||
observers.append(observer)
|
||||
}
|
||||
|
||||
override func addDependency(operation: NSOperation) {
|
||||
override func addDependency(_ operation: Operation) {
|
||||
assert(state < .Executing, "Dependencies cannot be modified after execution has begun.")
|
||||
|
||||
super.addDependency(operation)
|
||||
|
|
@ -218,7 +215,7 @@ class Operation: NSOperation {
|
|||
super.start()
|
||||
|
||||
// If the operation has been cancelled, we still need to enter the "Finished" state.
|
||||
if cancelled {
|
||||
if isCancelled {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
|
@ -226,11 +223,11 @@ class Operation: NSOperation {
|
|||
override final func main() {
|
||||
assert(state == .Ready, "This operation must be performed on an operation queue.")
|
||||
|
||||
if _internalErrors.isEmpty && !cancelled {
|
||||
if _internalErrors.isEmpty && !isCancelled {
|
||||
state = .Executing
|
||||
|
||||
for observer in observers {
|
||||
observer.operationDidStart(self)
|
||||
observer.operationDidStart(operation: self)
|
||||
}
|
||||
|
||||
execute()
|
||||
|
|
@ -251,7 +248,7 @@ class Operation: NSOperation {
|
|||
their readiness state.
|
||||
*/
|
||||
func execute() {
|
||||
print("\(self.dynamicType) must override `execute()`.")
|
||||
print("\(type(of: self)) must override `execute()`.")
|
||||
|
||||
finish()
|
||||
}
|
||||
|
|
@ -265,9 +262,9 @@ class Operation: NSOperation {
|
|||
cancel()
|
||||
}
|
||||
|
||||
final func produceOperation(operation: NSOperation) {
|
||||
final func produceOperation(operation: Operation) {
|
||||
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()`
|
||||
method. This is also useful if you wish to finish with an error provided
|
||||
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.
|
||||
*/
|
||||
final func finishWithError(error: NSError?) {
|
||||
if let error = error {
|
||||
finish([error])
|
||||
finish(errors: [error])
|
||||
}
|
||||
else {
|
||||
finish()
|
||||
|
|
@ -301,10 +298,10 @@ class Operation: NSOperation {
|
|||
state = .Finishing
|
||||
|
||||
let combinedErrors = _internalErrors + errors
|
||||
finished(combinedErrors)
|
||||
finished(errors: combinedErrors)
|
||||
|
||||
for observer in observers {
|
||||
observer.operationDidFinish(self, errors: combinedErrors)
|
||||
observer.operationDidFinish(operation: self, errors: combinedErrors)
|
||||
}
|
||||
|
||||
state = .Finished
|
||||
|
|
@ -339,10 +336,10 @@ class Operation: NSOperation {
|
|||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
private func ==(lhs: Operation.State, rhs: Operation.State) -> Bool {
|
||||
private func ==(lhs: EarthquakeOperation.State, rhs: EarthquakeOperation.State) -> Bool {
|
||||
return lhs.rawValue == rhs.rawValue
|
||||
}
|
||||
|
|
@ -18,9 +18,9 @@ import Foundation
|
|||
For example, `GroupOperation` is the delegate of its own internal
|
||||
`OperationQueue` and uses it to manage dependencies.
|
||||
*/
|
||||
@objc protocol OperationQueueDelegate: NSObjectProtocol {
|
||||
optional func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation)
|
||||
optional func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError])
|
||||
@objc protocol EarthquakeOperationQueueDelegate: NSObjectProtocol {
|
||||
@objc optional func operationQueue(operationQueue: EarthquakeOperationQueue, willAddOperation operation: Operation)
|
||||
@objc optional func operationQueue(operationQueue: EarthquakeOperationQueue, operationDidFinish operation: Operation, withErrors errors: [NSError])
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -31,11 +31,11 @@ import Foundation
|
|||
- Extracting generated dependencies from operation conditions
|
||||
- Setting up dependencies to enforce mutual exclusivity
|
||||
*/
|
||||
class OperationQueue: NSOperationQueue {
|
||||
weak var delegate: OperationQueueDelegate?
|
||||
class EarthquakeOperationQueue: OperationQueue {
|
||||
weak var delegate: EarthquakeOperationQueueDelegate?
|
||||
|
||||
override func addOperation(operation: NSOperation) {
|
||||
if let op = operation as? Operation {
|
||||
override func addOperation(_ operation: Operation) {
|
||||
if let op = operation as? EarthquakeOperation {
|
||||
// Set up a `BlockObserver` to invoke the `OperationQueueDelegate` method.
|
||||
let delegate = BlockObserver(
|
||||
startHandler: nil,
|
||||
|
|
@ -44,15 +44,15 @@ class OperationQueue: NSOperationQueue {
|
|||
},
|
||||
finishHandler: { [weak self] in
|
||||
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.
|
||||
let dependencies = op.conditions.flatMap {
|
||||
$0.dependencyForOperation(op)
|
||||
let dependencies = op.conditions.compactMap {
|
||||
$0.dependencyForOperation(operation: op)
|
||||
}
|
||||
|
||||
for dependency in dependencies {
|
||||
|
|
@ -65,21 +65,21 @@ class OperationQueue: NSOperationQueue {
|
|||
With condition dependencies added, we can now see if this needs
|
||||
dependencies to enforce mutual exclusivity.
|
||||
*/
|
||||
let concurrencyCategories: [String] = op.conditions.flatMap { condition in
|
||||
if !condition.dynamicType.isMutuallyExclusive { return nil }
|
||||
let concurrencyCategories: [String] = op.conditions.compactMap { condition in
|
||||
if !type(of: condition).isMutuallyExclusive { return nil }
|
||||
|
||||
return "\(condition.dynamicType)"
|
||||
return "\(type(of: condition))"
|
||||
}
|
||||
|
||||
if !concurrencyCategories.isEmpty {
|
||||
// Set up the mutual exclusivity dependencies.
|
||||
let exclusivityController = ExclusivityController.sharedExclusivityController
|
||||
|
||||
exclusivityController.addOperation(op, categories: concurrencyCategories)
|
||||
exclusivityController.addOperation(operation: op, categories: concurrencyCategories)
|
||||
|
||||
op.addObserver(BlockObserver { operation, _ in
|
||||
exclusivityController.removeOperation(operation, categories: concurrencyCategories)
|
||||
})
|
||||
op.addObserver(observer: BlockObserver(finishHandler: { operation, _ in
|
||||
exclusivityController.removeOperation(operation: operation, categories: concurrencyCategories)
|
||||
}))
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -98,15 +98,15 @@ class OperationQueue: NSOperationQueue {
|
|||
*/
|
||||
operation.addCompletionBlock { [weak self, weak operation] in
|
||||
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)
|
||||
}
|
||||
|
||||
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()`,
|
||||
so we'll call it ourselves.
|
||||
|
|
@ -17,8 +17,8 @@ import Foundation
|
|||
class ExclusivityController {
|
||||
static let sharedExclusivityController = ExclusivityController()
|
||||
|
||||
private let serialQueue = dispatch_queue_create("Operations.ExclusivityController", DISPATCH_QUEUE_SERIAL)
|
||||
private var operations: [String: [Operation]] = [:]
|
||||
private let serialQueue = DispatchQueue(label: "Operations.ExclusivityController")
|
||||
private var operations: [String: [EarthquakeOperation]] = [:]
|
||||
|
||||
private init() {
|
||||
/*
|
||||
|
|
@ -28,24 +28,24 @@ class ExclusivityController {
|
|||
}
|
||||
|
||||
/// 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.
|
||||
If this were async, then we might not get around to adding dependencies
|
||||
until after the operation had already begun, which would be incorrect.
|
||||
*/
|
||||
dispatch_sync(serialQueue) {
|
||||
serialQueue.sync {
|
||||
for category in categories {
|
||||
self.noqueue_addOperation(operation, category: category)
|
||||
self.noqueue_addOperation(operation: operation, category: category)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unregisters an operation from being mutually exclusive.
|
||||
func removeOperation(operation: Operation, categories: [String]) {
|
||||
dispatch_async(serialQueue) {
|
||||
func removeOperation(operation: EarthquakeOperation, categories: [String]) {
|
||||
serialQueue.async {
|
||||
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
|
||||
|
||||
private func noqueue_addOperation(operation: Operation, category: String) {
|
||||
private func noqueue_addOperation(operation: EarthquakeOperation, category: String) {
|
||||
var operationsWithThisCategory = operations[category] ?? []
|
||||
|
||||
if let last = operationsWithThisCategory.last {
|
||||
|
|
@ -65,13 +65,13 @@ class ExclusivityController {
|
|||
operations[category] = operationsWithThisCategory
|
||||
}
|
||||
|
||||
private func noqueue_removeOperation(operation: Operation, category: String) {
|
||||
private func noqueue_removeOperation(operation: EarthquakeOperation, category: String) {
|
||||
let matchingOperations = operations[category]
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,21 +21,21 @@ import Foundation
|
|||
subsequent operations (still within the outer `GroupOperation`) that will all
|
||||
be executed before the rest of the operations in the initial chain of operations.
|
||||
*/
|
||||
class GroupOperation: Operation {
|
||||
private let internalQueue = OperationQueue()
|
||||
private let startingOperation = NSBlockOperation(block: {})
|
||||
private let finishingOperation = NSBlockOperation(block: {})
|
||||
class GroupOperation: EarthquakeOperation {
|
||||
private let internalQueue = EarthquakeOperationQueue()
|
||||
private let startingOperation = BlockOperation(block: {})
|
||||
private let finishingOperation = BlockOperation(block: {})
|
||||
|
||||
private var aggregatedErrors = [NSError]()
|
||||
|
||||
convenience init(operations: NSOperation...) {
|
||||
convenience init(operations: Operation...) {
|
||||
self.init(operations: operations)
|
||||
}
|
||||
|
||||
init(operations: [NSOperation]) {
|
||||
init(operations: [Operation]) {
|
||||
super.init()
|
||||
|
||||
internalQueue.suspended = true
|
||||
internalQueue.isSuspended = true
|
||||
internalQueue.delegate = self
|
||||
internalQueue.addOperation(startingOperation)
|
||||
|
||||
|
|
@ -50,11 +50,11 @@ class GroupOperation: Operation {
|
|||
}
|
||||
|
||||
override func execute() {
|
||||
internalQueue.suspended = false
|
||||
internalQueue.isSuspended = false
|
||||
internalQueue.addOperation(finishingOperation)
|
||||
}
|
||||
|
||||
func addOperation(operation: NSOperation) {
|
||||
func addOperation(operation: Operation) {
|
||||
internalQueue.addOperation(operation)
|
||||
}
|
||||
|
||||
|
|
@ -67,14 +67,14 @@ class GroupOperation: Operation {
|
|||
aggregatedErrors.append(error)
|
||||
}
|
||||
|
||||
func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) {
|
||||
func operationDidFinish(operation: Operation, withErrors errors: [NSError]) {
|
||||
// For use by subclassers.
|
||||
}
|
||||
}
|
||||
|
||||
extension GroupOperation: OperationQueueDelegate {
|
||||
final func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation) {
|
||||
assert(!finishingOperation.finished && !finishingOperation.executing, "cannot add new operations to a group after the group has completed")
|
||||
extension GroupOperation: EarthquakeOperationQueueDelegate {
|
||||
final func operationQueue(operationQueue: EarthquakeOperationQueue, willAddOperation operation: Operation) {
|
||||
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.
|
||||
|
|
@ -97,15 +97,15 @@ extension GroupOperation: OperationQueueDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
final func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError]) {
|
||||
aggregatedErrors.appendContentsOf(errors)
|
||||
final func operationQueue(operationQueue: EarthquakeOperationQueue, operationDidFinish operation: Operation, withErrors errors: [NSError]) {
|
||||
aggregatedErrors.append(contentsOf: errors)
|
||||
|
||||
if operation === finishingOperation {
|
||||
internalQueue.suspended = true
|
||||
finish(aggregatedErrors)
|
||||
internalQueue.isSuspended = true
|
||||
finish(errors: aggregatedErrors)
|
||||
}
|
||||
else if operation !== startingOperation {
|
||||
operationDidFinish(operation, withErrors: errors)
|
||||
operationDidFinish(operation: operation, withErrors: errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ struct HealthCondition: OperationCondition {
|
|||
readTypes = typesToRead
|
||||
}
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
guard HKHealthStore.isHealthDataAvailable() else {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -50,9 +50,9 @@ struct HealthCondition: OperationCondition {
|
|||
return HealthPermissionOperation(shareTypes: shareTypes, readTypes: readTypes)
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||
guard HKHealthStore.isHealthDataAvailable() else {
|
||||
failed(shareTypes, completion: completion)
|
||||
failed(unauthorizedShareTypes: shareTypes, completion: completion)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -68,11 +68,11 @@ struct HealthCondition: OperationCondition {
|
|||
write data to HealthKit.
|
||||
*/
|
||||
let unauthorizedShareTypes = shareTypes.filter { shareType in
|
||||
return store.authorizationStatusForType(shareType) != .SharingAuthorized
|
||||
return store.authorizationStatus(for: shareType) != .sharingAuthorized
|
||||
}
|
||||
|
||||
if !unauthorizedShareTypes.isEmpty {
|
||||
failed(Set(unauthorizedShareTypes), completion: completion)
|
||||
failed(unauthorizedShareTypes: Set(unauthorizedShareTypes), completion: completion)
|
||||
}
|
||||
else {
|
||||
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.
|
||||
private func failed(unauthorizedShareTypes: Set<HKSampleType>, completion: OperationConditionResult -> Void) {
|
||||
private func failed(unauthorizedShareTypes: Set<HKSampleType>, completion: (OperationConditionResult) -> Void) {
|
||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name,
|
||||
self.dynamicType.healthDataAvailable: HKHealthStore.isHealthDataAvailable(),
|
||||
self.dynamicType.unauthorizedShareTypesKey: unauthorizedShareTypes
|
||||
OperationConditionKey: type(of: self).name,
|
||||
type(of: self).healthDataAvailable: HKHealthStore.isHealthDataAvailable(),
|
||||
type(of: self).unauthorizedShareTypesKey: unauthorizedShareTypes
|
||||
])
|
||||
|
||||
completion(.Failed(error))
|
||||
|
|
@ -95,7 +95,7 @@ struct HealthCondition: OperationCondition {
|
|||
A private `Operation` that will request access to the user's health data, if
|
||||
it has not already been granted.
|
||||
*/
|
||||
private class HealthPermissionOperation: Operation {
|
||||
private class HealthPermissionOperation: EarthquakeOperation {
|
||||
let shareTypes: Set<HKSampleType>
|
||||
let readTypes: Set<HKSampleType>
|
||||
|
||||
|
|
@ -105,20 +105,20 @@ private class HealthPermissionOperation: Operation {
|
|||
|
||||
super.init()
|
||||
|
||||
addCondition(MutuallyExclusive<HealthPermissionOperation>())
|
||||
addCondition(MutuallyExclusive<UIViewController>())
|
||||
addCondition(AlertPresentation())
|
||||
addCondition(condition: MutuallyExclusive<HealthPermissionOperation>())
|
||||
addCondition(condition: MutuallyExclusive<UIViewController>())
|
||||
addCondition(condition: AlertPresentation())
|
||||
}
|
||||
|
||||
override func execute() {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
DispatchQueue.main.async {
|
||||
let store = HKHealthStore()
|
||||
/*
|
||||
This method is smart enough to not re-prompt for access if access
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ struct LocationCondition: OperationCondition {
|
|||
self.usage = usage
|
||||
}
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
return LocationPermissionOperation(usage: usage)
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||
let enabled = CLLocationManager.locationServicesEnabled()
|
||||
let actual = CLLocationManager.authorizationStatus()
|
||||
|
||||
|
|
@ -42,11 +42,11 @@ struct LocationCondition: OperationCondition {
|
|||
|
||||
// There are several factors to consider when evaluating this condition
|
||||
switch (enabled, usage, actual) {
|
||||
case (true, _, .AuthorizedAlways):
|
||||
case (true, _, .authorizedAlways):
|
||||
// The service is enabled, and we have "Always" permission -> condition satisfied.
|
||||
break
|
||||
|
||||
case (true, .WhenInUse, .AuthorizedWhenInUse):
|
||||
case (true, .WhenInUse, .authorizedWhenInUse):
|
||||
/*
|
||||
The service is enabled, and we have and need "WhenInUse"
|
||||
permission -> condition satisfied.
|
||||
|
|
@ -63,9 +63,9 @@ struct LocationCondition: OperationCondition {
|
|||
The last case would happen if this condition were wrapped in a `SilentCondition`.
|
||||
*/
|
||||
error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name,
|
||||
self.dynamicType.locationServicesEnabledKey: enabled,
|
||||
self.dynamicType.authorizationStatusKey: Int(actual.rawValue)
|
||||
OperationConditionKey: type(of: self).name,
|
||||
type(of: self).locationServicesEnabledKey: enabled,
|
||||
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,
|
||||
if permission has not already been granted.
|
||||
*/
|
||||
private class LocationPermissionOperation: Operation {
|
||||
private class LocationPermissionOperation: EarthquakeOperation {
|
||||
let usage: LocationCondition.Usage
|
||||
var manager: CLLocationManager?
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ private class LocationPermissionOperation: Operation {
|
|||
This is an operation that potentially presents an alert so it should
|
||||
be mutually exclusive with anything else that presents an alert.
|
||||
*/
|
||||
addCondition(AlertPresentation())
|
||||
addCondition(condition: AlertPresentation())
|
||||
}
|
||||
|
||||
override func execute() {
|
||||
|
|
@ -102,8 +102,8 @@ private class LocationPermissionOperation: Operation {
|
|||
need to handle the "upgrade" (.WhenInUse -> .Always) case.
|
||||
*/
|
||||
switch (CLLocationManager.authorizationStatus(), usage) {
|
||||
case (.NotDetermined, _), (.AuthorizedWhenInUse, .Always):
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
case (.notDetermined, _), (.authorizedWhenInUse, .Always):
|
||||
DispatchQueue.main.async {
|
||||
self.requestPermission()
|
||||
}
|
||||
|
||||
|
|
@ -129,14 +129,15 @@ private class LocationPermissionOperation: Operation {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
@objc func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
|
||||
if manager == self.manager && executing && status != .NotDetermined {
|
||||
@available(iOS 14.0, *)
|
||||
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
||||
if manager == self.manager && isExecuting && manager.authorizationStatus != .notDetermined {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,25 +15,25 @@ import CoreLocation
|
|||
prompt for `WhenInUse` location authorization, if the app does not already
|
||||
have it.
|
||||
*/
|
||||
class LocationOperation: Operation, CLLocationManagerDelegate {
|
||||
class LocationOperation: EarthquakeOperation, CLLocationManagerDelegate {
|
||||
// MARK: Properties
|
||||
|
||||
private let accuracy: CLLocationAccuracy
|
||||
private var manager: CLLocationManager?
|
||||
private let handler: CLLocation -> Void
|
||||
private let handler: (CLLocation) -> Void
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(accuracy: CLLocationAccuracy, locationHandler: CLLocation -> Void) {
|
||||
init(accuracy: CLLocationAccuracy, locationHandler: @escaping (CLLocation) -> Void) {
|
||||
self.accuracy = accuracy
|
||||
self.handler = locationHandler
|
||||
super.init()
|
||||
addCondition(LocationCondition(usage: .WhenInUse))
|
||||
addCondition(MutuallyExclusive<CLLocationManager>())
|
||||
addCondition(condition: LocationCondition(usage: .WhenInUse))
|
||||
addCondition(condition: MutuallyExclusive<CLLocationManager>())
|
||||
}
|
||||
|
||||
override func execute() {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
DispatchQueue.main.async {
|
||||
/*
|
||||
`CLLocationManager` needs to be created on a thread with an active
|
||||
run loop, so for simplicity we do this on the main queue.
|
||||
|
|
@ -48,7 +48,7 @@ class LocationOperation: Operation, CLLocationManagerDelegate {
|
|||
}
|
||||
|
||||
override func cancel() {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
DispatchQueue.main.async {
|
||||
self.stopLocationUpdates()
|
||||
super.cancel()
|
||||
}
|
||||
|
|
@ -60,9 +60,9 @@ class LocationOperation: Operation, CLLocationManagerDelegate {
|
|||
}
|
||||
|
||||
// MARK: CLLocationManagerDelegate
|
||||
|
||||
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
guard let location = locations.last where location.horizontalAccuracy <= accuracy else {
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
guard let location = locations.last, location.horizontalAccuracy <= accuracy else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -71,8 +71,8 @@ class LocationOperation: Operation, CLLocationManagerDelegate {
|
|||
finish()
|
||||
}
|
||||
|
||||
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
|
||||
private func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
|
||||
stopLocationUpdates()
|
||||
finishWithError(error)
|
||||
finishWithError(error: error)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ struct MutuallyExclusive<T>: OperationCondition {
|
|||
|
||||
init() { }
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||
completion(.Satisfied)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@
|
|||
import Foundation
|
||||
|
||||
extension NSLock {
|
||||
func withCriticalScope<T>(@noescape block: Void -> T) -> T {
|
||||
func withCriticalScope<T>(block: () -> T) -> T {
|
||||
lock()
|
||||
let value = block()
|
||||
unlock()
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ A convenient extension to Foundation.NSOperation.
|
|||
|
||||
import Foundation
|
||||
|
||||
extension NSOperation {
|
||||
extension Operation {
|
||||
/**
|
||||
Add a completion block to be executed after the `NSOperation` enters the
|
||||
"finished" state.
|
||||
*/
|
||||
func addCompletionBlock(block: Void -> Void) {
|
||||
func addCompletionBlock(block: @escaping () -> Void) {
|
||||
if let existing = completionBlock {
|
||||
/*
|
||||
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.
|
||||
func addDependencies(dependencies: [NSOperation]) {
|
||||
func addDependencies(dependencies: [Operation]) {
|
||||
for dependency in dependencies {
|
||||
addDependency(dependency)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,17 +32,17 @@ struct NegatedCondition<T: OperationCondition>: OperationCondition {
|
|||
self.condition = condition
|
||||
}
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
return condition.dependencyForOperation(operation)
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
return condition.dependencyForOperation(operation: operation)
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
condition.evaluateForOperation(operation) { result in
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
|
||||
condition.evaluateForOperation(operation: operation) { result in
|
||||
if result == .Satisfied {
|
||||
// If the composed condition succeeded, then this one failed.
|
||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name,
|
||||
self.dynamicType.negatedConditionKey: self.condition.dynamicType.name
|
||||
OperationConditionKey: type(of: self).name,
|
||||
type(of: self).negatedConditionKey: type(of: self.condition).name
|
||||
])
|
||||
|
||||
completion(.Failed(error))
|
||||
|
|
|
|||
|
|
@ -22,19 +22,19 @@ struct NoCancelledDependencies: OperationCondition {
|
|||
// No op.
|
||||
}
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||
// Verify that all of the dependencies executed.
|
||||
let cancelled = operation.dependencies.filter { $0.cancelled }
|
||||
let cancelled = operation.dependencies.filter { $0.isCancelled }
|
||||
|
||||
if !cancelled.isEmpty {
|
||||
// At least one dependency was cancelled; the condition was not satisfied.
|
||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name,
|
||||
self.dynamicType.cancelledDependenciesKey: cancelled
|
||||
OperationConditionKey: type(of: self).name,
|
||||
type(of: self).cancelledDependenciesKey: cancelled
|
||||
])
|
||||
|
||||
completion(.Failed(error))
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ protocol OperationCondition {
|
|||
expressing that as multiple conditions. Alternatively, you could return
|
||||
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.
|
||||
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
|
||||
|
||||
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.
|
||||
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.
|
||||
for (index, condition) in conditions.enumerate() {
|
||||
dispatch_group_enter(conditionGroup)
|
||||
condition.evaluateForOperation(operation) { result in
|
||||
for (index, condition) in conditions.enumerated() {
|
||||
conditionGroup.enter()
|
||||
condition.evaluateForOperation(operation: operation) { result in
|
||||
results[index] = result
|
||||
dispatch_group_leave(conditionGroup)
|
||||
conditionGroup.leave()
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
var failures = results.flatMap { $0?.error }
|
||||
var failures = results.compactMap { $0?.error }
|
||||
|
||||
/*
|
||||
If any of the conditions caused this operation to be cancelled,
|
||||
check for that.
|
||||
*/
|
||||
if operation.cancelled {
|
||||
if operation.isCancelled {
|
||||
failures.append(NSError(code: .ConditionFailed))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ enum OperationErrorCode: Int {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,15 @@ import Foundation
|
|||
protocol OperationObserver {
|
||||
|
||||
/// Invoked immediately prior to the `Operation`'s `execute()` method.
|
||||
func operationDidStart(operation: Operation)
|
||||
func operationDidStart(operation: EarthquakeOperation)
|
||||
|
||||
/// 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
|
||||
execution (or readiness evaluation).
|
||||
*/
|
||||
func operationDidFinish(operation: Operation, errors: [NSError])
|
||||
func operationDidFinish(operation: EarthquakeOperation, errors: [NSError])
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ struct PassbookCondition: OperationCondition {
|
|||
|
||||
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
|
||||
on your device.
|
||||
|
|
@ -26,13 +26,13 @@ struct PassbookCondition: OperationCondition {
|
|||
return nil
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||
if PKPassLibrary.isPassLibraryAvailable() {
|
||||
completion(.Satisfied)
|
||||
}
|
||||
else {
|
||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name
|
||||
OperationConditionKey: type(of: self).name
|
||||
])
|
||||
|
||||
completion(.Failed(error))
|
||||
|
|
|
|||
|
|
@ -18,18 +18,18 @@ struct PhotosCondition: OperationCondition {
|
|||
|
||||
init() { }
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
return PhotosPermissionOperation()
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
|
||||
switch PHPhotoLibrary.authorizationStatus() {
|
||||
case .Authorized:
|
||||
case .authorized:
|
||||
completion(.Satisfied)
|
||||
|
||||
default:
|
||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name
|
||||
OperationConditionKey: type(of: self).name
|
||||
])
|
||||
|
||||
completion(.Failed(error))
|
||||
|
|
@ -41,17 +41,17 @@ struct PhotosCondition: OperationCondition {
|
|||
A private `Operation` that will request access to the user's Photos, if it
|
||||
has not already been granted.
|
||||
*/
|
||||
private class PhotosPermissionOperation: Operation {
|
||||
private class PhotosPermissionOperation: EarthquakeOperation {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
addCondition(AlertPresentation())
|
||||
addCondition(condition: AlertPresentation())
|
||||
}
|
||||
|
||||
override func execute() {
|
||||
switch PHPhotoLibrary.authorizationStatus() {
|
||||
case .NotDetermined:
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
case .notDetermined:
|
||||
DispatchQueue.main.async {
|
||||
PHPhotoLibrary.requestAuthorization { status in
|
||||
self.finish()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,26 +19,26 @@ struct ReachabilityCondition: OperationCondition {
|
|||
static let name = "Reachability"
|
||||
static let isMutuallyExclusive = false
|
||||
|
||||
let host: NSURL
|
||||
let host: URL
|
||||
|
||||
|
||||
init(host: NSURL) {
|
||||
init(host: URL) {
|
||||
self.host = host
|
||||
}
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
ReachabilityController.requestReachability(host) { reachable in
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
|
||||
ReachabilityController.requestReachability(url: host) { reachable in
|
||||
if reachable {
|
||||
completion(.Satisfied)
|
||||
}
|
||||
else {
|
||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name,
|
||||
self.dynamicType.hostKey: self.host
|
||||
OperationConditionKey: type(of: self).name,
|
||||
type(of: self).hostKey: self.host
|
||||
])
|
||||
|
||||
completion(.Failed(error))
|
||||
|
|
@ -52,16 +52,16 @@ struct ReachabilityCondition: OperationCondition {
|
|||
private class ReachabilityController {
|
||||
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 {
|
||||
dispatch_async(reachabilityQueue) {
|
||||
reachabilityQueue.async {
|
||||
var ref = self.reachabilityRefs[host]
|
||||
|
||||
if ref == nil {
|
||||
let hostString = host as NSString
|
||||
ref = SCNetworkReachabilityCreateWithName(nil, hostString.UTF8String)
|
||||
ref = SCNetworkReachabilityCreateWithName(nil, hostString.utf8String!)
|
||||
}
|
||||
|
||||
if let ref = ref {
|
||||
|
|
@ -76,7 +76,7 @@ private class ReachabilityController {
|
|||
such as whether or not the connection would require
|
||||
VPN, a cellular connection, etc.
|
||||
*/
|
||||
reachable = flags.contains(.Reachable)
|
||||
reachable = flags.contains(.reachable)
|
||||
}
|
||||
completionHandler(reachable)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ This file shows an example of implementing the OperationCondition protocol.
|
|||
|
||||
import UIKit
|
||||
|
||||
private let RemoteNotificationQueue = OperationQueue()
|
||||
private let RemoteNotificationName = "RemoteNotificationPermissionNotification"
|
||||
private let RemoteNotificationQueue = EarthquakeOperationQueue()
|
||||
private let RemoteNotificationName = Notification.Name("RemoteNotificationPermissionNotification")
|
||||
|
||||
private enum RemoteRegistrationResult {
|
||||
case Token(NSData)
|
||||
|
|
@ -23,14 +23,14 @@ struct RemoteNotificationCondition: OperationCondition {
|
|||
static let name = "RemoteNotification"
|
||||
static let isMutuallyExclusive = false
|
||||
|
||||
static func didReceiveNotificationToken(token: NSData) {
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(RemoteNotificationName, object: nil, userInfo: [
|
||||
static func didReceiveNotificationToken(token: Data) {
|
||||
NotificationCenter.default.post(name: RemoteNotificationName, object: nil, userInfo: [
|
||||
"token": token
|
||||
])
|
||||
}
|
||||
|
||||
static func didFailToRegister(error: NSError) {
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(RemoteNotificationName, object: nil, userInfo: [
|
||||
NotificationCenter.default.post(name: RemoteNotificationName, object: nil, userInfo: [
|
||||
"error": error
|
||||
])
|
||||
}
|
||||
|
|
@ -41,11 +41,11 @@ struct RemoteNotificationCondition: OperationCondition {
|
|||
self.application = application
|
||||
}
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
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
|
||||
queue.
|
||||
|
|
@ -57,7 +57,7 @@ struct RemoteNotificationCondition: OperationCondition {
|
|||
|
||||
case .Error(let underlyingError):
|
||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name,
|
||||
OperationConditionKey: type(of: self).name,
|
||||
NSUnderlyingErrorKey: underlyingError
|
||||
])
|
||||
|
||||
|
|
@ -78,11 +78,11 @@ struct RemoteNotificationCondition: OperationCondition {
|
|||
`RemoteNotificationCondition.didFailToRegister(_:)` in the appropriate
|
||||
`UIApplicationDelegate` method, as shown in the `AppDelegate.swift` file.
|
||||
*/
|
||||
private class RemoteNotificationPermissionOperation: Operation {
|
||||
private class RemoteNotificationPermissionOperation: EarthquakeOperation {
|
||||
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.handler = handler
|
||||
|
||||
|
|
@ -92,21 +92,21 @@ private class RemoteNotificationPermissionOperation: Operation {
|
|||
This operation cannot run at the same time as any other remote notification
|
||||
permission operation.
|
||||
*/
|
||||
addCondition(MutuallyExclusive<RemoteNotificationPermissionOperation>())
|
||||
addCondition(condition: MutuallyExclusive<RemoteNotificationPermissionOperation>())
|
||||
}
|
||||
|
||||
override func execute() {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
let notificationCenter = NSNotificationCenter.defaultCenter()
|
||||
DispatchQueue.main.async {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func didReceiveResponse(notification: NSNotification) {
|
||||
NSNotificationCenter.defaultCenter().removeObserver(self)
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
|
||||
let userInfo = notification.userInfo
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ struct SilentCondition<T: OperationCondition>: OperationCondition {
|
|||
self.condition = condition
|
||||
}
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
// Returning nil means we will never a dependency to be generated.
|
||||
return nil
|
||||
}
|
||||
|
||||
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) {
|
||||
condition.evaluateForOperation(operation, completion: completion)
|
||||
func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
|
||||
condition.evaluateForOperation(operation: operation, completion: completion)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,40 +17,38 @@ struct TimeoutObserver: OperationObserver {
|
|||
|
||||
static let timeoutKey = "Timeout"
|
||||
|
||||
private let timeout: NSTimeInterval
|
||||
private let timeout: Int
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
init(timeout: NSTimeInterval) {
|
||||
init(timeout: Int) {
|
||||
self.timeout = timeout
|
||||
}
|
||||
|
||||
// MARK: OperationObserver
|
||||
|
||||
func operationDidStart(operation: Operation) {
|
||||
func operationDidStart(operation: EarthquakeOperation) {
|
||||
// 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)))
|
||||
|
||||
dispatch_after(when, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
|
||||
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + .seconds(timeout)) {
|
||||
/*
|
||||
Cancel the operation if it hasn't finished and hasn't already
|
||||
been cancelled.
|
||||
*/
|
||||
if !operation.finished && !operation.cancelled {
|
||||
if !operation.isFinished && !operation.isCancelled {
|
||||
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.
|
||||
}
|
||||
|
||||
func operationDidFinish(operation: Operation, errors: [NSError]) {
|
||||
func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
|
||||
// No op.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ extension UIUserNotificationSettings {
|
|||
let otherCategories = settings.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 myCategories = categories ?? []
|
||||
var existingCategoriesByIdentifier = Dictionary(sequence: myCategories) { $0.identifier }
|
||||
var existingCategoriesByIdentifier: [String: UIUserNotificationCategory] = Dictionary(sequence: myCategories, keyMapper: \.identifier)
|
||||
|
||||
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 {
|
||||
existingCategoriesByIdentifier[newIdentifier] = newCategory
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
`URLSessionTaskOperation` is an `Operation` that lifts an `NSURLSessionTask`
|
||||
`URLSessionTaskOperation` is an `Operation` that lifts an `URLSessionTask`
|
||||
into an operation.
|
||||
|
||||
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
|
||||
occurred during execution of the task.
|
||||
|
||||
An example usage of `URLSessionTaskOperation` can be seen in the `DownloadEarthquakesOperation`.
|
||||
*/
|
||||
class URLSessionTaskOperation: Operation {
|
||||
let task: NSURLSessionTask
|
||||
class URLSessionTaskOperation: EarthquakeOperation {
|
||||
let task: URLSessionTask
|
||||
|
||||
init(task: NSURLSessionTask) {
|
||||
assert(task.state == .Suspended, "Tasks must be suspended.")
|
||||
init(task: URLSessionTask) {
|
||||
assert(task.state == .suspended, "Tasks must be suspended.")
|
||||
self.task = task
|
||||
super.init()
|
||||
}
|
||||
|
||||
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.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 }
|
||||
|
||||
if object === task && keyPath == "state" && task.state == .Completed {
|
||||
if object as? URLSessionTask === task && keyPath == "state" && task.state == .completed {
|
||||
task.removeObserver(self, forKeyPath: "state")
|
||||
finish()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,24 +54,24 @@ struct UserNotificationCondition: OperationCondition {
|
|||
self.behavior = behavior
|
||||
}
|
||||
|
||||
func dependencyForOperation(operation: Operation) -> NSOperation? {
|
||||
func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
|
||||
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 current = application.currentUserNotificationSettings()
|
||||
let current = application.currentUserNotificationSettings
|
||||
|
||||
switch (current, settings) {
|
||||
case (let current?, let settings) where current.contains(settings):
|
||||
case (let current?, let settings) where current.contains(settings: settings):
|
||||
result = .Satisfied
|
||||
|
||||
default:
|
||||
let error = NSError(code: .ConditionFailed, userInfo: [
|
||||
OperationConditionKey: self.dynamicType.name,
|
||||
self.dynamicType.currentSettings: current ?? NSNull(),
|
||||
self.dynamicType.desiredSettings: settings
|
||||
OperationConditionKey: type(of: self).name,
|
||||
type(of: self).currentSettings: current ?? NSNull(),
|
||||
type(of: self).desiredSettings: settings
|
||||
])
|
||||
|
||||
result = .Failed(error)
|
||||
|
|
@ -85,7 +85,7 @@ struct UserNotificationCondition: OperationCondition {
|
|||
A private `Operation` subclass to register a `UIUserNotificationSettings`
|
||||
object with a `UIApplication`, prompting the user for permission if necessary.
|
||||
*/
|
||||
private class UserNotificationPermissionOperation: Operation {
|
||||
private class UserNotificationPermissionOperation: EarthquakeOperation {
|
||||
let settings: UIUserNotificationSettings
|
||||
let application: UIApplication
|
||||
let behavior: UserNotificationCondition.Behavior
|
||||
|
|
@ -97,18 +97,18 @@ private class UserNotificationPermissionOperation: Operation {
|
|||
|
||||
super.init()
|
||||
|
||||
addCondition(AlertPresentation())
|
||||
addCondition(condition: AlertPresentation())
|
||||
}
|
||||
|
||||
override func execute() {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
let current = self.application.currentUserNotificationSettings()
|
||||
DispatchQueue.main.async {
|
||||
let current = self.application.currentUserNotificationSettings
|
||||
|
||||
let settingsToRegister: UIUserNotificationSettings
|
||||
|
||||
switch (current, self.behavior) {
|
||||
case (let currentSettings?, .Merge):
|
||||
settingsToRegister = currentSettings.settingsByMerging(self.settings)
|
||||
settingsToRegister = currentSettings.settingsByMerging(settings: self.settings)
|
||||
|
||||
default:
|
||||
settingsToRegister = self.settings
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import CoreData
|
|||
private struct ParsedEarthquake {
|
||||
// MARK: Properties.
|
||||
|
||||
let date: NSDate
|
||||
let date: Date
|
||||
|
||||
let identifier, name, link: String
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ private struct ParsedEarthquake {
|
|||
// MARK: Initialization
|
||||
|
||||
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
|
||||
|
||||
let properties = feature["properties"] as? [String: AnyObject] ?? [:]
|
||||
|
|
@ -34,16 +34,16 @@ private struct ParsedEarthquake {
|
|||
magnitude = properties["mag"] as? Double ?? 0.0
|
||||
|
||||
if let offset = properties["time"] as? Double {
|
||||
date = NSDate(timeIntervalSince1970: offset / 1000)
|
||||
date = Date(timeIntervalSince1970: offset / 1000)
|
||||
}
|
||||
else {
|
||||
date = NSDate.distantFuture()
|
||||
date = Date.distantFuture
|
||||
}
|
||||
|
||||
|
||||
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]
|
||||
latitude = coordinates[1]
|
||||
|
||||
|
|
@ -59,20 +59,20 @@ private struct ParsedEarthquake {
|
|||
}
|
||||
|
||||
/// An `Operation` to parse earthquakes out of a downloaded feed from the USGS.
|
||||
class ParseEarthquakesOperation: Operation {
|
||||
let cacheFile: NSURL
|
||||
class ParseEarthquakesOperation: EarthquakeOperation {
|
||||
let cacheFile: URL
|
||||
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
|
||||
basis for importing data. The operation will internally
|
||||
construct a new `NSManagedObjectContext` that points
|
||||
to the same `NSPersistentStoreCoordinator` as the
|
||||
passed-in context.
|
||||
*/
|
||||
init(cacheFile: NSURL, context: NSManagedObjectContext) {
|
||||
let importContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
|
||||
init(cacheFile: URL, context: NSManagedObjectContext) {
|
||||
let importContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
||||
importContext.persistentStoreCoordinator = context.persistentStoreCoordinator
|
||||
|
||||
/*
|
||||
|
|
@ -90,7 +90,7 @@ class ParseEarthquakesOperation: Operation {
|
|||
}
|
||||
|
||||
override func execute() {
|
||||
guard let stream = NSInputStream(URL: cacheFile) else {
|
||||
guard let stream = InputStream(url: cacheFile) else {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
|
@ -102,35 +102,35 @@ class ParseEarthquakesOperation: Operation {
|
|||
}
|
||||
|
||||
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]] {
|
||||
parse(features)
|
||||
parse(features: features)
|
||||
}
|
||||
else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
catch let jsonError as NSError {
|
||||
finishWithError(jsonError)
|
||||
finishWithError(error: jsonError)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
self.insert(newEarthquake)
|
||||
self.insert(parsed: newEarthquake)
|
||||
}
|
||||
|
||||
let error = self.saveContext()
|
||||
self.finishWithError(error)
|
||||
self.finishWithError(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
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.timestamp = parsed.date
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class SplitViewController: UISplitViewController {
|
|||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
preferredDisplayMode = .AllVisible
|
||||
preferredDisplayMode = .allVisible
|
||||
|
||||
delegate = self
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue