Update for Xcode 13 and Swift 4, runs but doesn't work

This commit is contained in:
Sami Samhuri 2022-02-16 23:15:44 -08:00
parent d60e09b136
commit 2ba16ef4e5
No known key found for this signature in database
GPG key ID: 4B4195422742FC16
48 changed files with 630 additions and 601 deletions

View file

@ -17,10 +17,10 @@
553F500F1B081A9D005F991E /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F500E1B081A9D005F991E /* NetworkObserver.swift */; }; 553F500F1B081A9D005F991E /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F500E1B081A9D005F991E /* NetworkObserver.swift */; };
553F50111B082BCF005F991E /* BackgroundObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50101B082BCF005F991E /* BackgroundObserver.swift */; }; 553F50111B082BCF005F991E /* BackgroundObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50101B082BCF005F991E /* BackgroundObserver.swift */; };
553F50161B08E98A005F991E /* LoadModelOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50151B08E98A005F991E /* LoadModelOperation.swift */; }; 553F50161B08E98A005F991E /* LoadModelOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50151B08E98A005F991E /* LoadModelOperation.swift */; };
55817C3A1B18FDF8001C0CE2 /* OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551B9C061AEB2D7800302388 /* OperationQueue.swift */; }; 55817C3A1B18FDF8001C0CE2 /* EarthquakeOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551B9C061AEB2D7800302388 /* EarthquakeOperationQueue.swift */; };
55817C3B1B18FDF8001C0CE2 /* ExclusivityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CD4D201AF5C05300E3A9E3 /* ExclusivityController.swift */; }; 55817C3B1B18FDF8001C0CE2 /* ExclusivityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CD4D201AF5C05300E3A9E3 /* ExclusivityController.swift */; };
55817C3C1B18FDF8001C0CE2 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551B9C021AEB1CA900302388 /* Operation.swift */; };
55817C3D1B18FDF8001C0CE2 /* EarthquakeBlockOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55857B3A1AF20DE800219D5A /* EarthquakeBlockOperation.swift */; }; 55817C3D1B18FDF8001C0CE2 /* EarthquakeBlockOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55857B3A1AF20DE800219D5A /* EarthquakeBlockOperation.swift */; };
55817C3C1B18FDF8001C0CE2 /* EarthquakeOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551B9C021AEB1CA900302388 /* EarthquakeOperation.swift */; };
55817C3E1B18FDF8001C0CE2 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55727FB11AF2798C00EC6916 /* GroupOperation.swift */; }; 55817C3E1B18FDF8001C0CE2 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55727FB11AF2798C00EC6916 /* GroupOperation.swift */; };
55817C3F1B18FDF8001C0CE2 /* URLSessionTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55727FB91AF2849E00EC6916 /* URLSessionTaskOperation.swift */; }; 55817C3F1B18FDF8001C0CE2 /* URLSessionTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55727FB91AF2849E00EC6916 /* URLSessionTaskOperation.swift */; };
55817C401B18FDF8001C0CE2 /* LocationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50031B07FB5E005F991E /* LocationOperation.swift */; }; 55817C401B18FDF8001C0CE2 /* LocationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553F50031B07FB5E005F991E /* LocationOperation.swift */; };
@ -84,9 +84,9 @@
551B9BE71AEB1C9700302388 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; 551B9BE71AEB1C9700302388 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
551B9BE91AEB1C9700302388 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; 551B9BE91AEB1C9700302388 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
551B9BEC1AEB1C9700302388 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; }; 551B9BEC1AEB1C9700302388 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
551B9C021AEB1CA900302388 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; }; 551B9C021AEB1CA900302388 /* EarthquakeOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EarthquakeOperation.swift; sourceTree = "<group>"; };
551B9C041AEB1CC800302388 /* OperationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationCondition.swift; sourceTree = "<group>"; }; 551B9C041AEB1CC800302388 /* OperationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationCondition.swift; sourceTree = "<group>"; };
551B9C061AEB2D7800302388 /* OperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationQueue.swift; sourceTree = "<group>"; }; 551B9C061AEB2D7800302388 /* EarthquakeOperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EarthquakeOperationQueue.swift; sourceTree = "<group>"; };
551B9C0B1AEBE4F300302388 /* CloudCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudCondition.swift; sourceTree = "<group>"; }; 551B9C0B1AEBE4F300302388 /* CloudCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudCondition.swift; sourceTree = "<group>"; };
551B9C0D1AEBE52800302388 /* ReachabilityCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityCondition.swift; sourceTree = "<group>"; }; 551B9C0D1AEBE52800302388 /* ReachabilityCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityCondition.swift; sourceTree = "<group>"; };
551B9C101AEBE54D00302388 /* PassbookCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassbookCondition.swift; sourceTree = "<group>"; }; 551B9C101AEBE54D00302388 /* PassbookCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassbookCondition.swift; sourceTree = "<group>"; };
@ -207,7 +207,7 @@
553F2D621AFFED5300BF4093 /* Operation Queue */ = { 553F2D621AFFED5300BF4093 /* Operation Queue */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
551B9C061AEB2D7800302388 /* OperationQueue.swift */, 551B9C061AEB2D7800302388 /* EarthquakeOperationQueue.swift */,
55CD4D201AF5C05300E3A9E3 /* ExclusivityController.swift */, 55CD4D201AF5C05300E3A9E3 /* ExclusivityController.swift */,
); );
name = "Operation Queue"; name = "Operation Queue";
@ -275,8 +275,8 @@
55857B301AF1E59900219D5A /* Operations */ = { 55857B301AF1E59900219D5A /* Operations */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
551B9C021AEB1CA900302388 /* Operation.swift */,
55857B3A1AF20DE800219D5A /* EarthquakeBlockOperation.swift */, 55857B3A1AF20DE800219D5A /* EarthquakeBlockOperation.swift */,
551B9C021AEB1CA900302388 /* EarthquakeOperation.swift */,
55727FB11AF2798C00EC6916 /* GroupOperation.swift */, 55727FB11AF2798C00EC6916 /* GroupOperation.swift */,
55727FB91AF2849E00EC6916 /* URLSessionTaskOperation.swift */, 55727FB91AF2849E00EC6916 /* URLSessionTaskOperation.swift */,
553F50031B07FB5E005F991E /* LocationOperation.swift */, 553F50031B07FB5E005F991E /* LocationOperation.swift */,
@ -396,8 +396,8 @@
55E7021F1AFD15C80032742F /* SplitViewController.swift in Sources */, 55E7021F1AFD15C80032742F /* SplitViewController.swift in Sources */,
55817C4D1B18FDF8001C0CE2 /* PassbookCondition.swift in Sources */, 55817C4D1B18FDF8001C0CE2 /* PassbookCondition.swift in Sources */,
55817C561B18FDF8001C0CE2 /* NSOperation+Operations.swift in Sources */, 55817C561B18FDF8001C0CE2 /* NSOperation+Operations.swift in Sources */,
55817C3C1B18FDF8001C0CE2 /* Operation.swift in Sources */, 55817C3C1B18FDF8001C0CE2 /* EarthquakeOperation.swift in Sources */,
55817C3A1B18FDF8001C0CE2 /* OperationQueue.swift in Sources */, 55817C3A1B18FDF8001C0CE2 /* EarthquakeOperationQueue.swift in Sources */,
55817C4A1B18FDF8001C0CE2 /* MutuallyExclusive.swift in Sources */, 55817C4A1B18FDF8001C0CE2 /* MutuallyExclusive.swift in Sources */,
55817C531B18FDF8001C0CE2 /* UserNotificationCondition.swift in Sources */, 55817C531B18FDF8001C0CE2 /* UserNotificationCondition.swift in Sources */,
55817C461B18FDF8001C0CE2 /* OperationCondition.swift in Sources */, 55817C461B18FDF8001C0CE2 /* OperationCondition.swift in Sources */,
@ -550,6 +550,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = Earthquakes; PRODUCT_NAME = Earthquakes;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Debug; name = Debug;
@ -566,6 +567,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = Earthquakes; PRODUCT_NAME = Earthquakes;
PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE = "";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Release; name = Release;

View file

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

View file

@ -8,10 +8,10 @@ This file shows how to present an alert as part of an operation.
import UIKit import UIKit
class AlertOperation: Operation { class AlertOperation: EarthquakeOperation {
// MARK: Properties // MARK: Properties
private let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .Alert) private let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .alert)
private let presentationContext: UIViewController? private let presentationContext: UIViewController?
var title: String? { var title: String? {
@ -38,21 +38,21 @@ class AlertOperation: Operation {
// MARK: Initialization // MARK: Initialization
init(presentationContext: UIViewController? = nil) { init(presentationContext: UIViewController? = nil) {
self.presentationContext = presentationContext ?? UIApplication.sharedApplication().keyWindow?.rootViewController self.presentationContext = presentationContext ?? UIApplication.shared.keyWindow?.rootViewController
super.init() super.init()
addCondition(AlertPresentation()) addCondition(condition: AlertPresentation())
/* /*
This operation modifies the view controller hierarchy. This operation modifies the view controller hierarchy.
Doing this while other such operations are executing can lead to Doing this while other such operations are executing can lead to
inconsistencies in UIKit. So, let's make them mutally exclusive. inconsistencies in UIKit. So, let's make them mutally exclusive.
*/ */
addCondition(MutuallyExclusive<UIViewController>()) addCondition(condition: MutuallyExclusive<UIViewController>())
} }
func addAction(title: String, style: UIAlertActionStyle = .Default, handler: AlertOperation -> Void = { _ in }) { func addAction(title: String, style: UIAlertActionStyle = .default, handler: @escaping (AlertOperation) -> Void = { _ in }) {
let action = UIAlertAction(title: title, style: style) { [weak self] _ in let action = UIAlertAction(title: title, style: style) { [weak self] _ in
if let strongSelf = self { if let strongSelf = self {
handler(strongSelf) handler(strongSelf)
@ -71,12 +71,12 @@ class AlertOperation: Operation {
return return
} }
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
if self.alertController.actions.isEmpty { if self.alertController.actions.isEmpty {
self.addAction("OK") self.addAction(title: "OK")
} }
presentationContext.presentViewController(self.alertController, animated: true, completion: nil) presentationContext.present(self.alertController, animated: true, completion: nil)
} }
} }
} }

View file

@ -16,11 +16,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: UIApplicationDelegate // MARK: UIApplicationDelegate
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) { func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
RemoteNotificationCondition.didFailToRegister(error) RemoteNotificationCondition.didFailToRegister(error: error as NSError)
} }
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) { func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
RemoteNotificationCondition.didReceiveNotificationToken(deviceToken) RemoteNotificationCondition.didReceiveNotificationToken(token: deviceToken)
} }
} }

View file

