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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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