@ -25,11 +25,11 @@ class BackgroundObserver: NSObject, OperationObserver {
super.init() super.init()
// We need to know when the application moves to/from the background. // We need to know when the application moves to/from the background.
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(BackgroundObserver.didEnterBackground(_:)), name: UIApplicationDidEnterBackgroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(BackgroundObserver.didEnterBackground(notification:)), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(BackgroundObserver.didEnterForeground(_:)), name: UIApplicationDidBecomeActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(BackgroundObserver.didEnterForeground(notification:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
isInBackground = UIApplication.sharedApplication().applicationState == .Background isInBackground = UIApplication.shared.applicationState == .background
// If we're in the background already, immediately begin the background task. // If we're in the background already, immediately begin the background task.
if isInBackground { if isInBackground {
@ -38,7 +38,7 @@ class BackgroundObserver: NSObject, OperationObserver {
} }
deinit { deinit {
NSNotificationCenter.defaultCenter().removeObserver(self) NotificationCenter.default.removeObserver(self)
} }
@objc func didEnterBackground(notification: NSNotification) { @objc func didEnterBackground(notification: NSNotification) {
@ -57,7 +57,7 @@ class BackgroundObserver: NSObject, OperationObserver {
private func startBackgroundTask() { private func startBackgroundTask() {
if identifier == UIBackgroundTaskInvalid { if identifier == UIBackgroundTaskInvalid {
identifier = UIApplication.sharedApplication().beginBackgroundTaskWithName("BackgroundObserver", expirationHandler: { identifier = UIApplication.shared.beginBackgroundTask(withName: "BackgroundObserver", expirationHandler: {
self.endBackgroundTask() self.endBackgroundTask()
}) })
} }
@ -65,18 +65,18 @@ class BackgroundObserver: NSObject, OperationObserver {
private func endBackgroundTask() { private func endBackgroundTask() {
if identifier != UIBackgroundTaskInvalid { if identifier != UIBackgroundTaskInvalid {
UIApplication.sharedApplication().endBackgroundTask(identifier) UIApplication.shared.endBackgroundTask(identifier)
identifier = UIBackgroundTaskInvalid identifier = UIBackgroundTaskInvalid
} }
} }
// MARK: Operation Observer // MARK: Operation Observer
func operationDidStart(operation: Operation) { } func operationDidStart(operation: EarthquakeOperation) { }
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { } func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) { }
func operationDidFinish(operation: Operation, errors: [NSError]) { func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
endBackgroundTask() endBackgroundTask()
} }
} }

View file

@ -11,12 +11,12 @@ import Foundation
class DownloadEarthquakesOperation: GroupOperation { class DownloadEarthquakesOperation: GroupOperation {
// MARK: Properties // MARK: Properties
let cacheFile: NSURL let cacheFile: URL
// MARK: Initialization // MARK: Initialization
/// - parameter cacheFile: The file `NSURL` to which the earthquake feed will be downloaded. /// - parameter cacheFile: The file `URL` to which the earthquake feed will be downloaded.
init(cacheFile: NSURL) { init(cacheFile: URL) {
self.cacheFile = cacheFile self.cacheFile = cacheFile
super.init(operations: []) super.init(operations: [])
name = "Download Earthquakes" name = "Download Earthquakes"
@ -29,43 +29,43 @@ class DownloadEarthquakesOperation: GroupOperation {
or when the services you use offer secure communication options, you or when the services you use offer secure communication options, you
should always prefer to use https. should always prefer to use https.
*/ */
let url = NSURL(string: "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson")! let url = URL(string: "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson")!
let task = NSURLSession.sharedSession().downloadTaskWithURL(url) { url, response, error in let task = URLSession.shared.downloadTask(with: url) { url, response, error in
self.downloadFinished(url, response: response as? NSHTTPURLResponse, error: error) self.downloadFinished(url: url, response: response as? HTTPURLResponse, error: error as NSError?)
} }
let taskOperation = URLSessionTaskOperation(task: task) let taskOperation = URLSessionTaskOperation(task: task)
let reachabilityCondition = ReachabilityCondition(host: url) let reachabilityCondition = ReachabilityCondition(host: url)
taskOperation.addCondition(reachabilityCondition) taskOperation.addCondition(condition: reachabilityCondition)
let networkObserver = NetworkObserver() let networkObserver = NetworkObserver()
taskOperation.addObserver(networkObserver) taskOperation.addObserver(observer: networkObserver)
addOperation(taskOperation) addOperation(operation: taskOperation)
} }
func downloadFinished(url: NSURL?, response: NSHTTPURLResponse?, error: NSError?) { func downloadFinished(url: URL?, response: HTTPURLResponse?, error: NSError?) {
if let localURL = url { if let localURL = url {
do { do {
/* /*
If we already have a file at this location, just delete it. If we already have a file at this location, just delete it.
Also, swallow the error, because we don't really care about it. Also, swallow the error, because we don't really care about it.
*/ */
try NSFileManager.defaultManager().removeItemAtURL(cacheFile) try FileManager.default.removeItem(at: cacheFile)
} }
catch { } catch { }
do { do {
try NSFileManager.defaultManager().moveItemAtURL(localURL, toURL: cacheFile) try FileManager.default.moveItem(at: localURL, to: cacheFile)
} }
catch let error as NSError { catch let error as NSError {
aggregateError(error) aggregateError(error: error)
} }
} }
else if let error = error { else if let error = error {
aggregateError(error) aggregateError(error: error)
} }
else { else {
// Do nothing, and the operation will automatically finish. // Do nothing, and the operation will automatically finish.

View file

@ -21,37 +21,37 @@ class Earthquake: NSManagedObject {
// MARK: Formatters // MARK: Formatters
static let timestampFormatter: NSDateFormatter = { static let timestampFormatter: DateFormatter = {
let timestampFormatter = NSDateFormatter() let timestampFormatter = DateFormatter()
timestampFormatter.dateStyle = .MediumStyle timestampFormatter.dateStyle = .medium
timestampFormatter.timeStyle = .MediumStyle timestampFormatter.timeStyle = .medium
return timestampFormatter return timestampFormatter
}() }()
static let magnitudeFormatter: NSNumberFormatter = { static let magnitudeFormatter: NumberFormatter = {
let magnitudeFormatter = NSNumberFormatter() let magnitudeFormatter = NumberFormatter()
magnitudeFormatter.numberStyle = .DecimalStyle magnitudeFormatter.numberStyle = .decimal
magnitudeFormatter.maximumFractionDigits = 1 magnitudeFormatter.maximumFractionDigits = 1
magnitudeFormatter.minimumFractionDigits = 1 magnitudeFormatter.minimumFractionDigits = 1
return magnitudeFormatter return magnitudeFormatter
}() }()
static let depthFormatter: NSLengthFormatter = { static let depthFormatter: LengthFormatter = {
let depthFormatter = NSLengthFormatter() let depthFormatter = LengthFormatter()
depthFormatter.forPersonHeightUse = false depthFormatter.isForPersonHeightUse = false
return depthFormatter return depthFormatter
}() }()
static let distanceFormatter: NSLengthFormatter = { static let distanceFormatter: LengthFormatter = {
let distanceFormatter = NSLengthFormatter() let distanceFormatter = LengthFormatter()
distanceFormatter.forPersonHeightUse = false distanceFormatter.isForPersonHeightUse = false
distanceFormatter.numberFormatter.maximumFractionDigits = 2 distanceFormatter.numberFormatter.maximumFractionDigits = 2
return distanceFormatter return distanceFormatter
@ -64,7 +64,7 @@ class Earthquake: NSManagedObject {
@NSManaged var longitude: Double @NSManaged var longitude: Double
@NSManaged var name: String @NSManaged var name: String
@NSManaged var magnitude: Double @NSManaged var magnitude: Double
@NSManaged var timestamp: NSDate @NSManaged var timestamp: Date
@NSManaged var depth: Double @NSManaged var depth: Double
@NSManaged var webLink: String @NSManaged var webLink: String
@ -73,6 +73,6 @@ class Earthquake: NSManagedObject {
} }
var location: CLLocation { var location: CLLocation {
return CLLocation(coordinate: coordinate, altitude: -depth, horizontalAccuracy: kCLLocationAccuracyBest, verticalAccuracy: kCLLocationAccuracyBest, timestamp: timestamp) return CLLocation(coordinate: coordinate, altitude: -depth, horizontalAccuracy: kCLLocationAccuracyBest, verticalAccuracy: kCLLocationAccuracyBest, timestamp: timestamp as Date)
} }
} }

View file

@ -19,9 +19,9 @@ class EarthquakeTableViewCell: UITableViewCell {
// MARK: Configuration // MARK: Configuration
func configure(earthquake: Earthquake) { func configure(earthquake: Earthquake) {
timestampLabel.text = Earthquake.timestampFormatter.stringFromDate(earthquake.timestamp) timestampLabel.text = Earthquake.timestampFormatter.string(for: earthquake.timestamp)
magnitudeLabel.text = Earthquake.magnitudeFormatter.stringFromNumber(earthquake.magnitude) magnitudeLabel.text = Earthquake.magnitudeFormatter.string(for: earthquake.magnitude)
locationLabel.text = earthquake.name locationLabel.text = earthquake.name

View file

@ -12,7 +12,7 @@ import MapKit
class EarthquakeTableViewController: UITableViewController { class EarthquakeTableViewController: UITableViewController {
// MARK: Properties // MARK: Properties
var queue: OperationQueue? var queue: EarthquakeOperationQueue?
var earthquake: Earthquake? var earthquake: Earthquake?
var locationRequest: LocationOperation? var locationRequest: LocationOperation?
@ -47,9 +47,9 @@ class EarthquakeTableViewController: UITableViewController {
map.addAnnotation(annotation) map.addAnnotation(annotation)
nameLabel.text = earthquake.name nameLabel.text = earthquake.name
magnitudeLabel.text = Earthquake.magnitudeFormatter.stringFromNumber(earthquake.magnitude) magnitudeLabel.text = Earthquake.magnitudeFormatter.string(from: NSNumber(value: earthquake.magnitude))
depthLabel.text = Earthquake.depthFormatter.stringFromMeters(earthquake.depth) depthLabel.text = Earthquake.depthFormatter.string(fromMeters: earthquake.depth)
timeLabel.text = Earthquake.timestampFormatter.stringFromDate(earthquake.timestamp) timeLabel.text = Earthquake.timestampFormatter.string(from: earthquake.timestamp as Date)
/* /*
We can use a `LocationOperation` to retrieve the user's current location. We can use a `LocationOperation` to retrieve the user's current location.
@ -62,8 +62,8 @@ class EarthquakeTableViewController: UITableViewController {
*/ */
let locationOperation = LocationOperation(accuracy: kCLLocationAccuracyKilometer) { location in let locationOperation = LocationOperation(accuracy: kCLLocationAccuracyKilometer) { location in
if let earthquakeLocation = self.earthquake?.location { if let earthquakeLocation = self.earthquake?.location {
let distance = location.distanceFromLocation(earthquakeLocation) let distance = location.distance(from: earthquakeLocation)
self.distanceLabel.text = Earthquake.distanceFormatter.stringFromMeters(distance) self.distanceLabel.text = Earthquake.distanceFormatter.string(fromMeters: distance)
} }
self.locationRequest = nil self.locationRequest = nil
@ -73,7 +73,7 @@ class EarthquakeTableViewController: UITableViewController {
locationRequest = locationOperation locationRequest = locationOperation
} }
override func viewWillDisappear(animated: Bool) { override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated) super.viewWillDisappear(animated)
// If the LocationOperation is still going on, then cancel it. // If the LocationOperation is still going on, then cancel it.
locationRequest?.cancel() locationRequest?.cancel()
@ -81,29 +81,29 @@ class EarthquakeTableViewController: UITableViewController {
@IBAction func shareEarthquake(sender: UIBarButtonItem) { @IBAction func shareEarthquake(sender: UIBarButtonItem) {
guard let earthquake = earthquake else { return } guard let earthquake = earthquake else { return }
guard let url = NSURL(string: earthquake.webLink) else { return } guard let url = URL(string: earthquake.webLink) else { return }
let location = earthquake.location let location = earthquake.location
let items = [url, location] let items = [url, location] as [Any]
/* /*
We could present the share sheet manually, but by putting it inside We could present the share sheet manually, but by putting it inside
an `Operation`, we can make it mutually exclusive with other operations an `Operation`, we can make it mutually exclusive with other operations
that modify the view controller hierarchy. that modify the view controller hierarchy.
*/ */
let shareOperation = EarthquakeBlockOperation { (continuation: Void -> Void) in let shareOperation = EarthquakeBlockOperation { (continuation: @escaping () -> Void) in
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
let shareSheet = UIActivityViewController(activityItems: items, applicationActivities: nil) let shareSheet = UIActivityViewController(activityItems: items, applicationActivities: nil)
shareSheet.popoverPresentationController?.barButtonItem = sender shareSheet.popoverPresentationController?.barButtonItem = sender
shareSheet.completionWithItemsHandler = { _ in shareSheet.completionWithItemsHandler = { _, _, _, _ in
// End the operation when the share sheet completes. // End the operation when the share sheet completes.
continuation() continuation()
} }
self.presentViewController(shareSheet, animated: true, completion: nil) self.present(shareSheet, animated: true, completion: nil)
} }
} }
@ -111,15 +111,15 @@ class EarthquakeTableViewController: UITableViewController {
Indicate that this operation modifies the View Controller hierarchy Indicate that this operation modifies the View Controller hierarchy
and is thus mutually exclusive. and is thus mutually exclusive.
*/ */
shareOperation.addCondition(MutuallyExclusive<UIViewController>()) shareOperation.addCondition(condition: MutuallyExclusive<UIViewController>())
queue?.addOperation(shareOperation) queue?.addOperation(shareOperation)
} }
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 1 && indexPath.row == 0 { if indexPath.section == 1 && indexPath.row == 0 {
// The user has tapped the "More Information" button. // The user has tapped the "More Information" button.
if let link = earthquake?.webLink, url = NSURL(string: link) { if let link = earthquake?.webLink, let url = URL(string: link) {
// If we have a link, present the "More Information" dialog. // If we have a link, present the "More Information" dialog.
let moreInformation = MoreInformationOperation(URL: url) let moreInformation = MoreInformationOperation(URL: url)
@ -134,28 +134,28 @@ class EarthquakeTableViewController: UITableViewController {
} }
} }
tableView.deselectRowAtIndexPath(indexPath, animated: true) tableView.deselectRow(at: indexPath as IndexPath, animated: true)
} }
} }
extension EarthquakeTableViewController: MKMapViewDelegate { extension EarthquakeTableViewController: MKMapViewDelegate {
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? { func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard let earthquake = earthquake else { return nil } guard let earthquake = earthquake else { return nil }
var view = mapView.dequeueReusableAnnotationViewWithIdentifier("pin") as? MKPinAnnotationView var view = mapView.dequeueReusableAnnotationView(withIdentifier: "pin") as? MKPinAnnotationView
view = view ?? MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin") view = view ?? MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
guard let pin = view else { return nil } guard let pin = view else { return nil }
switch earthquake.magnitude { switch earthquake.magnitude {
case 0..<3: pin.pinTintColor = UIColor.grayColor() case 0..<3: pin.pinTintColor = UIColor.gray
case 3..<4: pin.pinTintColor = UIColor.blueColor() case 3..<4: pin.pinTintColor = UIColor.blue
case 4..<5: pin.pinTintColor = UIColor.orangeColor() case 4..<5: pin.pinTintColor = UIColor.orange
default: pin.pinTintColor = UIColor.redColor() default: pin.pinTintColor = UIColor.red
} }
pin.enabled = false pin.isEnabled = false
return pin return pin
} }

View file

@ -13,9 +13,9 @@ import CloudKit
class EarthquakesTableViewController: UITableViewController { class EarthquakesTableViewController: UITableViewController {
// MARK: Properties // MARK: Properties
var fetchedResultsController: NSFetchedResultsController? var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>?
let operationQueue = OperationQueue() let operationQueue = EarthquakeOperationQueue()
// MARK: View Controller // MARK: View Controller
@ -24,14 +24,14 @@ class EarthquakesTableViewController: UITableViewController {
let operation = LoadModelOperation { context in let operation = LoadModelOperation { context in
// Now that we have a context, build our `FetchedResultsController`. // Now that we have a context, build our `FetchedResultsController`.
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
let request = NSFetchRequest(entityName: Earthquake.entityName) let request = NSFetchRequest<NSFetchRequestResult>(entityName: Earthquake.entityName)
request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)] request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
request.fetchLimit = 100 request.fetchLimit = 100
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil) let controller = NSFetchedResultsController<NSFetchRequestResult>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
self.fetchedResultsController = controller self.fetchedResultsController = controller
@ -42,27 +42,27 @@ class EarthquakesTableViewController: UITableViewController {
operationQueue.addOperation(operation) operationQueue.addOperation(operation)
} }
override func numberOfSectionsInTableView(tableView: UITableView) -> Int { override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController?.sections?.count ?? 0 return fetchedResultsController?.sections?.count ?? 0
} }
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section = fetchedResultsController?.sections?[section] let section = fetchedResultsController?.sections?[section]
return section?.numberOfObjects ?? 0 return section?.numberOfObjects ?? 0
} }
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("earthquakeCell", forIndexPath: indexPath) as! EarthquakeTableViewCell let cell = tableView.dequeueReusableCell(withIdentifier: "earthquakeCell", for: indexPath as IndexPath) as! EarthquakeTableViewCell
if let earthquake = fetchedResultsController?.objectAtIndexPath(indexPath) as? Earthquake { if let earthquake = fetchedResultsController?.object(at: indexPath) as? Earthquake {
cell.configure(earthquake) cell.configure(earthquake: earthquake)
} }
return cell return cell
} }
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
/* /*
Instead of performing the segue directly, we can wrap it in a Instead of performing the segue directly, we can wrap it in a
`EarthquakeBlockOperation`. This allows us to attach conditions to the `EarthquakeBlockOperation`. This allows us to attach conditions to the
@ -83,37 +83,37 @@ class EarthquakesTableViewController: UITableViewController {
*/ */
let operation = EarthquakeBlockOperation { let operation = EarthquakeBlockOperation {
self.performSegueWithIdentifier("showEarthquake", sender: nil) self.performSegue(withIdentifier: "showEarthquake", sender: nil)
} }
operation.addCondition(MutuallyExclusive<UIViewController>()) operation.addCondition(condition: MutuallyExclusive<UIViewController>())
let blockObserver = BlockObserver { _, errors in let blockObserver = BlockObserver(finishHandler: { _, errors in
/* /*
If the operation errored (ex: a condition failed) then the segue If the operation errored (ex: a condition failed) then the segue
isn't going to happen. We shouldn't leave the row selected. isn't going to happen. We shouldn't leave the row selected.
*/ */
if !errors.isEmpty { if !errors.isEmpty {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
tableView.deselectRowAtIndexPath(indexPath, animated: true) tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
} }
} }
})
operation.addObserver(blockObserver) operation.addObserver(observer: blockObserver)
operationQueue.addOperation(operation) operationQueue.addOperation(operation)
} }
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let navigationVC = segue.destinationViewController as? UINavigationController, guard let navigationVC = segue.destination as? UINavigationController,
detailVC = navigationVC.viewControllers.first as? EarthquakeTableViewController else { let detailVC = navigationVC.viewControllers.first as? EarthquakeTableViewController else {
return return
} }
detailVC.queue = operationQueue detailVC.queue = operationQueue
if let indexPath = tableView.indexPathForSelectedRow { if let indexPath = tableView.indexPathForSelectedRow {
detailVC.earthquake = fetchedResultsController?.objectAtIndexPath(indexPath) as? Earthquake detailVC.earthquake = fetchedResultsController?.object(at: indexPath) as? Earthquake
} }
} }
@ -124,7 +124,7 @@ class EarthquakesTableViewController: UITableViewController {
private func getEarthquakes(userInitiated: Bool = true) { private func getEarthquakes(userInitiated: Bool = true) {
if let context = fetchedResultsController?.managedObjectContext { if let context = fetchedResultsController?.managedObjectContext {
let getEarthquakesOperation = GetEarthquakesOperation(context: context) { let getEarthquakesOperation = GetEarthquakesOperation(context: context) {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
self.refreshControl?.endRefreshing() self.refreshControl?.endRefreshing()
self.updateUI() self.updateUI()
} }
@ -138,8 +138,7 @@ class EarthquakesTableViewController: UITableViewController {
We don't have a context to operate on, so wait a bit and just make We don't have a context to operate on, so wait a bit and just make
the refresh control end. the refresh control end.
*/ */
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.3 * Double(NSEC_PER_SEC))) DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
dispatch_after(when, dispatch_get_main_queue()) {
self.refreshControl?.endRefreshing() self.refreshControl?.endRefreshing()
} }
} }

View file

@ -25,10 +25,10 @@ class GetEarthquakesOperation: GroupOperation {
parsing are complete. This handler will be parsing are complete. This handler will be
invoked on an arbitrary queue. invoked on an arbitrary queue.
*/ */
init(context: NSManagedObjectContext, completionHandler: Void -> Void) { init(context: NSManagedObjectContext, completionHandler: @escaping () -> Void) {
let cachesFolder = try! NSFileManager.defaultManager().URLForDirectory(.CachesDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true) let cachesFolder = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let cacheFile = cachesFolder.URLByAppendingPathComponent("earthquakes.json") let cacheFile = cachesFolder.appendingPathComponent("earthquakes.json")
/* /*
This operation is made of three child operations: This operation is made of three child operations:
@ -39,7 +39,7 @@ class GetEarthquakesOperation: GroupOperation {
downloadOperation = DownloadEarthquakesOperation(cacheFile: cacheFile) downloadOperation = DownloadEarthquakesOperation(cacheFile: cacheFile)
parseOperation = ParseEarthquakesOperation(cacheFile: cacheFile, context: context) parseOperation = ParseEarthquakesOperation(cacheFile: cacheFile, context: context)
let finishOperation = NSBlockOperation(block: completionHandler) let finishOperation = EarthquakeBlockOperation(block: { _ in completionHandler() })
// These operations must be executed in order // These operations must be executed in order
parseOperation.addDependency(downloadOperation) parseOperation.addDependency(downloadOperation)
@ -50,9 +50,9 @@ class GetEarthquakesOperation: GroupOperation {
name = "Get Earthquakes" name = "Get Earthquakes"
} }
override func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) { override func operationDidFinish(operation: Operation, withErrors errors: [NSError]) {
if let firstError = errors.first where (operation === downloadOperation || operation === parseOperation) { if let firstError = errors.first, (operation === downloadOperation || operation === parseOperation) {
produceAlert(firstError) produceAlert(error: firstError)
} }
} }
@ -75,7 +75,7 @@ class GetEarthquakesOperation: GroupOperation {
switch errorReason { switch errorReason {
case failedReachability: case failedReachability:
// We failed because the network isn't reachable. // We failed because the network isn't reachable.
let hostURL = error.userInfo[ReachabilityCondition.hostKey] as! NSURL let hostURL = error.userInfo[ReachabilityCondition.hostKey] as! URL
alert.title = "Unable to Connect" alert.title = "Unable to Connect"
alert.message = "Cannot connect to \(hostURL.host!). Make sure your device is connected to the internet and try again." alert.message = "Cannot connect to \(hostURL.host!). Make sure your device is connected to the internet and try again."
@ -89,7 +89,7 @@ class GetEarthquakesOperation: GroupOperation {
return return
} }
produceOperation(alert) produceOperation(operation: alert)
hasProducedAlert = true hasProducedAlert = true
} }
} }

View file

@ -1,86 +1,111 @@
{ {
"images" : [ "images" : [
{ {
"size" : "29x29",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "29@2x-2.png", "filename" : "29@2x-2.png",
"scale" : "2x" "idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
}, },
{ {
"size" : "29x29",
"idiom" : "iphone",
"filename" : "29@3x.png", "filename" : "29@3x.png",
"scale" : "3x" "idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
}, },
{ {
"size" : "40x40",
"idiom" : "iphone",
"filename" : "40@2x-2.png", "filename" : "40@2x-2.png",
"scale" : "2x" "idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
}, },
{ {
"size" : "40x40",
"idiom" : "iphone",
"filename" : "40@3x.png", "filename" : "40@3x.png",
"scale" : "3x" "idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
}, },
{ {
"size" : "60x60",
"idiom" : "iphone",
"filename" : "60@2x.png", "filename" : "60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone", "idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "60@3x.png", "filename" : "60@3x.png",
"scale" : "3x" "idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
}, },
{ {
"size" : "29x29",
"idiom" : "ipad", "idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "29.png", "filename" : "29.png",
"scale" : "1x" "idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
}, },
{ {
"size" : "29x29",
"idiom" : "ipad",
"filename" : "29@2x-1.png", "filename" : "29@2x-1.png",
"scale" : "2x" "idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
}, },
{ {
"size" : "40x40",
"idiom" : "ipad",
"filename" : "40.png", "filename" : "40.png",
"scale" : "1x" "idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
}, },
{ {
"size" : "40x40",
"idiom" : "ipad",
"filename" : "40@2x-1.png", "filename" : "40@2x-1.png",
"scale" : "2x" "idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
}, },
{ {
"size" : "76x76",
"idiom" : "ipad",
"filename" : "76.png", "filename" : "76.png",
"scale" : "1x" "idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
}, },
{ {
"size" : "76x76",
"idiom" : "ipad",
"filename" : "76@2x.png", "filename" : "76@2x.png",
"scale" : "2x" "idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
}, },
{ {
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "83.5@2x.png", "filename" : "83.5@2x.png",
"scale" : "2x" "idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
} }
], ],
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
} }
} }

View file

@ -12,20 +12,20 @@ import CoreData
An `Operation` subclass that loads the Core Data stack. If this operation fails, An `Operation` subclass that loads the Core Data stack. If this operation fails,
it will produce an `AlertOperation` that will offer to retry the operation. it will produce an `AlertOperation` that will offer to retry the operation.
*/ */
class LoadModelOperation: Operation { class LoadModelOperation: EarthquakeOperation {
// MARK: Properties // MARK: Properties
let loadHandler: NSManagedObjectContext -> Void let loadHandler: (NSManagedObjectContext) -> Void
// MARK: Initialization // MARK: Initialization
init(loadHandler: NSManagedObjectContext -> Void) { init(loadHandler: @escaping (NSManagedObjectContext) -> Void) {
self.loadHandler = loadHandler self.loadHandler = loadHandler
super.init() super.init()
// We only want one of these going at a time. // We only want one of these going at a time.
addCondition(MutuallyExclusive<LoadModelOperation>()) addCondition(condition: MutuallyExclusive<LoadModelOperation>())
} }
override func execute() { override func execute() {
@ -34,9 +34,9 @@ class LoadModelOperation: Operation {
get the Caches directory, then your entire sandbox is broken and get the Caches directory, then your entire sandbox is broken and
there's nothing we can possibly do to fix it. there's nothing we can possibly do to fix it.
*/ */
let cachesFolder = try! NSFileManager.defaultManager().URLForDirectory(.CachesDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true) let cachesFolder = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let storeURL = cachesFolder.URLByAppendingPathComponent("earthquakes.sqlite") let storeURL = cachesFolder.appendingPathComponent("earthquakes.sqlite")
/* /*
Force unwrap this model, because this would only fail if we haven't Force unwrap this model, because this would only fail if we haven't
@ -44,14 +44,14 @@ class LoadModelOperation: Operation {
we deserve to crash. Plus, there's really no easy way to recover from we deserve to crash. Plus, there's really no easy way to recover from
a missing model without reconstructing it programmatically a missing model without reconstructing it programmatically
*/ */
let model = NSManagedObjectModel.mergedModelFromBundles(nil)! let model = NSManagedObjectModel.mergedModel(from: nil)!
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model) let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType) let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.persistentStoreCoordinator = persistentStoreCoordinator context.persistentStoreCoordinator = persistentStoreCoordinator
var error = createStore(persistentStoreCoordinator, atURL: storeURL) var error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
if persistentStoreCoordinator.persistentStores.isEmpty { if persistentStoreCoordinator.persistentStores.isEmpty {
/* /*
@ -59,14 +59,14 @@ class LoadModelOperation: Operation {
is why it's in the Caches folder). If we fail to add it, we can is why it's in the Caches folder). If we fail to add it, we can
delete it and try again. delete it and try again.
*/ */
destroyStore(persistentStoreCoordinator, atURL: storeURL) destroyStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
error = createStore(persistentStoreCoordinator, atURL: storeURL) error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: storeURL)
} }
if persistentStoreCoordinator.persistentStores.isEmpty { if persistentStoreCoordinator.persistentStores.isEmpty {
print("Error creating SQLite store: \(error).") print("Error creating SQLite store: \(String(describing: error)).")
print("Falling back to `.InMemory` store.") print("Falling back to `.InMemory` store.")
error = createStore(persistentStoreCoordinator, atURL: nil, type: NSInMemoryStoreType) error = createStore(persistentStoreCoordinator: persistentStoreCoordinator, atURL: nil, type: NSInMemoryStoreType)
} }
if !persistentStoreCoordinator.persistentStores.isEmpty { if !persistentStoreCoordinator.persistentStores.isEmpty {
@ -74,13 +74,13 @@ class LoadModelOperation: Operation {
error = nil error = nil
} }
finishWithError(error) finishWithError(error: error)
} }
private func createStore(persistentStoreCoordinator: NSPersistentStoreCoordinator, atURL URL: NSURL?, type: String = NSSQLiteStoreType) -> NSError? { private func createStore(persistentStoreCoordinator: NSPersistentStoreCoordinator, atURL URL: URL?, type: String = NSSQLiteStoreType) -> NSError? {
var error: NSError? var error: NSError?
do { do {
let _ = try persistentStoreCoordinator.addPersistentStoreWithType(type, configuration: nil, URL: URL, options: nil) let _ = try persistentStoreCoordinator.addPersistentStore(ofType: type, configurationName: nil, at: URL as URL?, options: nil)
} }
catch let storeError as NSError { catch let storeError as NSError {
error = storeError error = storeError
@ -89,15 +89,15 @@ class LoadModelOperation: Operation {
return error return error
} }
private func destroyStore(persistentStoreCoordinator: NSPersistentStoreCoordinator, atURL URL: NSURL, type: String = NSSQLiteStoreType) { private func destroyStore(persistentStoreCoordinator: NSPersistentStoreCoordinator, atURL URL: URL, type: String = NSSQLiteStoreType) {
do { do {
let _ = try persistentStoreCoordinator.destroyPersistentStoreAtURL(URL, withType: type, options: nil) let _ = try persistentStoreCoordinator.destroyPersistentStore(at: URL as URL, ofType: type, options: nil)
} }
catch { } catch { }
} }
override func finished(errors: [NSError]) { override func finished(errors: [NSError]) {
guard let firstError = errors.first where userInitiated else { return } guard let firstError = errors.first, userInitiated else { return }
/* /*
We failed to load the model on a user initiated operation try and present We failed to load the model on a user initiated operation try and present
@ -111,7 +111,7 @@ class LoadModelOperation: Operation {
alert.message = "An error occurred while loading the database. \(firstError.localizedDescription). Please try again later." alert.message = "An error occurred while loading the database. \(firstError.localizedDescription). Please try again later."
// No custom action for this button. // No custom action for this button.
alert.addAction("Retry Later", style: .Cancel) alert.addAction(title: "Retry Later", style: .cancel)
// Declare this as a local variable to avoid capturing self in the closure below. // Declare this as a local variable to avoid capturing self in the closure below.
let handler = loadHandler let handler = loadHandler
@ -123,14 +123,14 @@ class LoadModelOperation: Operation {
simply by creating a new copy of the operation and giving it the same simply by creating a new copy of the operation and giving it the same
loadHandler. loadHandler.
*/ */
alert.addAction("Retry Now") { alertOperation in alert.addAction(title: "Retry Now") { alertOperation in
let retryOperation = LoadModelOperation(loadHandler: handler) let retryOperation = LoadModelOperation(loadHandler: handler)
retryOperation.userInitiated = true retryOperation.userInitiated = true
alertOperation.produceOperation(retryOperation) alertOperation.produceOperation(operation: retryOperation)
} }
produceOperation(alert) produceOperation(operation: alert)
} }
} }

View file

@ -9,35 +9,35 @@ This file contains the code to present more information about an earthquake as a
import Foundation import Foundation
import SafariServices import SafariServices
/// An `Operation` to display an `NSURL` in an app-modal `SFSafariViewController`. /// An `Operation` to display an `URL` in an app-modal `SFSafariViewController`.
class MoreInformationOperation: Operation { class MoreInformationOperation: EarthquakeOperation {
// MARK: Properties // MARK: Properties
let URL: NSURL let URL: URL
// MARK: Initialization // MARK: Initialization
init(URL: NSURL) { init(URL: URL) {
self.URL = URL self.URL = URL
super.init() super.init()
addCondition(MutuallyExclusive<UIViewController>()) addCondition(condition: MutuallyExclusive<UIViewController>())
} }
// MARK: Overrides // MARK: Overrides
override func execute() { override func execute() {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
self.showSafariViewController() self.showSafariViewController()
} }
} }
private func showSafariViewController() { private func showSafariViewController() {
if let context = UIApplication.sharedApplication().keyWindow?.rootViewController { if let context = UIApplication.shared.keyWindow?.rootViewController {
let safari = SFSafariViewController(URL: URL, entersReaderIfAvailable: false) let safari = SFSafariViewController(url: URL, entersReaderIfAvailable: false)
safari.delegate = self safari.delegate = self
context.presentViewController(safari, animated: true, completion: nil) context.present(safari, animated: true, completion: nil)
} }
else { else {
finish() finish()
@ -46,8 +46,8 @@ class MoreInformationOperation: Operation {
} }
extension MoreInformationOperation: SFSafariViewControllerDelegate { extension MoreInformationOperation: SFSafariViewControllerDelegate {
func safariViewControllerDidFinish(controller: SFSafariViewController) { func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
controller.dismissViewControllerAnimated(true) { controller.dismiss(animated: true) {
self.finish() self.finish()
} }
} }

View file

@ -17,17 +17,17 @@ struct NetworkObserver: OperationObserver {
init() { } init() { }
func operationDidStart(operation: Operation) { func operationDidStart(operation: EarthquakeOperation) {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
// Increment the network indicator's "reference count" // Increment the network indicator's "reference count"
NetworkIndicatorController.sharedIndicatorController.networkActivityDidStart() NetworkIndicatorController.sharedIndicatorController.networkActivityDidStart()
} }
} }
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { } func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) { }
func operationDidFinish(operation: Operation, errors: [NSError]) { func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
// Decrement the network indicator's "reference count". // Decrement the network indicator's "reference count".
NetworkIndicatorController.sharedIndicatorController.networkActivityDidEnd() NetworkIndicatorController.sharedIndicatorController.networkActivityDidEnd()
} }
@ -48,7 +48,7 @@ private class NetworkIndicatorController {
// MARK: Methods // MARK: Methods
func networkActivityDidStart() { func networkActivityDidStart() {
assert(NSThread.isMainThread(), "Altering network activity indicator state can only be done on the main thread.") assert(Thread.isMainThread, "Altering network activity indicator state can only be done on the main thread.")
activityCount += 1 activityCount += 1
@ -56,7 +56,7 @@ private class NetworkIndicatorController {
} }
func networkActivityDidEnd() { func networkActivityDidEnd() {
assert(NSThread.isMainThread(), "Altering network activity indicator state can only be done on the main thread.") assert(Thread.isMainThread, "Altering network activity indicator state can only be done on the main thread.")
activityCount -= 1 activityCount -= 1
@ -73,7 +73,7 @@ private class NetworkIndicatorController {
hiding of the indicator by one second. This provides the chance hiding of the indicator by one second. This provides the chance
to come in and invalidate the timer before it fires. to come in and invalidate the timer before it fires.
*/ */
visibilityTimer = Timer(interval: 1.0) { visibilityTimer = Timer(interval: 1) {
self.hideIndicator() self.hideIndicator()
} }
} }
@ -82,13 +82,13 @@ private class NetworkIndicatorController {
private func showIndicator() { private func showIndicator() {
visibilityTimer?.cancel() visibilityTimer?.cancel()
visibilityTimer = nil visibilityTimer = nil
UIApplication.sharedApplication().networkActivityIndicatorVisible = true UIApplication.shared.isNetworkActivityIndicatorVisible = true
} }
private func hideIndicator() { private func hideIndicator() {
visibilityTimer?.cancel() visibilityTimer?.cancel()
visibilityTimer = nil visibilityTimer = nil
UIApplication.sharedApplication().networkActivityIndicatorVisible = false UIApplication.shared.isNetworkActivityIndicatorVisible = false
} }
} }
@ -100,10 +100,8 @@ class Timer {
// MARK: Initialization // MARK: Initialization
init(interval: NSTimeInterval, handler: dispatch_block_t) { init(interval: Int, handler: @escaping () -> Void) {
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC))) DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(interval)) { [weak self] in
dispatch_after(when, dispatch_get_main_queue()) { [weak self] in
if self?.isCancelled == false { if self?.isCancelled == false {
handler() handler()
} }

View file

@ -15,11 +15,11 @@ import Foundation
struct BlockObserver: OperationObserver { struct BlockObserver: OperationObserver {
// MARK: Properties // MARK: Properties
private let startHandler: (Operation -> Void)? private let startHandler: ((EarthquakeOperation) -> Void)?
private let produceHandler: ((Operation, NSOperation) -> Void)? private let produceHandler: ((EarthquakeOperation, Operation) -> Void)?
private let finishHandler: ((Operation, [NSError]) -> Void)? private let finishHandler: ((EarthquakeOperation, [NSError]) -> Void)?
init(startHandler: (Operation -> Void)? = nil, produceHandler: ((Operation, NSOperation) -> Void)? = nil, finishHandler: ((Operation, [NSError]) -> Void)? = nil) { init(startHandler: ((EarthquakeOperation) -> Void)? = nil, produceHandler: ((EarthquakeOperation, Operation) -> Void)? = nil, finishHandler: ((EarthquakeOperation, [NSError]) -> Void)? = nil) {
self.startHandler = startHandler self.startHandler = startHandler
self.produceHandler = produceHandler self.produceHandler = produceHandler
self.finishHandler = finishHandler self.finishHandler = finishHandler
@ -27,15 +27,15 @@ struct BlockObserver: OperationObserver {
// MARK: OperationObserver // MARK: OperationObserver
func operationDidStart(operation: Operation) { func operationDidStart(operation: EarthquakeOperation) {
startHandler?(operation) startHandler?(operation)
} }
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) {
produceHandler?(operation, newOperation) produceHandler?(operation, newOperation)
} }
func operationDidFinish(operation: Operation, errors: [NSError]) { func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
finishHandler?(operation, errors) finishHandler?(operation, errors)
} }
} }

View file

@ -24,8 +24,8 @@ extension CKContainer {
operation fails. If the verification was successful, this value will operation fails. If the verification was successful, this value will
be `nil`. be `nil`.
*/ */
func verifyPermission(permission: CKApplicationPermissions, requestingIfNecessary shouldRequest: Bool = false, completion: NSError? -> Void) { func verifyPermission(permission: CKApplicationPermissions, requestingIfNecessary shouldRequest: Bool = false, completion: @escaping (Error?) -> Void) {
verifyAccountStatus(self, permission: permission, shouldRequest: shouldRequest, completion: completion) verifyAccountStatus(container: self, permission: permission, shouldRequest: shouldRequest, completion: completion)
} }
} }
@ -33,46 +33,46 @@ extension CKContainer {
Make these helper functions instead of helper methods, so we don't pollute Make these helper functions instead of helper methods, so we don't pollute
`CKContainer`. `CKContainer`.
*/ */
private func verifyAccountStatus(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: NSError? -> Void) { private func verifyAccountStatus(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: @escaping (Error?) -> Void) {
container.accountStatusWithCompletionHandler { accountStatus, accountError in container.accountStatus { accountStatus, accountError in
if accountStatus == .Available { if accountStatus == .available {
if permission != [] { if permission != [] {
verifyPermission(container, permission: permission, shouldRequest: shouldRequest, completion: completion) verifyPermission(container: container, permission: permission, shouldRequest: shouldRequest, completion: completion)
} }
else { else {
completion(nil) completion(nil)
} }
} }
else { else {
let error = accountError ?? NSError(domain: CKErrorDomain, code: CKErrorCode.NotAuthenticated.rawValue, userInfo: nil) let error = accountError ?? NSError(domain: CKErrorDomain, code: CKError.Code.notAuthenticated.rawValue, userInfo: nil)
completion(error) completion(error)
} }
} }
} }
private func verifyPermission(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: NSError? -> Void) { private func verifyPermission(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: @escaping (Error?) -> Void) {
container.statusForApplicationPermission(permission) { permissionStatus, permissionError in container.status(forApplicationPermission: permission) { permissionStatus, permissionError in
if permissionStatus == .Granted { if permissionStatus == .granted {
completion(nil) completion(nil)
} }
else if permissionStatus == .InitialState && shouldRequest { else if permissionStatus == .initialState && shouldRequest {
requestPermission(container, permission: permission, completion: completion) requestPermission(container: container, permission: permission, completion: completion)
} }
else { else {
let error = permissionError ?? NSError(domain: CKErrorDomain, code: CKErrorCode.PermissionFailure.rawValue, userInfo: nil) let error = permissionError ?? NSError(domain: CKErrorDomain, code: CKError.Code.permissionFailure.rawValue, userInfo: nil)
completion(error) completion(error)
} }
} }
} }
private func requestPermission(container: CKContainer, permission: CKApplicationPermissions, completion: NSError? -> Void) { private func requestPermission(container: CKContainer, permission: CKApplicationPermissions, completion: @escaping (Error?) -> Void) {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
container.requestApplicationPermission(permission) { requestStatus, requestError in container.requestApplicationPermission(permission) { requestStatus, requestError in
if requestStatus == .Granted { if requestStatus == .granted {
completion(nil) completion(nil)
} }
else { else {
let error = requestError ?? NSError(domain: CKErrorDomain, code: CKErrorCode.PermissionFailure.rawValue, userInfo: nil) let error = requestError ?? NSError(domain: CKErrorDomain, code: CKError.Code.permissionFailure.rawValue, userInfo: nil)
completion(error) completion(error)
} }
} }

View file

@ -21,20 +21,20 @@ struct CalendarCondition: OperationCondition {
self.entityType = entityType self.entityType = entityType
} }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return CalendarPermissionOperation(entityType: entityType) return CalendarPermissionOperation(entityType: entityType)
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
switch EKEventStore.authorizationStatusForEntityType(entityType) { switch EKEventStore.authorizationStatus(for: entityType) {
case .Authorized: case .authorized:
completion(.Satisfied) completion(.Satisfied)
default: default:
// We are not authorized to access entities of this type. // We are not authorized to access entities of this type.
let error = NSError(code: .ConditionFailed, userInfo: [ let error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name, OperationConditionKey: type(of: self).name,
self.dynamicType.entityTypeKey: entityType.rawValue type(of: self).entityTypeKey: entityType.rawValue,
]) ])
completion(.Failed(error)) completion(.Failed(error))
@ -53,22 +53,22 @@ private let SharedEventStore = EKEventStore()
A private `Operation` that will request access to the user's Calendar/Reminders, A private `Operation` that will request access to the user's Calendar/Reminders,
if it has not already been granted. if it has not already been granted.
*/ */
private class CalendarPermissionOperation: Operation { private class CalendarPermissionOperation: EarthquakeOperation {
let entityType: EKEntityType let entityType: EKEntityType
init(entityType: EKEntityType) { init(entityType: EKEntityType) {
self.entityType = entityType self.entityType = entityType
super.init() super.init()
addCondition(AlertPresentation()) addCondition(condition: AlertPresentation())
} }
override func execute() { override func execute() {
let status = EKEventStore.authorizationStatusForEntityType(entityType) let status = EKEventStore.authorizationStatus(for: entityType)
switch status { switch status {
case .NotDetermined: case .notDetermined:
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
SharedEventStore.requestAccessToEntityType(self.entityType) { granted, error in SharedEventStore.requestAccess(to: self.entityType) { granted, error in
self.finish() self.finish()
} }
} }

View file

@ -36,16 +36,16 @@ struct CloudContainerCondition: OperationCondition {
self.permission = permission self.permission = permission
} }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return CloudKitPermissionOperation(container: container, permission: permission) return CloudKitPermissionOperation(container: container, permission: permission)
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
container.verifyPermission(permission, requestingIfNecessary: false) { error in container.verifyPermission(permission: permission, requestingIfNecessary: false) { error in
if let error = error { if let error = error {
let conditionError = NSError(code: .ConditionFailed, userInfo: [ let conditionError = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name, OperationConditionKey: type(of: self).name,
self.dynamicType.containerKey: self.container, type(of: self).containerKey: self.container,
NSUnderlyingErrorKey: error NSUnderlyingErrorKey: error
]) ])
@ -62,7 +62,7 @@ struct CloudContainerCondition: OperationCondition {
This operation asks the user for permission to use CloudKit, if necessary. This operation asks the user for permission to use CloudKit, if necessary.
If permission has already been granted, this operation will quickly finish. If permission has already been granted, this operation will quickly finish.
*/ */
private class CloudKitPermissionOperation: Operation { private class CloudKitPermissionOperation: EarthquakeOperation {
let container: CKContainer let container: CKContainer
let permission: CKApplicationPermissions let permission: CKApplicationPermissions
@ -77,13 +77,13 @@ private class CloudKitPermissionOperation: Operation {
an alert, so it should not run at the same time as anything else an alert, so it should not run at the same time as anything else
that presents an alert. that presents an alert.
*/ */
addCondition(AlertPresentation()) addCondition(condition: AlertPresentation())
} }
} }
override func execute() { override func execute() {
container.verifyPermission(permission, requestingIfNecessary: true) { error in container.verifyPermission(permission: permission, requestingIfNecessary: true) { error in
self.finishWithError(error) self.finishWithError(error: error as NSError?)
} }
} }

View file

@ -20,12 +20,12 @@ import Foundation
If the interval is negative, or the `NSDate` is in the past, then this operation If the interval is negative, or the `NSDate` is in the past, then this operation
immediately finishes. immediately finishes.
*/ */
class DelayOperation: Operation { class DelayOperation: EarthquakeOperation {
// MARK: Types // MARK: Types
private enum Delay { private enum Delay {
case Interval(NSTimeInterval) case Interval(Int)
case Date(NSDate) case Date(Date)
} }
// MARK: Properties // MARK: Properties
@ -34,18 +34,18 @@ class DelayOperation: Operation {
// MARK: Initialization // MARK: Initialization
init(interval: NSTimeInterval) { init(interval: TimeInterval) {
delay = .Interval(interval) delay = .Interval(Int(interval))
super.init() super.init()
} }
init(until date: NSDate) { init(until date: NSDate) {
delay = .Date(date) delay = .Date(date as Date)
super.init() super.init()
} }
override func execute() { override func execute() {
let interval: NSTimeInterval let interval: Int
// Figure out how long we should wait for. // Figure out how long we should wait for.
switch delay { switch delay {
@ -53,7 +53,7 @@ class DelayOperation: Operation {
interval = theInterval interval = theInterval
case .Date(let date): case .Date(let date):
interval = date.timeIntervalSinceNow interval = Int(date.timeIntervalSinceNow)
} }
guard interval > 0 else { guard interval > 0 else {
@ -61,10 +61,9 @@ class DelayOperation: Operation {
return return
} }
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC))) DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + .seconds(interval)) {
dispatch_after(when, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
// If we were cancelled, then finish() has already been called. // If we were cancelled, then finish() has already been called.
if !self.cancelled { if !self.isCancelled {
self.finish() self.finish()
} }
} }

View file

@ -19,9 +19,8 @@ extension Dictionary {
be used as the key for the value in the `Dictionary`. If the closure be used as the key for the value in the `Dictionary`. If the closure
returns `nil`, then the value will be omitted from the `Dictionary`. returns `nil`, then the value will be omitted from the `Dictionary`.
*/ */
init<Sequence: SequenceType where Sequence.Generator.Element == Value>(sequence: Sequence, @noescape keyMapper: Value -> Key?) { init<S: Sequence>(sequence: S, keyMapper: (Value) -> Key?) where S.Element == Value {
self.init() self.init()
for item in sequence { for item in sequence {
if let key = keyMapper(item) { if let key = keyMapper(item) {
self[key] = item self[key] = item

View file

@ -9,10 +9,13 @@ This code shows how to create a simple subclass of Operation.
import Foundation import Foundation
/// A closure type that takes a closure as its parameter. /// A closure type that takes a closure as its parameter.
typealias OperationBlock = (Void -> Void) -> Void typealias OperationBlock = (@escaping () -> Void) -> Void
/// A sublcass of `Operation` to execute a closure. @available(*, deprecated, message: "Use () -> Void directly, it's shorter")
class EarthquakeBlockOperation: Operation { typealias dispatch_block_t = () -> Void
/// A sublcass of `EarthquakeOperation` to execute a closure.
class EarthquakeBlockOperation: EarthquakeOperation {
private let block: OperationBlock? private let block: OperationBlock?
/** /**
@ -37,9 +40,9 @@ class EarthquakeBlockOperation: Operation {
the designated initializer). The operation will be automatically ended the designated initializer). The operation will be automatically ended
after the `mainQueueBlock` is executed. after the `mainQueueBlock` is executed.
*/ */
convenience init(mainQueueBlock: dispatch_block_t) { convenience init(mainQueueBlock: @escaping () -> Void) {
self.init(block: { continuation in self.init(block: { continuation in
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
mainQueueBlock() mainQueueBlock()
continuation() continuation()
} }

View file

@ -14,24 +14,21 @@ import Foundation
extended readiness requirements, as well as notify many interested parties extended readiness requirements, as well as notify many interested parties
about interesting operation state changes about interesting operation state changes
*/ */
class Operation: NSOperation { class EarthquakeOperation: Operation {
// use the KVO mechanism to indicate that changes to "state" affect other properties as well // use the KVO mechanism to indicate that changes to "state" affect other properties as well
class func keyPathsForValuesAffectingIsReady() -> Set<NSObject> { override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
switch key {
case "isExecuting", "isFinished", "isReady":
return ["state"] return ["state"]
default:
return []
} }
class func keyPathsForValuesAffectingIsExecuting() -> Set<NSObject> {
return ["state"]
}
class func keyPathsForValuesAffectingIsFinished() -> Set<NSObject> {
return ["state"]
} }
// MARK: State Management // MARK: State Management
private enum State: Int, Comparable { fileprivate enum State: Int, Comparable {
/// The initial state of an `Operation`. /// The initial state of an `Operation`.
case Initialized case Initialized
@ -111,37 +108,37 @@ class Operation: NSOperation {
acquire the lock, then we'd be stuck waiting on our own lock. It's the acquire the lock, then we'd be stuck waiting on our own lock. It's the
classic definition of deadlock. classic definition of deadlock.
*/ */
willChangeValueForKey("state") willChangeValue(forKey: "state")
stateLock.withCriticalScope { Void -> Void in stateLock.withCriticalScope { () -> Void in
guard _state != .Finished else { guard _state != .Finished else {
return return
} }
assert(_state.canTransitionToState(newState), "Performing invalid state transition.") assert(_state.canTransitionToState(target: newState), "Performing invalid state transition.")
_state = newState _state = newState
} }
didChangeValueForKey("state") didChangeValue(forKey: "state")
} }
} }
// Here is where we extend our definition of "readiness". // Here is where we extend our definition of "readiness".
override var ready: Bool { override var isReady: Bool {
switch state { switch state {
case .Initialized: case .Initialized:
// If the operation has been cancelled, "isReady" should return true // If the operation has been cancelled, "isReady" should return true
return cancelled return isCancelled
case .Pending: case .Pending:
// If the operation has been cancelled, "isReady" should return true // If the operation has been cancelled, "isReady" should return true
guard !cancelled else { guard !isCancelled else {
return true return true
} }
// If super isReady, conditions can be evaluated // If super isReady, conditions can be evaluated
if super.ready { if super.isReady {
evaluateConditions() evaluateConditions()
} }
@ -149,7 +146,7 @@ class Operation: NSOperation {
return false return false
case .Ready: case .Ready:
return super.ready || cancelled return super.isReady || isCancelled
default: default:
return false return false
@ -158,31 +155,31 @@ class Operation: NSOperation {
var userInitiated: Bool { var userInitiated: Bool {
get { get {
return qualityOfService == .UserInitiated return qualityOfService == .userInitiated
} }
set { set {
assert(state < .Executing, "Cannot modify userInitiated after execution has begun.") assert(state < .Executing, "Cannot modify userInitiated after execution has begun.")
qualityOfService = newValue ? .UserInitiated : .Default qualityOfService = newValue ? .userInitiated : .default
} }
} }
override var executing: Bool { override var isExecuting: Bool {
return state == .Executing return state == .Executing
} }
override var finished: Bool { override var isFinished: Bool {
return state == .Finished return state == .Finished
} }
private func evaluateConditions() { private func evaluateConditions() {
assert(state == .Pending && !cancelled, "evaluateConditions() was called out-of-order") assert(state == .Pending && !isCancelled, "evaluateConditions() was called out-of-order")
state = .EvaluatingConditions state = .EvaluatingConditions
OperationConditionEvaluator.evaluate(conditions, operation: self) { failures in OperationConditionEvaluator.evaluate(conditions: conditions, operation: self) { failures in
self._internalErrors.appendContentsOf(failures) self._internalErrors.append(contentsOf: failures)
self.state = .Ready self.state = .Ready
} }
} }
@ -205,7 +202,7 @@ class Operation: NSOperation {
observers.append(observer) observers.append(observer)
} }
override func addDependency(operation: NSOperation) { override func addDependency(_ operation: Operation) {
assert(state < .Executing, "Dependencies cannot be modified after execution has begun.") assert(state < .Executing, "Dependencies cannot be modified after execution has begun.")
super.addDependency(operation) super.addDependency(operation)
@ -218,7 +215,7 @@ class Operation: NSOperation {
super.start() super.start()
// If the operation has been cancelled, we still need to enter the "Finished" state. // If the operation has been cancelled, we still need to enter the "Finished" state.
if cancelled { if isCancelled {
finish() finish()
} }
} }
@ -226,11 +223,11 @@ class Operation: NSOperation {
override final func main() { override final func main() {
assert(state == .Ready, "This operation must be performed on an operation queue.") assert(state == .Ready, "This operation must be performed on an operation queue.")
if _internalErrors.isEmpty && !cancelled { if _internalErrors.isEmpty && !isCancelled {
state = .Executing state = .Executing
for observer in observers { for observer in observers {
observer.operationDidStart(self) observer.operationDidStart(operation: self)
} }
execute() execute()
@ -251,7 +248,7 @@ class Operation: NSOperation {
their readiness state. their readiness state.
*/ */
func execute() { func execute() {
print("\(self.dynamicType) must override `execute()`.") print("\(type(of: self)) must override `execute()`.")
finish() finish()
} }
@ -265,9 +262,9 @@ class Operation: NSOperation {
cancel() cancel()
} }
final func produceOperation(operation: NSOperation) { final func produceOperation(operation: Operation) {
for observer in observers { for observer in observers {
observer.operation(self, didProduceOperation: operation) observer.operation(operation: self, didProduceOperation: operation)
} }
} }
@ -278,12 +275,12 @@ class Operation: NSOperation {
This is a convenience method to simplify calling the actual `finish()` This is a convenience method to simplify calling the actual `finish()`
method. This is also useful if you wish to finish with an error provided method. This is also useful if you wish to finish with an error provided
by the system frameworks. As an example, see `DownloadEarthquakesOperation` by the system frameworks. As an example, see `DownloadEarthquakesOperation`
for how an error from an `NSURLSession` is passed along via the for how an error from an `URLSession` is passed along via the
`finishWithError()` method. `finishWithError()` method.
*/ */
final func finishWithError(error: NSError?) { final func finishWithError(error: NSError?) {
if let error = error { if let error = error {
finish([error]) finish(errors: [error])
} }
else { else {
finish() finish()
@ -301,10 +298,10 @@ class Operation: NSOperation {
state = .Finishing state = .Finishing
let combinedErrors = _internalErrors + errors let combinedErrors = _internalErrors + errors
finished(combinedErrors) finished(errors: combinedErrors)
for observer in observers { for observer in observers {
observer.operationDidFinish(self, errors: combinedErrors) observer.operationDidFinish(operation: self, errors: combinedErrors)
} }
state = .Finished state = .Finished
@ -339,10 +336,10 @@ class Operation: NSOperation {
} }
// Simple operator functions to simplify the assertions used above. // Simple operator functions to simplify the assertions used above.
private func <(lhs: Operation.State, rhs: Operation.State) -> Bool { private func <(lhs: EarthquakeOperation.State, rhs: EarthquakeOperation.State) -> Bool {
return lhs.rawValue < rhs.rawValue return lhs.rawValue < rhs.rawValue
} }
private func ==(lhs: Operation.State, rhs: Operation.State) -> Bool { private func ==(lhs: EarthquakeOperation.State, rhs: EarthquakeOperation.State) -> Bool {
return lhs.rawValue == rhs.rawValue return lhs.rawValue == rhs.rawValue
} }

View file

@ -18,9 +18,9 @@ import Foundation
For example, `GroupOperation` is the delegate of its own internal For example, `GroupOperation` is the delegate of its own internal
`OperationQueue` and uses it to manage dependencies. `OperationQueue` and uses it to manage dependencies.
*/ */
@objc protocol OperationQueueDelegate: NSObjectProtocol { @objc protocol EarthquakeOperationQueueDelegate: NSObjectProtocol {
optional func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation) @objc optional func operationQueue(operationQueue: EarthquakeOperationQueue, willAddOperation operation: Operation)
optional func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError]) @objc optional func operationQueue(operationQueue: EarthquakeOperationQueue, operationDidFinish operation: Operation, withErrors errors: [NSError])
} }
/** /**
@ -31,11 +31,11 @@ import Foundation
- Extracting generated dependencies from operation conditions - Extracting generated dependencies from operation conditions
- Setting up dependencies to enforce mutual exclusivity - Setting up dependencies to enforce mutual exclusivity
*/ */
class OperationQueue: NSOperationQueue { class EarthquakeOperationQueue: OperationQueue {
weak var delegate: OperationQueueDelegate? weak var delegate: EarthquakeOperationQueueDelegate?
override func addOperation(operation: NSOperation) { override func addOperation(_ operation: Operation) {
if let op = operation as? Operation { if let op = operation as? EarthquakeOperation {
// Set up a `BlockObserver` to invoke the `OperationQueueDelegate` method. // Set up a `BlockObserver` to invoke the `OperationQueueDelegate` method.
let delegate = BlockObserver( let delegate = BlockObserver(
startHandler: nil, startHandler: nil,
@ -44,15 +44,15 @@ class OperationQueue: NSOperationQueue {
}, },
finishHandler: { [weak self] in finishHandler: { [weak self] in
if let q = self { if let q = self {
q.delegate?.operationQueue?(q, operationDidFinish: $0, withErrors: $1) q.delegate?.operationQueue?(operationQueue: q, operationDidFinish: $0, withErrors: $1)
} }
} }
) )
op.addObserver(delegate) op.addObserver(observer: delegate)
// Extract any dependencies needed by this operation. // Extract any dependencies needed by this operation.
let dependencies = op.conditions.flatMap { let dependencies = op.conditions.compactMap {
$0.dependencyForOperation(op) $0.dependencyForOperation(operation: op)
} }
for dependency in dependencies { for dependency in dependencies {
@ -65,21 +65,21 @@ class OperationQueue: NSOperationQueue {
With condition dependencies added, we can now see if this needs With condition dependencies added, we can now see if this needs
dependencies to enforce mutual exclusivity. dependencies to enforce mutual exclusivity.
*/ */
let concurrencyCategories: [String] = op.conditions.flatMap { condition in let concurrencyCategories: [String] = op.conditions.compactMap { condition in
if !condition.dynamicType.isMutuallyExclusive { return nil } if !type(of: condition).isMutuallyExclusive { return nil }
return "\(condition.dynamicType)" return "\(type(of: condition))"
} }
if !concurrencyCategories.isEmpty { if !concurrencyCategories.isEmpty {
// Set up the mutual exclusivity dependencies. // Set up the mutual exclusivity dependencies.
let exclusivityController = ExclusivityController.sharedExclusivityController let exclusivityController = ExclusivityController.sharedExclusivityController
exclusivityController.addOperation(op, categories: concurrencyCategories) exclusivityController.addOperation(operation: op, categories: concurrencyCategories)
op.addObserver(BlockObserver { operation, _ in op.addObserver(observer: BlockObserver(finishHandler: { operation, _ in
exclusivityController.removeOperation(operation, categories: concurrencyCategories) exclusivityController.removeOperation(operation: operation, categories: concurrencyCategories)
}) }))
} }
/* /*
@ -98,15 +98,15 @@ class OperationQueue: NSOperationQueue {
*/ */
operation.addCompletionBlock { [weak self, weak operation] in operation.addCompletionBlock { [weak self, weak operation] in
guard let queue = self, let operation = operation else { return } guard let queue = self, let operation = operation else { return }
queue.delegate?.operationQueue?(queue, operationDidFinish: operation, withErrors: []) queue.delegate?.operationQueue?(operationQueue: queue, operationDidFinish: operation, withErrors: [])
} }
} }
delegate?.operationQueue?(self, willAddOperation: operation) delegate?.operationQueue?(operationQueue: self, willAddOperation: operation)
super.addOperation(operation) super.addOperation(operation)
} }
override func addOperations(operations: [NSOperation], waitUntilFinished wait: Bool) { override func addOperations(_ operations: [Operation], waitUntilFinished wait: Bool) {
/* /*
The base implementation of this method does not call `addOperation()`, The base implementation of this method does not call `addOperation()`,
so we'll call it ourselves. so we'll call it ourselves.

View file

@ -17,8 +17,8 @@ import Foundation
class ExclusivityController { class ExclusivityController {
static let sharedExclusivityController = ExclusivityController() static let sharedExclusivityController = ExclusivityController()
private let serialQueue = dispatch_queue_create("Operations.ExclusivityController", DISPATCH_QUEUE_SERIAL) private let serialQueue = DispatchQueue(label: "Operations.ExclusivityController")
private var operations: [String: [Operation]] = [:] private var operations: [String: [EarthquakeOperation]] = [:]
private init() { private init() {
/* /*
@ -28,24 +28,24 @@ class ExclusivityController {
} }
/// Registers an operation as being mutually exclusive /// Registers an operation as being mutually exclusive
func addOperation(operation: Operation, categories: [String]) { func addOperation(operation: EarthquakeOperation, categories: [String]) {
/* /*
This needs to be a synchronous operation. This needs to be a synchronous operation.
If this were async, then we might not get around to adding dependencies If this were async, then we might not get around to adding dependencies
until after the operation had already begun, which would be incorrect. until after the operation had already begun, which would be incorrect.
*/ */
dispatch_sync(serialQueue) { serialQueue.sync {
for category in categories { for category in categories {
self.noqueue_addOperation(operation, category: category) self.noqueue_addOperation(operation: operation, category: category)
} }
} }
} }
/// Unregisters an operation from being mutually exclusive. /// Unregisters an operation from being mutually exclusive.
func removeOperation(operation: Operation, categories: [String]) { func removeOperation(operation: EarthquakeOperation, categories: [String]) {
dispatch_async(serialQueue) { serialQueue.async {
for category in categories { for category in categories {
self.noqueue_removeOperation(operation, category: category) self.noqueue_removeOperation(operation: operation, category: category)
} }
} }
} }
@ -53,7 +53,7 @@ class ExclusivityController {
// MARK: Operation Management // MARK: Operation Management
private func noqueue_addOperation(operation: Operation, category: String) { private func noqueue_addOperation(operation: EarthquakeOperation, category: String) {
var operationsWithThisCategory = operations[category] ?? [] var operationsWithThisCategory = operations[category] ?? []
if let last = operationsWithThisCategory.last { if let last = operationsWithThisCategory.last {
@ -65,13 +65,13 @@ class ExclusivityController {
operations[category] = operationsWithThisCategory operations[category] = operationsWithThisCategory
} }
private func noqueue_removeOperation(operation: Operation, category: String) { private func noqueue_removeOperation(operation: EarthquakeOperation, category: String) {
let matchingOperations = operations[category] let matchingOperations = operations[category]
if var operationsWithThisCategory = matchingOperations, if var operationsWithThisCategory = matchingOperations,
let index = operationsWithThisCategory.indexOf(operation) { let index = operationsWithThisCategory.firstIndex(of: operation) {
operationsWithThisCategory.removeAtIndex(index) operationsWithThisCategory.remove(at: index)
operations[category] = operationsWithThisCategory operations[category] = operationsWithThisCategory
} }
} }

View file

@ -21,21 +21,21 @@ import Foundation
subsequent operations (still within the outer `GroupOperation`) that will all subsequent operations (still within the outer `GroupOperation`) that will all
be executed before the rest of the operations in the initial chain of operations. be executed before the rest of the operations in the initial chain of operations.
*/ */
class GroupOperation: Operation { class GroupOperation: EarthquakeOperation {
private let internalQueue = OperationQueue() private let internalQueue = EarthquakeOperationQueue()
private let startingOperation = NSBlockOperation(block: {}) private let startingOperation = BlockOperation(block: {})
private let finishingOperation = NSBlockOperation(block: {}) private let finishingOperation = BlockOperation(block: {})
private var aggregatedErrors = [NSError]() private var aggregatedErrors = [NSError]()
convenience init(operations: NSOperation...) { convenience init(operations: Operation...) {
self.init(operations: operations) self.init(operations: operations)
} }
init(operations: [NSOperation]) { init(operations: [Operation]) {
super.init() super.init()
internalQueue.suspended = true internalQueue.isSuspended = true
internalQueue.delegate = self internalQueue.delegate = self
internalQueue.addOperation(startingOperation) internalQueue.addOperation(startingOperation)
@ -50,11 +50,11 @@ class GroupOperation: Operation {
} }
override func execute() { override func execute() {
internalQueue.suspended = false internalQueue.isSuspended = false
internalQueue.addOperation(finishingOperation) internalQueue.addOperation(finishingOperation)
} }
func addOperation(operation: NSOperation) { func addOperation(operation: Operation) {
internalQueue.addOperation(operation) internalQueue.addOperation(operation)
} }
@ -67,14 +67,14 @@ class GroupOperation: Operation {
aggregatedErrors.append(error) aggregatedErrors.append(error)
} }
func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) { func operationDidFinish(operation: Operation, withErrors errors: [NSError]) {
// For use by subclassers. // For use by subclassers.
} }
} }
extension GroupOperation: OperationQueueDelegate { extension GroupOperation: EarthquakeOperationQueueDelegate {
final func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation) { final func operationQueue(operationQueue: EarthquakeOperationQueue, willAddOperation operation: Operation) {
assert(!finishingOperation.finished && !finishingOperation.executing, "cannot add new operations to a group after the group has completed") assert(!finishingOperation.isFinished && !finishingOperation.isExecuting, "cannot add new operations to a group after the group has completed")
/* /*
Some operation in this group has produced a new operation to execute. Some operation in this group has produced a new operation to execute.
@ -97,15 +97,15 @@ extension GroupOperation: OperationQueueDelegate {
} }
} }
final func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError]) { final func operationQueue(operationQueue: EarthquakeOperationQueue, operationDidFinish operation: Operation, withErrors errors: [NSError]) {
aggregatedErrors.appendContentsOf(errors) aggregatedErrors.append(contentsOf: errors)
if operation === finishingOperation { if operation === finishingOperation {
internalQueue.suspended = true internalQueue.isSuspended = true
finish(aggregatedErrors) finish(errors: aggregatedErrors)
} }
else if operation !== startingOperation { else if operation !== startingOperation {
operationDidFinish(operation, withErrors: errors) operationDidFinish(operation: operation, withErrors: errors)
} }
} }
} }

View file

@ -38,7 +38,7 @@ struct HealthCondition: OperationCondition {
readTypes = typesToRead readTypes = typesToRead
} }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
guard HKHealthStore.isHealthDataAvailable() else { guard HKHealthStore.isHealthDataAvailable() else {
return nil return nil
} }
@ -50,9 +50,9 @@ struct HealthCondition: OperationCondition {
return HealthPermissionOperation(shareTypes: shareTypes, readTypes: readTypes) return HealthPermissionOperation(shareTypes: shareTypes, readTypes: readTypes)
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
guard HKHealthStore.isHealthDataAvailable() else { guard HKHealthStore.isHealthDataAvailable() else {
failed(shareTypes, completion: completion) failed(unauthorizedShareTypes: shareTypes, completion: completion)
return return
} }
@ -68,11 +68,11 @@ struct HealthCondition: OperationCondition {
write data to HealthKit. write data to HealthKit.
*/ */
let unauthorizedShareTypes = shareTypes.filter { shareType in let unauthorizedShareTypes = shareTypes.filter { shareType in
return store.authorizationStatusForType(shareType) != .SharingAuthorized return store.authorizationStatus(for: shareType) != .sharingAuthorized
} }
if !unauthorizedShareTypes.isEmpty { if !unauthorizedShareTypes.isEmpty {
failed(Set(unauthorizedShareTypes), completion: completion) failed(unauthorizedShareTypes: Set(unauthorizedShareTypes), completion: completion)
} }
else { else {
completion(.Satisfied) completion(.Satisfied)
@ -80,11 +80,11 @@ struct HealthCondition: OperationCondition {
} }
// Break this out in to its own method so we don't clutter up the evaluate... method. // Break this out in to its own method so we don't clutter up the evaluate... method.
private func failed(unauthorizedShareTypes: Set<HKSampleType>, completion: OperationConditionResult -> Void) { private func failed(unauthorizedShareTypes: Set<HKSampleType>, completion: (OperationConditionResult) -> Void) {
let error = NSError(code: .ConditionFailed, userInfo: [ let error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name, OperationConditionKey: type(of: self).name,
self.dynamicType.healthDataAvailable: HKHealthStore.isHealthDataAvailable(), type(of: self).healthDataAvailable: HKHealthStore.isHealthDataAvailable(),
self.dynamicType.unauthorizedShareTypesKey: unauthorizedShareTypes type(of: self).unauthorizedShareTypesKey: unauthorizedShareTypes
]) ])
completion(.Failed(error)) completion(.Failed(error))
@ -95,7 +95,7 @@ struct HealthCondition: OperationCondition {
A private `Operation` that will request access to the user's health data, if A private `Operation` that will request access to the user's health data, if
it has not already been granted. it has not already been granted.
*/ */
private class HealthPermissionOperation: Operation { private class HealthPermissionOperation: EarthquakeOperation {
let shareTypes: Set<HKSampleType> let shareTypes: Set<HKSampleType>
let readTypes: Set<HKSampleType> let readTypes: Set<HKSampleType>
@ -105,20 +105,20 @@ private class HealthPermissionOperation: Operation {
super.init() super.init()
addCondition(MutuallyExclusive<HealthPermissionOperation>()) addCondition(condition: MutuallyExclusive<HealthPermissionOperation>())
addCondition(MutuallyExclusive<UIViewController>()) addCondition(condition: MutuallyExclusive<UIViewController>())
addCondition(AlertPresentation()) addCondition(condition: AlertPresentation())
} }
override func execute() { override func execute() {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
let store = HKHealthStore() let store = HKHealthStore()
/* /*
This method is smart enough to not re-prompt for access if access This method is smart enough to not re-prompt for access if access
has already been granted. has already been granted.
*/ */
store.requestAuthorizationToShareTypes(self.shareTypes, readTypes: self.readTypes) { completed, error in store.requestAuthorization(toShare: self.shareTypes, read: self.readTypes) { completed, error in
self.finish() self.finish()
} }
} }

View file

@ -30,11 +30,11 @@ struct LocationCondition: OperationCondition {
self.usage = usage self.usage = usage
} }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return LocationPermissionOperation(usage: usage) return LocationPermissionOperation(usage: usage)
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
let enabled = CLLocationManager.locationServicesEnabled() let enabled = CLLocationManager.locationServicesEnabled()
let actual = CLLocationManager.authorizationStatus() let actual = CLLocationManager.authorizationStatus()
@ -42,11 +42,11 @@ struct LocationCondition: OperationCondition {
// There are several factors to consider when evaluating this condition // There are several factors to consider when evaluating this condition
switch (enabled, usage, actual) { switch (enabled, usage, actual) {
case (true, _, .AuthorizedAlways): case (true, _, .authorizedAlways):
// The service is enabled, and we have "Always" permission -> condition satisfied. // The service is enabled, and we have "Always" permission -> condition satisfied.
break break
case (true, .WhenInUse, .AuthorizedWhenInUse): case (true, .WhenInUse, .authorizedWhenInUse):
/* /*
The service is enabled, and we have and need "WhenInUse" The service is enabled, and we have and need "WhenInUse"
permission -> condition satisfied. permission -> condition satisfied.
@ -63,9 +63,9 @@ struct LocationCondition: OperationCondition {
The last case would happen if this condition were wrapped in a `SilentCondition`. The last case would happen if this condition were wrapped in a `SilentCondition`.
*/ */
error = NSError(code: .ConditionFailed, userInfo: [ error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name, OperationConditionKey: type(of: self).name,
self.dynamicType.locationServicesEnabledKey: enabled, type(of: self).locationServicesEnabledKey: enabled,
self.dynamicType.authorizationStatusKey: Int(actual.rawValue) type(of: self).authorizationStatusKey: Int(actual.rawValue)
]) ])
} }
@ -82,7 +82,7 @@ struct LocationCondition: OperationCondition {
A private `Operation` that will request permission to access the user's location, A private `Operation` that will request permission to access the user's location,
if permission has not already been granted. if permission has not already been granted.
*/ */
private class LocationPermissionOperation: Operation { private class LocationPermissionOperation: EarthquakeOperation {
let usage: LocationCondition.Usage let usage: LocationCondition.Usage
var manager: CLLocationManager? var manager: CLLocationManager?
@ -93,7 +93,7 @@ private class LocationPermissionOperation: Operation {
This is an operation that potentially presents an alert so it should This is an operation that potentially presents an alert so it should
be mutually exclusive with anything else that presents an alert. be mutually exclusive with anything else that presents an alert.
*/ */
addCondition(AlertPresentation()) addCondition(condition: AlertPresentation())
} }
override func execute() { override func execute() {
@ -102,8 +102,8 @@ private class LocationPermissionOperation: Operation {
need to handle the "upgrade" (.WhenInUse -> .Always) case. need to handle the "upgrade" (.WhenInUse -> .Always) case.
*/ */
switch (CLLocationManager.authorizationStatus(), usage) { switch (CLLocationManager.authorizationStatus(), usage) {
case (.NotDetermined, _), (.AuthorizedWhenInUse, .Always): case (.notDetermined, _), (.authorizedWhenInUse, .Always):
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
self.requestPermission() self.requestPermission()
} }
@ -129,14 +129,15 @@ private class LocationPermissionOperation: Operation {
} }
// This is helpful when developing the app. // This is helpful when developing the app.
assert(NSBundle.mainBundle().objectForInfoDictionaryKey(key) != nil, "Requesting location permission requires the \(key) key in your Info.plist") assert(Bundle.main.object(forInfoDictionaryKey: key) != nil, "Requesting location permission requires the \(key) key in your Info.plist")
} }
} }
extension LocationPermissionOperation: CLLocationManagerDelegate { extension LocationPermissionOperation: CLLocationManagerDelegate {
@objc func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) { @available(iOS 14.0, *)
if manager == self.manager && executing && status != .NotDetermined { func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if manager == self.manager && isExecuting && manager.authorizationStatus != .notDetermined {
finish() finish()
} }
} }

View file

@ -15,25 +15,25 @@ import CoreLocation
prompt for `WhenInUse` location authorization, if the app does not already prompt for `WhenInUse` location authorization, if the app does not already
have it. have it.
*/ */
class LocationOperation: Operation, CLLocationManagerDelegate { class LocationOperation: EarthquakeOperation, CLLocationManagerDelegate {
// MARK: Properties // MARK: Properties
private let accuracy: CLLocationAccuracy private let accuracy: CLLocationAccuracy
private var manager: CLLocationManager? private var manager: CLLocationManager?
private let handler: CLLocation -> Void private let handler: (CLLocation) -> Void
// MARK: Initialization // MARK: Initialization
init(accuracy: CLLocationAccuracy, locationHandler: CLLocation -> Void) { init(accuracy: CLLocationAccuracy, locationHandler: @escaping (CLLocation) -> Void) {
self.accuracy = accuracy self.accuracy = accuracy
self.handler = locationHandler self.handler = locationHandler
super.init() super.init()
addCondition(LocationCondition(usage: .WhenInUse)) addCondition(condition: LocationCondition(usage: .WhenInUse))
addCondition(MutuallyExclusive<CLLocationManager>()) addCondition(condition: MutuallyExclusive<CLLocationManager>())
} }
override func execute() { override func execute() {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
/* /*
`CLLocationManager` needs to be created on a thread with an active `CLLocationManager` needs to be created on a thread with an active
run loop, so for simplicity we do this on the main queue. run loop, so for simplicity we do this on the main queue.
@ -48,7 +48,7 @@ class LocationOperation: Operation, CLLocationManagerDelegate {
} }
override func cancel() { override func cancel() {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
self.stopLocationUpdates() self.stopLocationUpdates()
super.cancel() super.cancel()
} }
@ -61,8 +61,8 @@ class LocationOperation: Operation, CLLocationManagerDelegate {
// MARK: CLLocationManagerDelegate // MARK: CLLocationManagerDelegate
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last where location.horizontalAccuracy <= accuracy else { guard let location = locations.last, location.horizontalAccuracy <= accuracy else {
return return
} }
@ -71,8 +71,8 @@ class LocationOperation: Operation, CLLocationManagerDelegate {
finish() finish()
} }
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) { private func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
stopLocationUpdates() stopLocationUpdates()
finishWithError(error) finishWithError(error: error)
} }
} }

View file

@ -20,11 +20,11 @@ struct MutuallyExclusive<T>: OperationCondition {
init() { } init() { }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return nil return nil
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
completion(.Satisfied) completion(.Satisfied)
} }
} }

View file

@ -9,7 +9,7 @@
import Foundation import Foundation
extension NSLock { extension NSLock {
func withCriticalScope<T>(@noescape block: Void -> T) -> T { func withCriticalScope<T>(block: () -> T) -> T {
lock() lock()
let value = block() let value = block()
unlock() unlock()

View file

@ -8,12 +8,12 @@ A convenient extension to Foundation.NSOperation.
import Foundation import Foundation
extension NSOperation { extension Operation {
/** /**
Add a completion block to be executed after the `NSOperation` enters the Add a completion block to be executed after the `NSOperation` enters the
"finished" state. "finished" state.
*/ */
func addCompletionBlock(block: Void -> Void) { func addCompletionBlock(block: @escaping () -> Void) {
if let existing = completionBlock { if let existing = completionBlock {
/* /*
If we already have a completion block, we construct a new one by If we already have a completion block, we construct a new one by
@ -30,7 +30,7 @@ extension NSOperation {
} }
/// Add multiple depdendencies to the operation. /// Add multiple depdendencies to the operation.
func addDependencies(dependencies: [NSOperation]) { func addDependencies(dependencies: [Operation]) {
for dependency in dependencies { for dependency in dependencies {
addDependency(dependency) addDependency(dependency)
} }

View file

@ -32,17 +32,17 @@ struct NegatedCondition<T: OperationCondition>: OperationCondition {
self.condition = condition self.condition = condition
} }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return condition.dependencyForOperation(operation) return condition.dependencyForOperation(operation: operation)
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
condition.evaluateForOperation(operation) { result in condition.evaluateForOperation(operation: operation) { result in
if result == .Satisfied { if result == .Satisfied {
// If the composed condition succeeded, then this one failed. // If the composed condition succeeded, then this one failed.
let error = NSError(code: .ConditionFailed, userInfo: [ let error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name, OperationConditionKey: type(of: self).name,
self.dynamicType.negatedConditionKey: self.condition.dynamicType.name type(of: self).negatedConditionKey: type(of: self.condition).name
]) ])
completion(.Failed(error)) completion(.Failed(error))

View file

@ -22,19 +22,19 @@ struct NoCancelledDependencies: OperationCondition {
// No op. // No op.
} }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return nil return nil
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
// Verify that all of the dependencies executed. // Verify that all of the dependencies executed.
let cancelled = operation.dependencies.filter { $0.cancelled } let cancelled = operation.dependencies.filter { $0.isCancelled }
if !cancelled.isEmpty { if !cancelled.isEmpty {
// At least one dependency was cancelled; the condition was not satisfied. // At least one dependency was cancelled; the condition was not satisfied.
let error = NSError(code: .ConditionFailed, userInfo: [ let error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name, OperationConditionKey: type(of: self).name,
self.dynamicType.cancelledDependenciesKey: cancelled type(of: self).cancelledDependenciesKey: cancelled
]) ])
completion(.Failed(error)) completion(.Failed(error))

View file

@ -39,10 +39,10 @@ protocol OperationCondition {
expressing that as multiple conditions. Alternatively, you could return expressing that as multiple conditions. Alternatively, you could return
a single `GroupOperation` that executes multiple operations internally. a single `GroupOperation` that executes multiple operations internally.
*/ */
func dependencyForOperation(operation: Operation) -> NSOperation? func dependencyForOperation(operation: EarthquakeOperation) -> Operation?
/// Evaluate the condition, to see if it has been satisfied or not. /// Evaluate the condition, to see if it has been satisfied or not.
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void)
} }
/** /**
@ -76,31 +76,31 @@ func ==(lhs: OperationConditionResult, rhs: OperationConditionResult) -> Bool {
// MARK: Evaluate Conditions // MARK: Evaluate Conditions
struct OperationConditionEvaluator { struct OperationConditionEvaluator {
static func evaluate(conditions: [OperationCondition], operation: Operation, completion: [NSError] -> Void) { static func evaluate(conditions: [OperationCondition], operation: EarthquakeOperation, completion: @escaping ([NSError]) -> Void) {
// Check conditions. // Check conditions.
let conditionGroup = dispatch_group_create() let conditionGroup = DispatchGroup()
var results = [OperationConditionResult?](count: conditions.count, repeatedValue: nil) var results: [OperationConditionResult?] = Array(repeating: nil, count: conditions.count)
// Ask each condition to evaluate and store its result in the "results" array. // Ask each condition to evaluate and store its result in the "results" array.
for (index, condition) in conditions.enumerate() { for (index, condition) in conditions.enumerated() {
dispatch_group_enter(conditionGroup) conditionGroup.enter()
condition.evaluateForOperation(operation) { result in condition.evaluateForOperation(operation: operation) { result in
results[index] = result results[index] = result
dispatch_group_leave(conditionGroup) conditionGroup.leave()
} }
} }
// After all the conditions have evaluated, this block will execute. // After all the conditions have evaluated, this block will execute.
dispatch_group_notify(conditionGroup, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) { conditionGroup.notify(queue: DispatchQueue.global(qos: .default)) {
// Aggregate the errors that occurred, in order. // Aggregate the errors that occurred, in order.
var failures = results.flatMap { $0?.error } var failures = results.compactMap { $0?.error }
/* /*
If any of the conditions caused this operation to be cancelled, If any of the conditions caused this operation to be cancelled,
check for that. check for that.
*/ */
if operation.cancelled { if operation.isCancelled {
failures.append(NSError(code: .ConditionFailed)) failures.append(NSError(code: .ConditionFailed))
} }

View file

@ -16,7 +16,7 @@ enum OperationErrorCode: Int {
} }
extension NSError { extension NSError {
convenience init(code: OperationErrorCode, userInfo: [NSObject: AnyObject]? = nil) { convenience init(code: OperationErrorCode, userInfo: [String: Any]? = nil) {
self.init(domain: OperationErrorDomain, code: code.rawValue, userInfo: userInfo) self.init(domain: OperationErrorDomain, code: code.rawValue, userInfo: userInfo)
} }
} }

View file

@ -15,15 +15,15 @@ import Foundation
protocol OperationObserver { protocol OperationObserver {
/// Invoked immediately prior to the `Operation`'s `execute()` method. /// Invoked immediately prior to the `Operation`'s `execute()` method.
func operationDidStart(operation: Operation) func operationDidStart(operation: EarthquakeOperation)
/// Invoked when `Operation.produceOperation(_:)` is executed. /// Invoked when `Operation.produceOperation(_:)` is executed.
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation)
/** /**
Invoked as an `Operation` finishes, along with any errors produced during Invoked as an `Operation` finishes, along with any errors produced during
execution (or readiness evaluation). execution (or readiness evaluation).
*/ */
func operationDidFinish(operation: Operation, errors: [NSError]) func operationDidFinish(operation: EarthquakeOperation, errors: [NSError])
} }

View file

@ -18,7 +18,7 @@ struct PassbookCondition: OperationCondition {
init() { } init() { }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
/* /*
There's nothing you can do to make Passbook available if it's not There's nothing you can do to make Passbook available if it's not
on your device. on your device.
@ -26,13 +26,13 @@ struct PassbookCondition: OperationCondition {
return nil return nil
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
if PKPassLibrary.isPassLibraryAvailable() { if PKPassLibrary.isPassLibraryAvailable() {
completion(.Satisfied) completion(.Satisfied)
} }
else { else {
let error = NSError(code: .ConditionFailed, userInfo: [ let error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name OperationConditionKey: type(of: self).name
]) ])
completion(.Failed(error)) completion(.Failed(error))

View file

@ -18,18 +18,18 @@ struct PhotosCondition: OperationCondition {
init() { } init() { }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return PhotosPermissionOperation() return PhotosPermissionOperation()
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
switch PHPhotoLibrary.authorizationStatus() { switch PHPhotoLibrary.authorizationStatus() {
case .Authorized: case .authorized:
completion(.Satisfied) completion(.Satisfied)
default: default:
let error = NSError(code: .ConditionFailed, userInfo: [ let error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name OperationConditionKey: type(of: self).name
]) ])
completion(.Failed(error)) completion(.Failed(error))
@ -41,17 +41,17 @@ struct PhotosCondition: OperationCondition {
A private `Operation` that will request access to the user's Photos, if it A private `Operation` that will request access to the user's Photos, if it
has not already been granted. has not already been granted.
*/ */
private class PhotosPermissionOperation: Operation { private class PhotosPermissionOperation: EarthquakeOperation {
override init() { override init() {
super.init() super.init()
addCondition(AlertPresentation()) addCondition(condition: AlertPresentation())
} }
override func execute() { override func execute() {
switch PHPhotoLibrary.authorizationStatus() { switch PHPhotoLibrary.authorizationStatus() {
case .NotDetermined: case .notDetermined:
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
PHPhotoLibrary.requestAuthorization { status in PHPhotoLibrary.requestAuthorization { status in
self.finish() self.finish()
} }

View file

@ -19,26 +19,26 @@ struct ReachabilityCondition: OperationCondition {
static let name = "Reachability" static let name = "Reachability"
static let isMutuallyExclusive = false static let isMutuallyExclusive = false
let host: NSURL let host: URL
init(host: NSURL) { init(host: URL) {
self.host = host self.host = host
} }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return nil return nil
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
ReachabilityController.requestReachability(host) { reachable in ReachabilityController.requestReachability(url: host) { reachable in
if reachable { if reachable {
completion(.Satisfied) completion(.Satisfied)
} }
else { else {
let error = NSError(code: .ConditionFailed, userInfo: [ let error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name, OperationConditionKey: type(of: self).name,
self.dynamicType.hostKey: self.host type(of: self).hostKey: self.host
]) ])
completion(.Failed(error)) completion(.Failed(error))
@ -52,16 +52,16 @@ struct ReachabilityCondition: OperationCondition {
private class ReachabilityController { private class ReachabilityController {
static var reachabilityRefs = [String: SCNetworkReachability]() static var reachabilityRefs = [String: SCNetworkReachability]()
static let reachabilityQueue = dispatch_queue_create("Operations.Reachability", DISPATCH_QUEUE_SERIAL) static let reachabilityQueue = DispatchQueue(label: "Operations.Reachability")
static func requestReachability(url: NSURL, completionHandler: (Bool) -> Void) { static func requestReachability(url: URL, completionHandler: @escaping (Bool) -> Void) {
if let host = url.host { if let host = url.host {
dispatch_async(reachabilityQueue) { reachabilityQueue.async {
var ref = self.reachabilityRefs[host] var ref = self.reachabilityRefs[host]
if ref == nil { if ref == nil {
let hostString = host as NSString let hostString = host as NSString
ref = SCNetworkReachabilityCreateWithName(nil, hostString.UTF8String) ref = SCNetworkReachabilityCreateWithName(nil, hostString.utf8String!)
} }
if let ref = ref { if let ref = ref {
@ -76,7 +76,7 @@ private class ReachabilityController {
such as whether or not the connection would require such as whether or not the connection would require
VPN, a cellular connection, etc. VPN, a cellular connection, etc.
*/ */
reachable = flags.contains(.Reachable) reachable = flags.contains(.reachable)
} }
completionHandler(reachable) completionHandler(reachable)
} }

View file

@ -10,8 +10,8 @@ This file shows an example of implementing the OperationCondition protocol.
import UIKit import UIKit
private let RemoteNotificationQueue = OperationQueue() private let RemoteNotificationQueue = EarthquakeOperationQueue()
private let RemoteNotificationName = "RemoteNotificationPermissionNotification" private let RemoteNotificationName = Notification.Name("RemoteNotificationPermissionNotification")
private enum RemoteRegistrationResult { private enum RemoteRegistrationResult {
case Token(NSData) case Token(NSData)
@ -23,14 +23,14 @@ struct RemoteNotificationCondition: OperationCondition {
static let name = "RemoteNotification" static let name = "RemoteNotification"
static let isMutuallyExclusive = false static let isMutuallyExclusive = false
static func didReceiveNotificationToken(token: NSData) { static func didReceiveNotificationToken(token: Data) {
NSNotificationCenter.defaultCenter().postNotificationName(RemoteNotificationName, object: nil, userInfo: [ NotificationCenter.default.post(name: RemoteNotificationName, object: nil, userInfo: [
"token": token "token": token
]) ])
} }
static func didFailToRegister(error: NSError) { static func didFailToRegister(error: NSError) {
NSNotificationCenter.defaultCenter().postNotificationName(RemoteNotificationName, object: nil, userInfo: [ NotificationCenter.default.post(name: RemoteNotificationName, object: nil, userInfo: [
"error": error "error": error
]) ])
} }
@ -41,11 +41,11 @@ struct RemoteNotificationCondition: OperationCondition {
self.application = application self.application = application
} }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return RemoteNotificationPermissionOperation(application: application, handler: { _ in }) return RemoteNotificationPermissionOperation(application: application, handler: { _ in })
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
/* /*
Since evaluation requires executing an operation, use a private operation Since evaluation requires executing an operation, use a private operation
queue. queue.
@ -57,7 +57,7 @@ struct RemoteNotificationCondition: OperationCondition {
case .Error(let underlyingError): case .Error(let underlyingError):
let error = NSError(code: .ConditionFailed, userInfo: [ let error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name, OperationConditionKey: type(of: self).name,
NSUnderlyingErrorKey: underlyingError NSUnderlyingErrorKey: underlyingError
]) ])
@ -78,11 +78,11 @@ struct RemoteNotificationCondition: OperationCondition {
`RemoteNotificationCondition.didFailToRegister(_:)` in the appropriate `RemoteNotificationCondition.didFailToRegister(_:)` in the appropriate
`UIApplicationDelegate` method, as shown in the `AppDelegate.swift` file. `UIApplicationDelegate` method, as shown in the `AppDelegate.swift` file.
*/ */
private class RemoteNotificationPermissionOperation: Operation { private class RemoteNotificationPermissionOperation: EarthquakeOperation {
let application: UIApplication let application: UIApplication
private let handler: RemoteRegistrationResult -> Void private let handler: (RemoteRegistrationResult) -> Void
private init(application: UIApplication, handler: RemoteRegistrationResult -> Void) { init(application: UIApplication, handler: @escaping (RemoteRegistrationResult) -> Void) {
self.application = application self.application = application
self.handler = handler self.handler = handler
@ -92,21 +92,21 @@ private class RemoteNotificationPermissionOperation: Operation {
This operation cannot run at the same time as any other remote notification This operation cannot run at the same time as any other remote notification
permission operation. permission operation.
*/ */
addCondition(MutuallyExclusive<RemoteNotificationPermissionOperation>()) addCondition(condition: MutuallyExclusive<RemoteNotificationPermissionOperation>())
} }
override func execute() { override func execute() {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
let notificationCenter = NSNotificationCenter.defaultCenter() let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(RemoteNotificationPermissionOperation.didReceiveResponse(_:)), name: RemoteNotificationName, object: nil) notificationCenter.addObserver(self, selector: #selector(RemoteNotificationPermissionOperation.didReceiveResponse(notification:)), name: RemoteNotificationName, object: nil)
self.application.registerForRemoteNotifications() self.application.registerForRemoteNotifications()
} }
} }
@objc func didReceiveResponse(notification: NSNotification) { @objc func didReceiveResponse(notification: NSNotification) {
NSNotificationCenter.defaultCenter().removeObserver(self) NotificationCenter.default.removeObserver(self)
let userInfo = notification.userInfo let userInfo = notification.userInfo

View file

@ -29,12 +29,12 @@ struct SilentCondition<T: OperationCondition>: OperationCondition {
self.condition = condition self.condition = condition
} }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
// Returning nil means we will never a dependency to be generated. // Returning nil means we will never a dependency to be generated.
return nil return nil
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: @escaping (OperationConditionResult) -> Void) {
condition.evaluateForOperation(operation, completion: completion) condition.evaluateForOperation(operation: operation, completion: completion)
} }
} }

View file

@ -17,40 +17,38 @@ struct TimeoutObserver: OperationObserver {
static let timeoutKey = "Timeout" static let timeoutKey = "Timeout"
private let timeout: NSTimeInterval private let timeout: Int
// MARK: Initialization // MARK: Initialization
init(timeout: NSTimeInterval) { init(timeout: Int) {
self.timeout = timeout self.timeout = timeout
} }
// MARK: OperationObserver // MARK: OperationObserver
func operationDidStart(operation: Operation) { func operationDidStart(operation: EarthquakeOperation) {
// When the operation starts, queue up a block to cause it to time out. // When the operation starts, queue up a block to cause it to time out.
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * Double(NSEC_PER_SEC))) DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + .seconds(timeout)) {
dispatch_after(when, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
/* /*
Cancel the operation if it hasn't finished and hasn't already Cancel the operation if it hasn't finished and hasn't already
been cancelled. been cancelled.
*/ */
if !operation.finished && !operation.cancelled { if !operation.isFinished && !operation.isCancelled {
let error = NSError(code: .ExecutionFailed, userInfo: [ let error = NSError(code: .ExecutionFailed, userInfo: [
self.dynamicType.timeoutKey: self.timeout type(of: self).timeoutKey: self.timeout
]) ])
operation.cancelWithError(error) operation.cancelWithError(error: error)
} }
} }
} }
func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) {
// No op. // No op.
} }
func operationDidFinish(operation: Operation, errors: [NSError]) { func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) {
// No op. // No op.
} }
} }

View file

@ -21,7 +21,7 @@ extension UIUserNotificationSettings {
let otherCategories = settings.categories ?? [] let otherCategories = settings.categories ?? []
let myCategories = categories ?? [] let myCategories = categories ?? []
return myCategories.isSupersetOf(otherCategories) return myCategories.isSuperset(of: otherCategories)
} }
/** /**
@ -32,17 +32,17 @@ extension UIUserNotificationSettings {
let mergedTypes = types.union(settings.types) let mergedTypes = types.union(settings.types)
let myCategories = categories ?? [] let myCategories = categories ?? []
var existingCategoriesByIdentifier = Dictionary(sequence: myCategories) { $0.identifier } var existingCategoriesByIdentifier: [String: UIUserNotificationCategory] = Dictionary(sequence: myCategories, keyMapper: \.identifier)
let newCategories = settings.categories ?? [] let newCategories = settings.categories ?? []
let newCategoriesByIdentifier = Dictionary(sequence: newCategories) { $0.identifier } let newCategoriesByIdentifier: [String: UIUserNotificationCategory] = Dictionary(sequence: newCategories, keyMapper: \.identifier)
for (newIdentifier, newCategory) in newCategoriesByIdentifier { for (newIdentifier, newCategory) in newCategoriesByIdentifier {
existingCategoriesByIdentifier[newIdentifier] = newCategory existingCategoriesByIdentifier[newIdentifier] = newCategory
} }
let mergedCategories = Set(existingCategoriesByIdentifier.values) let mergedCategories = Set(existingCategoriesByIdentifier.values)
return UIUserNotificationSettings(forTypes: mergedTypes, categories: mergedCategories) return UIUserNotificationSettings(types: mergedTypes, categories: mergedCategories)
} }
} }

View file

@ -11,37 +11,37 @@ import Foundation
private var URLSessionTaksOperationKVOContext = 0 private var URLSessionTaksOperationKVOContext = 0
/** /**
`URLSessionTaskOperation` is an `Operation` that lifts an `NSURLSessionTask` `URLSessionTaskOperation` is an `Operation` that lifts an `URLSessionTask`
into an operation. into an operation.
Note that this operation does not participate in any of the delegate callbacks \ Note that this operation does not participate in any of the delegate callbacks \
of an `NSURLSession`, but instead uses Key-Value-Observing to know when the of an `URLSession`, but instead uses Key-Value-Observing to know when the
task has been completed. It also does not get notified about any errors that task has been completed. It also does not get notified about any errors that
occurred during execution of the task. occurred during execution of the task.
An example usage of `URLSessionTaskOperation` can be seen in the `DownloadEarthquakesOperation`. An example usage of `URLSessionTaskOperation` can be seen in the `DownloadEarthquakesOperation`.
*/ */
class URLSessionTaskOperation: Operation { class URLSessionTaskOperation: EarthquakeOperation {
let task: NSURLSessionTask let task: URLSessionTask
init(task: NSURLSessionTask) { init(task: URLSessionTask) {
assert(task.state == .Suspended, "Tasks must be suspended.") assert(task.state == .suspended, "Tasks must be suspended.")
self.task = task self.task = task
super.init() super.init()
} }
override func execute() { override func execute() {
assert(task.state == .Suspended, "Task was resumed by something other than \(self).") assert(task.state == .suspended, "Task was resumed by something other than \(self).")
task.addObserver(self, forKeyPath: "state", options: [], context: &URLSessionTaksOperationKVOContext) task.addObserver(self, forKeyPath: "state", options: [], context: &URLSessionTaksOperationKVOContext)
task.resume() task.resume()
} }
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) { override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &URLSessionTaksOperationKVOContext else { return } guard context == &URLSessionTaksOperationKVOContext else { return }
if object === task && keyPath == "state" && task.state == .Completed { if object as? URLSessionTask === task && keyPath == "state" && task.state == .completed {
task.removeObserver(self, forKeyPath: "state") task.removeObserver(self, forKeyPath: "state")
finish() finish()
} }

View file

@ -54,24 +54,24 @@ struct UserNotificationCondition: OperationCondition {
self.behavior = behavior self.behavior = behavior
} }
func dependencyForOperation(operation: Operation) -> NSOperation? { func dependencyForOperation(operation: EarthquakeOperation) -> Operation? {
return UserNotificationPermissionOperation(settings: settings, application: application, behavior: behavior) return UserNotificationPermissionOperation(settings: settings, application: application, behavior: behavior)
} }
func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) {
let result: OperationConditionResult let result: OperationConditionResult
let current = application.currentUserNotificationSettings() let current = application.currentUserNotificationSettings
switch (current, settings) { switch (current, settings) {
case (let current?, let settings) where current.contains(settings): case (let current?, let settings) where current.contains(settings: settings):
result = .Satisfied result = .Satisfied
default: default:
let error = NSError(code: .ConditionFailed, userInfo: [ let error = NSError(code: .ConditionFailed, userInfo: [
OperationConditionKey: self.dynamicType.name, OperationConditionKey: type(of: self).name,
self.dynamicType.currentSettings: current ?? NSNull(), type(of: self).currentSettings: current ?? NSNull(),
self.dynamicType.desiredSettings: settings type(of: self).desiredSettings: settings
]) ])
result = .Failed(error) result = .Failed(error)
@ -85,7 +85,7 @@ struct UserNotificationCondition: OperationCondition {
A private `Operation` subclass to register a `UIUserNotificationSettings` A private `Operation` subclass to register a `UIUserNotificationSettings`
object with a `UIApplication`, prompting the user for permission if necessary. object with a `UIApplication`, prompting the user for permission if necessary.
*/ */
private class UserNotificationPermissionOperation: Operation { private class UserNotificationPermissionOperation: EarthquakeOperation {
let settings: UIUserNotificationSettings let settings: UIUserNotificationSettings
let application: UIApplication let application: UIApplication
let behavior: UserNotificationCondition.Behavior let behavior: UserNotificationCondition.Behavior
@ -97,18 +97,18 @@ private class UserNotificationPermissionOperation: Operation {
super.init() super.init()
addCondition(AlertPresentation()) addCondition(condition: AlertPresentation())
} }
override func execute() { override func execute() {
dispatch_async(dispatch_get_main_queue()) { DispatchQueue.main.async {
let current = self.application.currentUserNotificationSettings() let current = self.application.currentUserNotificationSettings
let settingsToRegister: UIUserNotificationSettings let settingsToRegister: UIUserNotificationSettings
switch (current, self.behavior) { switch (current, self.behavior) {
case (let currentSettings?, .Merge): case (let currentSettings?, .Merge):
settingsToRegister = currentSettings.settingsByMerging(self.settings) settingsToRegister = currentSettings.settingsByMerging(settings: self.settings)
default: default:
settingsToRegister = self.settings settingsToRegister = self.settings

View file

@ -13,7 +13,7 @@ import CoreData
private struct ParsedEarthquake { private struct ParsedEarthquake {
// MARK: Properties. // MARK: Properties.
let date: NSDate let date: Date
let identifier, name, link: String let identifier, name, link: String
@ -22,7 +22,7 @@ private struct ParsedEarthquake {
// MARK: Initialization // MARK: Initialization
init?(feature: [String: AnyObject]) { init?(feature: [String: AnyObject]) {
guard let earthquakeID = feature["id"] as? String where !earthquakeID.isEmpty else { return nil } guard let earthquakeID = feature["id"] as? String, !earthquakeID.isEmpty else { return nil }
identifier = earthquakeID identifier = earthquakeID
let properties = feature["properties"] as? [String: AnyObject] ?? [:] let properties = feature["properties"] as? [String: AnyObject] ?? [:]
@ -34,16 +34,16 @@ private struct ParsedEarthquake {
magnitude = properties["mag"] as? Double ?? 0.0 magnitude = properties["mag"] as? Double ?? 0.0
if let offset = properties["time"] as? Double { if let offset = properties["time"] as? Double {
date = NSDate(timeIntervalSince1970: offset / 1000) date = Date(timeIntervalSince1970: offset / 1000)
} }
else { else {
date = NSDate.distantFuture() date = Date.distantFuture
} }
let geometry = feature["geometry"] as? [String: AnyObject] ?? [:] let geometry = feature["geometry"] as? [String: AnyObject] ?? [:]
if let coordinates = geometry["coordinates"] as? [Double] where coordinates.count == 3 { if let coordinates = geometry["coordinates"] as? [Double], coordinates.count == 3 {
longitude = coordinates[0] longitude = coordinates[0]
latitude = coordinates[1] latitude = coordinates[1]
@ -59,20 +59,20 @@ private struct ParsedEarthquake {
} }
/// An `Operation` to parse earthquakes out of a downloaded feed from the USGS. /// An `Operation` to parse earthquakes out of a downloaded feed from the USGS.
class ParseEarthquakesOperation: Operation { class ParseEarthquakesOperation: EarthquakeOperation {
let cacheFile: NSURL let cacheFile: URL
let context: NSManagedObjectContext let context: NSManagedObjectContext
/** /**
- parameter cacheFile: The file `NSURL` from which to load earthquake data. - parameter cacheFile: The file `URL` from which to load earthquake data.
- parameter context: The `NSManagedObjectContext` that will be used as the - parameter context: The `NSManagedObjectContext` that will be used as the
basis for importing data. The operation will internally basis for importing data. The operation will internally
construct a new `NSManagedObjectContext` that points construct a new `NSManagedObjectContext` that points
to the same `NSPersistentStoreCoordinator` as the to the same `NSPersistentStoreCoordinator` as the
passed-in context. passed-in context.
*/ */
init(cacheFile: NSURL, context: NSManagedObjectContext) { init(cacheFile: URL, context: NSManagedObjectContext) {
let importContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType) let importContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
importContext.persistentStoreCoordinator = context.persistentStoreCoordinator importContext.persistentStoreCoordinator = context.persistentStoreCoordinator
/* /*
@ -90,7 +90,7 @@ class ParseEarthquakesOperation: Operation {
} }
override func execute() { override func execute() {
guard let stream = NSInputStream(URL: cacheFile) else { guard let stream = InputStream(url: cacheFile) else {
finish() finish()
return return
} }
@ -102,35 +102,35 @@ class ParseEarthquakesOperation: Operation {
} }
do { do {
let json = try NSJSONSerialization.JSONObjectWithStream(stream, options: []) as? [String: AnyObject] let json = try JSONSerialization.jsonObject(with: stream, options: []) as? [String: AnyObject]
if let features = json?["features"] as? [[String: AnyObject]] { if let features = json?["features"] as? [[String: AnyObject]] {
parse(features) parse(features: features)
} }
else { else {
finish() finish()
} }
} }
catch let jsonError as NSError { catch let jsonError as NSError {
finishWithError(jsonError) finishWithError(error: jsonError)
} }
} }
private func parse(features: [[String: AnyObject]]) { private func parse(features: [[String: AnyObject]]) {
let parsedEarthquakes = features.flatMap { ParsedEarthquake(feature: $0) } let parsedEarthquakes = features.compactMap { ParsedEarthquake(feature: $0) }
context.performBlock { context.perform {
for newEarthquake in parsedEarthquakes { for newEarthquake in parsedEarthquakes {
self.insert(newEarthquake) self.insert(parsed: newEarthquake)
} }
let error = self.saveContext() let error = self.saveContext()
self.finishWithError(error) self.finishWithError(error: error)
} }
} }
private func insert(parsed: ParsedEarthquake) { private func insert(parsed: ParsedEarthquake) {
let earthquake = NSEntityDescription.insertNewObjectForEntityForName(Earthquake.entityName, inManagedObjectContext: context) as! Earthquake let earthquake = NSEntityDescription.insertNewObject(forEntityName: Earthquake.entityName, into: context) as! Earthquake
earthquake.identifier = parsed.identifier earthquake.identifier = parsed.identifier
earthquake.timestamp = parsed.date earthquake.timestamp = parsed.date

View file

@ -14,7 +14,7 @@ class SplitViewController: UISplitViewController {
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
preferredDisplayMode = .AllVisible preferredDisplayMode = .allVisible
delegate = self delegate = self
} }