From 2ba16ef4e5a73389b3bd4cacc353d76628d8ef73 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Wed, 16 Feb 2022 23:15:44 -0800 Subject: [PATCH] Update for Xcode 13 and Swift 4, runs but doesn't work --- .../project.pbxproj | 18 +-- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++ Earthquakes/AlertOperation.swift | 18 +-- Earthquakes/AppDelegate.swift | 10 +- Earthquakes/BackgroundObserver.swift | 18 +-- .../DownloadEarthquakesOperation.swift | 28 ++--- Earthquakes/Earthquake.swift | 30 ++--- Earthquakes/EarthquakeTableViewCell.swift | 4 +- .../EarthquakeTableViewController.swift | 96 ++++++++-------- .../EarthquakesTableViewController.swift | 99 ++++++++-------- Earthquakes/GetEarthquakesOperation.swift | 18 +-- .../AppIcon.appiconset/Contents.json | 107 +++++++++++------- Earthquakes/LoadModelOperation.swift | 46 ++++---- Earthquakes/MoreInformationOperation.swift | 22 ++-- Earthquakes/NetworkObserver.swift | 28 +++-- Earthquakes/Operations/BlockObserver.swift | 14 +-- .../Operations/CKContainer+Operations.swift | 34 +++--- .../Operations/CalendarCondition.swift | 36 +++--- Earthquakes/Operations/CloudCondition.swift | 18 +-- Earthquakes/Operations/DelayOperation.swift | 21 ++-- .../Operations/Dictionary+Operations.swift | 3 +- .../Operations/EarthquakeBlockOperation.swift | 25 ++-- ...ration.swift => EarthquakeOperation.swift} | 81 +++++++------ ...e.swift => EarthquakeOperationQueue.swift} | 42 +++---- .../Operations/ExclusivityController.swift | 24 ++-- Earthquakes/Operations/GroupOperation.swift | 36 +++--- Earthquakes/Operations/HealthCondition.swift | 30 ++--- .../Operations/LocationCondition.swift | 29 ++--- .../Operations/LocationOperation.swift | 24 ++-- .../Operations/MutuallyExclusive.swift | 4 +- .../Operations/NSLock+Operations.swift | 4 +- .../Operations/NSOperation+Operations.swift | 6 +- Earthquakes/Operations/NegatedCondition.swift | 12 +- .../Operations/NoCancelledDependencies.swift | 10 +- .../Operations/OperationCondition.swift | 24 ++-- Earthquakes/Operations/OperationErrors.swift | 2 +- .../Operations/OperationObserver.swift | 6 +- .../Operations/PassbookCondition.swift | 6 +- Earthquakes/Operations/PhotosCondition.swift | 16 +-- .../Operations/ReachabilityCondition.swift | 24 ++-- .../RemoteNotificationCondition.swift | 32 +++--- Earthquakes/Operations/SilentCondition.swift | 6 +- Earthquakes/Operations/TimeoutObserver.swift | 20 ++-- .../UIUserNotifications+Operations.swift | 8 +- .../Operations/URLSessionTaskOperation.swift | 20 ++-- .../UserNotificationCondition.swift | 24 ++-- Earthquakes/ParseEarthquakesOperation.swift | 38 +++---- Earthquakes/SplitViewController.swift | 2 +- 48 files changed, 630 insertions(+), 601 deletions(-) create mode 100644 Advanced NSOperations.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename Earthquakes/Operations/{Operation.swift => EarthquakeOperation.swift} (82%) rename Earthquakes/Operations/{OperationQueue.swift => EarthquakeOperationQueue.swift} (70%) diff --git a/Advanced NSOperations.xcodeproj/project.pbxproj b/Advanced NSOperations.xcodeproj/project.pbxproj index f967e82..b4d904f 100644 --- a/Advanced NSOperations.xcodeproj/project.pbxproj +++ b/Advanced NSOperations.xcodeproj/project.pbxproj @@ -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 = ""; }; 551B9BE91AEB1C9700302388 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 551B9BEC1AEB1C9700302388 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; - 551B9C021AEB1CA900302388 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = ""; }; + 551B9C021AEB1CA900302388 /* EarthquakeOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EarthquakeOperation.swift; sourceTree = ""; }; 551B9C041AEB1CC800302388 /* OperationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationCondition.swift; sourceTree = ""; }; - 551B9C061AEB2D7800302388 /* OperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationQueue.swift; sourceTree = ""; }; + 551B9C061AEB2D7800302388 /* EarthquakeOperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EarthquakeOperationQueue.swift; sourceTree = ""; }; 551B9C0B1AEBE4F300302388 /* CloudCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudCondition.swift; sourceTree = ""; }; 551B9C0D1AEBE52800302388 /* ReachabilityCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityCondition.swift; sourceTree = ""; }; 551B9C101AEBE54D00302388 /* PassbookCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassbookCondition.swift; sourceTree = ""; }; @@ -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; diff --git a/Advanced NSOperations.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Advanced NSOperations.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Advanced NSOperations.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Earthquakes/AlertOperation.swift b/Earthquakes/AlertOperation.swift index 6932fcd..fe1afc4 100644 --- a/Earthquakes/AlertOperation.swift +++ b/Earthquakes/AlertOperation.swift @@ -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()) + addCondition(condition: MutuallyExclusive()) } - 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) } } } diff --git a/Earthquakes/AppDelegate.swift b/Earthquakes/AppDelegate.swift index 4ae0e0f..56012e5 100644 --- a/Earthquakes/AppDelegate.swift +++ b/Earthquakes/AppDelegate.swift @@ -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) } } diff --git a/Earthquakes/BackgroundObserver.swift b/Earthquakes/BackgroundObserver.swift index c2d9093..ba8435a 100644 --- a/Earthquakes/BackgroundObserver.swift +++ b/Earthquakes/BackgroundObserver.swift @@ -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() } } diff --git a/Earthquakes/DownloadEarthquakesOperation.swift b/Earthquakes/DownloadEarthquakesOperation.swift index cadd532..536c7ad 100644 --- a/Earthquakes/DownloadEarthquakesOperation.swift +++ b/Earthquakes/DownloadEarthquakesOperation.swift @@ -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. diff --git a/Earthquakes/Earthquake.swift b/Earthquakes/Earthquake.swift index ed56520..0848108 100644 --- a/Earthquakes/Earthquake.swift +++ b/Earthquakes/Earthquake.swift @@ -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) } } diff --git a/Earthquakes/EarthquakeTableViewCell.swift b/Earthquakes/EarthquakeTableViewCell.swift index 77316ee..3ea6637 100644 --- a/Earthquakes/EarthquakeTableViewCell.swift +++ b/Earthquakes/EarthquakeTableViewCell.swift @@ -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 diff --git a/Earthquakes/EarthquakeTableViewController.swift b/Earthquakes/EarthquakeTableViewController.swift index b457632..037b5b9 100644 --- a/Earthquakes/EarthquakeTableViewController.swift +++ b/Earthquakes/EarthquakeTableViewController.swift @@ -12,19 +12,19 @@ import MapKit class EarthquakeTableViewController: UITableViewController { // MARK: Properties - var queue: OperationQueue? + var queue: EarthquakeOperationQueue? var earthquake: Earthquake? var locationRequest: LocationOperation? - + @IBOutlet var map: MKMapView! @IBOutlet var nameLabel: UILabel! @IBOutlet var magnitudeLabel: UILabel! @IBOutlet var depthLabel: UILabel! @IBOutlet var timeLabel: UILabel! @IBOutlet var distanceLabel: UILabel! - + // MARKL View Controller - + override func viewDidLoad() { super.viewDidLoad() @@ -38,88 +38,88 @@ class EarthquakeTableViewController: UITableViewController { return } - + let span = MKCoordinateSpan(latitudeDelta: 15, longitudeDelta: 15) map.region = MKCoordinateRegion(center: earthquake.coordinate, span: span) - + let annotation = MKPointAnnotation() annotation.coordinate = earthquake.coordinate map.addAnnotation(annotation) - + nameLabel.text = earthquake.name - magnitudeLabel.text = Earthquake.magnitudeFormatter.stringFromNumber(earthquake.magnitude) - depthLabel.text = Earthquake.depthFormatter.stringFromMeters(earthquake.depth) - timeLabel.text = Earthquake.timestampFormatter.stringFromDate(earthquake.timestamp) - + magnitudeLabel.text = Earthquake.magnitudeFormatter.string(from: NSNumber(value: earthquake.magnitude)) + depthLabel.text = Earthquake.depthFormatter.string(fromMeters: earthquake.depth) + timeLabel.text = Earthquake.timestampFormatter.string(from: earthquake.timestamp as Date) + /* We can use a `LocationOperation` to retrieve the user's current location. Once we have the location, we can compute how far they currently are from the epicenter of the earthquake. - + If this operation fails (ie, we are denied access to their location), then the text in the `UILabel` will remain as what it is defined to be in the storyboard. */ let locationOperation = LocationOperation(accuracy: kCLLocationAccuracyKilometer) { location in if let earthquakeLocation = self.earthquake?.location { - let distance = location.distanceFromLocation(earthquakeLocation) - self.distanceLabel.text = Earthquake.distanceFormatter.stringFromMeters(distance) + let distance = location.distance(from: earthquakeLocation) + self.distanceLabel.text = Earthquake.distanceFormatter.string(fromMeters: distance) } self.locationRequest = nil } - + queue?.addOperation(locationOperation) locationRequest = locationOperation } - - override func viewWillDisappear(animated: Bool) { + + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // If the LocationOperation is still going on, then cancel it. locationRequest?.cancel() } - + @IBAction func shareEarthquake(sender: UIBarButtonItem) { guard let earthquake = earthquake else { return } - guard let url = NSURL(string: earthquake.webLink) else { return } - + guard let url = URL(string: earthquake.webLink) else { return } + let location = earthquake.location - - let items = [url, location] - + + let items = [url, location] as [Any] + /* We could present the share sheet manually, but by putting it inside an `Operation`, we can make it mutually exclusive with other operations that modify the view controller hierarchy. */ - let shareOperation = EarthquakeBlockOperation { (continuation: Void -> Void) in - dispatch_async(dispatch_get_main_queue()) { + let shareOperation = EarthquakeBlockOperation { (continuation: @escaping () -> Void) in + DispatchQueue.main.async { let shareSheet = UIActivityViewController(activityItems: items, applicationActivities: nil) - + shareSheet.popoverPresentationController?.barButtonItem = sender - shareSheet.completionWithItemsHandler = { _ in + shareSheet.completionWithItemsHandler = { _, _, _, _ in // End the operation when the share sheet completes. continuation() } - - self.presentViewController(shareSheet, animated: true, completion: nil) + + self.present(shareSheet, animated: true, completion: nil) } } - + /* Indicate that this operation modifies the View Controller hierarchy and is thus mutually exclusive. */ - shareOperation.addCondition(MutuallyExclusive()) + shareOperation.addCondition(condition: MutuallyExclusive()) queue?.addOperation(shareOperation) } - - override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.section == 1 && indexPath.row == 0 { // The user has tapped the "More Information" button. - if let link = earthquake?.webLink, url = NSURL(string: link) { + if let link = earthquake?.webLink, let url = URL(string: link) { // If we have a link, present the "More Information" dialog. let moreInformation = MoreInformationOperation(URL: url) @@ -133,29 +133,29 @@ class EarthquakeTableViewController: UITableViewController { queue?.addOperation(alert) } } - - tableView.deselectRowAtIndexPath(indexPath, animated: true) + + tableView.deselectRow(at: indexPath as IndexPath, animated: true) } } extension EarthquakeTableViewController: MKMapViewDelegate { - func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? { + func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { guard let earthquake = earthquake else { return nil } - - var view = mapView.dequeueReusableAnnotationViewWithIdentifier("pin") as? MKPinAnnotationView - + + var view = mapView.dequeueReusableAnnotationView(withIdentifier: "pin") as? MKPinAnnotationView + view = view ?? MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin") - + guard let pin = view else { return nil } - + switch earthquake.magnitude { - case 0..<3: pin.pinTintColor = UIColor.grayColor() - case 3..<4: pin.pinTintColor = UIColor.blueColor() - case 4..<5: pin.pinTintColor = UIColor.orangeColor() - default: pin.pinTintColor = UIColor.redColor() + case 0..<3: pin.pinTintColor = UIColor.gray + case 3..<4: pin.pinTintColor = UIColor.blue + case 4..<5: pin.pinTintColor = UIColor.orange + default: pin.pinTintColor = UIColor.red } - - pin.enabled = false + + pin.isEnabled = false return pin } diff --git a/Earthquakes/EarthquakesTableViewController.swift b/Earthquakes/EarthquakesTableViewController.swift index 2242957..57adcaf 100644 --- a/Earthquakes/EarthquakesTableViewController.swift +++ b/Earthquakes/EarthquakesTableViewController.swift @@ -13,28 +13,28 @@ import CloudKit class EarthquakesTableViewController: UITableViewController { // MARK: Properties - var fetchedResultsController: NSFetchedResultsController? - - let operationQueue = OperationQueue() - + var fetchedResultsController: NSFetchedResultsController? + + let operationQueue = EarthquakeOperationQueue() + // MARK: View Controller - + override func viewDidLoad() { super.viewDidLoad() let operation = LoadModelOperation { context in // Now that we have a context, build our `FetchedResultsController`. - dispatch_async(dispatch_get_main_queue()) { - let request = NSFetchRequest(entityName: Earthquake.entityName) + DispatchQueue.main.async { + let request = NSFetchRequest(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(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil) + self.fetchedResultsController = controller - + self.updateUI() } } @@ -42,89 +42,89 @@ class EarthquakesTableViewController: UITableViewController { operationQueue.addOperation(operation) } - override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + override func numberOfSections(in tableView: UITableView) -> Int { return fetchedResultsController?.sections?.count ?? 0 } - override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let section = fetchedResultsController?.sections?[section] return section?.numberOfObjects ?? 0 } - override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCellWithIdentifier("earthquakeCell", forIndexPath: indexPath) as! EarthquakeTableViewCell - - if let earthquake = fetchedResultsController?.objectAtIndexPath(indexPath) as? Earthquake { - cell.configure(earthquake) + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "earthquakeCell", for: indexPath as IndexPath) as! EarthquakeTableViewCell + + if let earthquake = fetchedResultsController?.object(at: indexPath) as? Earthquake { + cell.configure(earthquake: earthquake) } return cell } - - override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { /* Instead of performing the segue directly, we can wrap it in a `EarthquakeBlockOperation`. This allows us to attach conditions to the operation. For example, you could make it so that you could only perform the segue if the network is reachable and you have access to the user's Photos library. - + If you decide to use this pattern in your apps, choose conditions that are sensible and do not place onerous requirements on the user. - + It's also worth noting that the Observer attached to the `EarthquakeBlockOperation` will cause the tableview row to be deselected automatically if the `EarthquakeOperation` fails. - + You may choose to add your own observer to introspect the errors reported as the operation finishes. Doing so would allow you to present a message to the user about why you were unable to perform the requested action. */ - + let operation = EarthquakeBlockOperation { - self.performSegueWithIdentifier("showEarthquake", sender: nil) + self.performSegue(withIdentifier: "showEarthquake", sender: nil) } - - operation.addCondition(MutuallyExclusive()) - - let blockObserver = BlockObserver { _, errors in + + operation.addCondition(condition: MutuallyExclusive()) + + let blockObserver = BlockObserver(finishHandler: { _, errors in /* - If the operation errored (ex: a condition failed) then the segue - isn't going to happen. We shouldn't leave the row selected. - */ + If the operation errored (ex: a condition failed) then the segue + isn't going to happen. We shouldn't leave the row selected. + */ if !errors.isEmpty { - dispatch_async(dispatch_get_main_queue()) { - tableView.deselectRowAtIndexPath(indexPath, animated: true) + DispatchQueue.main.async { + tableView.deselectRow(at: indexPath as IndexPath, animated: true) } } - } - - operation.addObserver(blockObserver) - + }) + + operation.addObserver(observer: blockObserver) + operationQueue.addOperation(operation) } - - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { - guard let navigationVC = segue.destinationViewController as? UINavigationController, - detailVC = navigationVC.viewControllers.first as? EarthquakeTableViewController else { + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + guard let navigationVC = segue.destination as? UINavigationController, + let detailVC = navigationVC.viewControllers.first as? EarthquakeTableViewController else { return } detailVC.queue = operationQueue if let indexPath = tableView.indexPathForSelectedRow { - detailVC.earthquake = fetchedResultsController?.objectAtIndexPath(indexPath) as? Earthquake + detailVC.earthquake = fetchedResultsController?.object(at: indexPath) as? Earthquake } } - + @IBAction func startRefreshing(sender: UIRefreshControl) { getEarthquakes() } - + private func getEarthquakes(userInitiated: Bool = true) { if let context = fetchedResultsController?.managedObjectContext { let getEarthquakesOperation = GetEarthquakesOperation(context: context) { - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { self.refreshControl?.endRefreshing() self.updateUI() } @@ -138,13 +138,12 @@ class EarthquakesTableViewController: UITableViewController { We don't have a context to operate on, so wait a bit and just make the refresh control end. */ - let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.3 * Double(NSEC_PER_SEC))) - dispatch_after(when, dispatch_get_main_queue()) { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { self.refreshControl?.endRefreshing() } } } - + private func updateUI() { do { try fetchedResultsController?.performFetch() diff --git a/Earthquakes/GetEarthquakesOperation.swift b/Earthquakes/GetEarthquakesOperation.swift index ba56c77..eda5a03 100644 --- a/Earthquakes/GetEarthquakesOperation.swift +++ b/Earthquakes/GetEarthquakesOperation.swift @@ -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 } } diff --git a/Earthquakes/Images.xcassets/AppIcon.appiconset/Contents.json b/Earthquakes/Images.xcassets/AppIcon.appiconset/Contents.json index e44f4ce..d27f2a0 100644 --- a/Earthquakes/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Earthquakes/Images.xcassets/AppIcon.appiconset/Contents.json @@ -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 } -} \ No newline at end of file +} diff --git a/Earthquakes/LoadModelOperation.swift b/Earthquakes/LoadModelOperation.swift index b81c8c0..122be85 100644 --- a/Earthquakes/LoadModelOperation.swift +++ b/Earthquakes/LoadModelOperation.swift @@ -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()) + addCondition(condition: MutuallyExclusive()) } 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) } } diff --git a/Earthquakes/MoreInformationOperation.swift b/Earthquakes/MoreInformationOperation.swift index 265250b..3bf97e0 100644 --- a/Earthquakes/MoreInformationOperation.swift +++ b/Earthquakes/MoreInformationOperation.swift @@ -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()) + addCondition(condition: MutuallyExclusive()) } // 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() } } diff --git a/Earthquakes/NetworkObserver.swift b/Earthquakes/NetworkObserver.swift index fbd2f19..0480a07 100644 --- a/Earthquakes/NetworkObserver.swift +++ b/Earthquakes/NetworkObserver.swift @@ -17,17 +17,17 @@ struct NetworkObserver: OperationObserver { init() { } - func operationDidStart(operation: Operation) { - dispatch_async(dispatch_get_main_queue()) { + func operationDidStart(operation: EarthquakeOperation) { + DispatchQueue.main.async { // Increment the network indicator's "reference count" NetworkIndicatorController.sharedIndicatorController.networkActivityDidStart() } } - func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { } + func operation(operation: EarthquakeOperation, didProduceOperation newOperation: Operation) { } - func operationDidFinish(operation: Operation, errors: [NSError]) { - dispatch_async(dispatch_get_main_queue()) { + func operationDidFinish(operation: EarthquakeOperation, errors: [NSError]) { + DispatchQueue.main.async { // Decrement the network indicator's "reference count". NetworkIndicatorController.sharedIndicatorController.networkActivityDidEnd() } @@ -48,7 +48,7 @@ private class NetworkIndicatorController { // MARK: Methods func networkActivityDidStart() { - assert(NSThread.isMainThread(), "Altering network activity indicator state can only be done on the main thread.") + assert(Thread.isMainThread, "Altering network activity indicator state can only be done on the main thread.") activityCount += 1 @@ -56,7 +56,7 @@ private class NetworkIndicatorController { } func networkActivityDidEnd() { - assert(NSThread.isMainThread(), "Altering network activity indicator state can only be done on the main thread.") + assert(Thread.isMainThread, "Altering network activity indicator state can only be done on the main thread.") activityCount -= 1 @@ -73,7 +73,7 @@ private class NetworkIndicatorController { hiding of the indicator by one second. This provides the chance to come in and invalidate the timer before it fires. */ - visibilityTimer = Timer(interval: 1.0) { + visibilityTimer = Timer(interval: 1) { self.hideIndicator() } } @@ -82,13 +82,13 @@ private class NetworkIndicatorController { private func showIndicator() { visibilityTimer?.cancel() visibilityTimer = nil - UIApplication.sharedApplication().networkActivityIndicatorVisible = true + UIApplication.shared.isNetworkActivityIndicatorVisible = true } private func hideIndicator() { visibilityTimer?.cancel() visibilityTimer = nil - UIApplication.sharedApplication().networkActivityIndicatorVisible = false + UIApplication.shared.isNetworkActivityIndicatorVisible = false } } @@ -100,10 +100,8 @@ class Timer { // MARK: Initialization - init(interval: NSTimeInterval, handler: dispatch_block_t) { - let when = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC))) - - dispatch_after(when, dispatch_get_main_queue()) { [weak self] in + init(interval: Int, handler: @escaping () -> Void) { + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(interval)) { [weak self] in if self?.isCancelled == false { handler() } @@ -113,4 +111,4 @@ class Timer { func cancel() { isCancelled = true } -} \ No newline at end of file +} diff --git a/Earthquakes/Operations/BlockObserver.swift b/Earthquakes/Operations/BlockObserver.swift index f09b4b3..fbd9cf5 100644 --- a/Earthquakes/Operations/BlockObserver.swift +++ b/Earthquakes/Operations/BlockObserver.swift @@ -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) } } diff --git a/Earthquakes/Operations/CKContainer+Operations.swift b/Earthquakes/Operations/CKContainer+Operations.swift index 52079c7..47d19ea 100644 --- a/Earthquakes/Operations/CKContainer+Operations.swift +++ b/Earthquakes/Operations/CKContainer+Operations.swift @@ -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) } } diff --git a/Earthquakes/Operations/CalendarCondition.swift b/Earthquakes/Operations/CalendarCondition.swift index ee3df1a..5dbaf98 100644 --- a/Earthquakes/Operations/CalendarCondition.swift +++ b/Earthquakes/Operations/CalendarCondition.swift @@ -21,23 +21,23 @@ struct CalendarCondition: OperationCondition { self.entityType = entityType } - func dependencyForOperation(operation: Operation) -> NSOperation? { + func dependencyForOperation(operation: EarthquakeOperation) -> Operation? { return CalendarPermissionOperation(entityType: entityType) } - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { - switch EKEventStore.authorizationStatusForEntityType(entityType) { - case .Authorized: + func evaluateForOperation(operation: EarthquakeOperation, completion: (OperationConditionResult) -> Void) { + switch EKEventStore.authorizationStatus(for: entityType) { + case .authorized: completion(.Satisfied) - default: - // We are not authorized to access entities of this type. - let error = NSError(code: .ConditionFailed, userInfo: [ - OperationConditionKey: self.dynamicType.name, - self.dynamicType.entityTypeKey: entityType.rawValue - ]) - - completion(.Failed(error)) + default: + // We are not authorized to access entities of this type. + let error = NSError(code: .ConditionFailed, userInfo: [ + OperationConditionKey: type(of: self).name, + type(of: self).entityTypeKey: entityType.rawValue, + ]) + + completion(.Failed(error)) } } } @@ -53,22 +53,22 @@ private let SharedEventStore = EKEventStore() A private `Operation` that will request access to the user's Calendar/Reminders, if it has not already been granted. */ -private class CalendarPermissionOperation: Operation { +private class CalendarPermissionOperation: EarthquakeOperation { let entityType: EKEntityType init(entityType: EKEntityType) { self.entityType = entityType super.init() - addCondition(AlertPresentation()) + addCondition(condition: AlertPresentation()) } override func execute() { - let status = EKEventStore.authorizationStatusForEntityType(entityType) + let status = EKEventStore.authorizationStatus(for: entityType) switch status { - case .NotDetermined: - dispatch_async(dispatch_get_main_queue()) { - SharedEventStore.requestAccessToEntityType(self.entityType) { granted, error in + case .notDetermined: + DispatchQueue.main.async { + SharedEventStore.requestAccess(to: self.entityType) { granted, error in self.finish() } } diff --git a/Earthquakes/Operations/CloudCondition.swift b/Earthquakes/Operations/CloudCondition.swift index 5c303cc..be7921c 100644 --- a/Earthquakes/Operations/CloudCondition.swift +++ b/Earthquakes/Operations/CloudCondition.swift @@ -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?) } } diff --git a/Earthquakes/Operations/DelayOperation.swift b/Earthquakes/Operations/DelayOperation.swift index 4c85cfe..1be41e6 100644 --- a/Earthquakes/Operations/DelayOperation.swift +++ b/Earthquakes/Operations/DelayOperation.swift @@ -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() } } diff --git a/Earthquakes/Operations/Dictionary+Operations.swift b/Earthquakes/Operations/Dictionary+Operations.swift index c092727..8f9375b 100644 --- a/Earthquakes/Operations/Dictionary+Operations.swift +++ b/Earthquakes/Operations/Dictionary+Operations.swift @@ -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: Sequence, @noescape keyMapper: Value -> Key?) { + init(sequence: S, keyMapper: (Value) -> Key?) where S.Element == Value { self.init() - for item in sequence { if let key = keyMapper(item) { self[key] = item diff --git a/Earthquakes/Operations/EarthquakeBlockOperation.swift b/Earthquakes/Operations/EarthquakeBlockOperation.swift index 5687b8f..b98ca77 100644 --- a/Earthquakes/Operations/EarthquakeBlockOperation.swift +++ b/Earthquakes/Operations/EarthquakeBlockOperation.swift @@ -9,15 +9,18 @@ This code shows how to create a simple subclass of Operation. import Foundation /// A closure type that takes a closure as its parameter. -typealias OperationBlock = (Void -> Void) -> Void +typealias OperationBlock = (@escaping () -> Void) -> Void -/// A sublcass of `Operation` to execute a closure. -class EarthquakeBlockOperation: Operation { +@available(*, deprecated, message: "Use () -> Void directly, it's shorter") +typealias dispatch_block_t = () -> Void + +/// A sublcass of `EarthquakeOperation` to execute a closure. +class EarthquakeBlockOperation: EarthquakeOperation { private let block: OperationBlock? - + /** The designated initializer. - + - parameter block: The closure to run when the operation executes. This closure will be run on an arbitrary queue. The parameter passed to the block **MUST** be invoked by your code, or else the `BlockOperation` @@ -28,30 +31,30 @@ class EarthquakeBlockOperation: Operation { self.block = block super.init() } - + /** A convenience initializer to execute a block on the main queue. - + - parameter mainQueueBlock: The block to execute on the main queue. Note that this block does not have a "continuation" block to execute (unlike the designated initializer). The operation will be automatically ended after the `mainQueueBlock` is executed. */ - convenience init(mainQueueBlock: dispatch_block_t) { + convenience init(mainQueueBlock: @escaping () -> Void) { self.init(block: { continuation in - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { mainQueueBlock() continuation() } }) } - + override func execute() { guard let block = block else { finish() return } - + block { self.finish() } diff --git a/Earthquakes/Operations/Operation.swift b/Earthquakes/Operations/EarthquakeOperation.swift similarity index 82% rename from Earthquakes/Operations/Operation.swift rename to Earthquakes/Operations/EarthquakeOperation.swift index 6782fe0..7a339d3 100644 --- a/Earthquakes/Operations/Operation.swift +++ b/Earthquakes/Operations/EarthquakeOperation.swift @@ -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 { - return ["state"] + override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set { + switch key { + case "isExecuting", "isFinished", "isReady": + return ["state"] + default: + return [] + } } - - class func keyPathsForValuesAffectingIsExecuting() -> Set { - return ["state"] - } - - class func keyPathsForValuesAffectingIsFinished() -> Set { - 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 } diff --git a/Earthquakes/Operations/OperationQueue.swift b/Earthquakes/Operations/EarthquakeOperationQueue.swift similarity index 70% rename from Earthquakes/Operations/OperationQueue.swift rename to Earthquakes/Operations/EarthquakeOperationQueue.swift index 371b8d9..4486996 100644 --- a/Earthquakes/Operations/OperationQueue.swift +++ b/Earthquakes/Operations/EarthquakeOperationQueue.swift @@ -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. diff --git a/Earthquakes/Operations/ExclusivityController.swift b/Earthquakes/Operations/ExclusivityController.swift index cf96a90..a7c7903 100644 --- a/Earthquakes/Operations/ExclusivityController.swift +++ b/Earthquakes/Operations/ExclusivityController.swift @@ -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 } } diff --git a/Earthquakes/Operations/GroupOperation.swift b/Earthquakes/Operations/GroupOperation.swift index a304fc6..b845848 100644 --- a/Earthquakes/Operations/GroupOperation.swift +++ b/Earthquakes/Operations/GroupOperation.swift @@ -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) } } } diff --git a/Earthquakes/Operations/HealthCondition.swift b/Earthquakes/Operations/HealthCondition.swift index 4d29ecb..9e4e26f 100644 --- a/Earthquakes/Operations/HealthCondition.swift +++ b/Earthquakes/Operations/HealthCondition.swift @@ -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, completion: OperationConditionResult -> Void) { + private func failed(unauthorizedShareTypes: Set, 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 let readTypes: Set @@ -105,20 +105,20 @@ private class HealthPermissionOperation: Operation { super.init() - addCondition(MutuallyExclusive()) - addCondition(MutuallyExclusive()) - addCondition(AlertPresentation()) + addCondition(condition: MutuallyExclusive()) + addCondition(condition: MutuallyExclusive()) + 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() } } diff --git a/Earthquakes/Operations/LocationCondition.swift b/Earthquakes/Operations/LocationCondition.swift index 97ddbfd..09d32dd 100644 --- a/Earthquakes/Operations/LocationCondition.swift +++ b/Earthquakes/Operations/LocationCondition.swift @@ -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() } } diff --git a/Earthquakes/Operations/LocationOperation.swift b/Earthquakes/Operations/LocationOperation.swift index 41d9807..0862493 100644 --- a/Earthquakes/Operations/LocationOperation.swift +++ b/Earthquakes/Operations/LocationOperation.swift @@ -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()) + addCondition(condition: LocationCondition(usage: .WhenInUse)) + addCondition(condition: MutuallyExclusive()) } override func execute() { - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { /* `CLLocationManager` needs to be created on a thread with an active run loop, so for simplicity we do this on the main queue. @@ -48,7 +48,7 @@ class LocationOperation: Operation, CLLocationManagerDelegate { } override func cancel() { - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { self.stopLocationUpdates() super.cancel() } @@ -60,9 +60,9 @@ class LocationOperation: Operation, CLLocationManagerDelegate { } // MARK: CLLocationManagerDelegate - - func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - guard let location = locations.last where location.horizontalAccuracy <= accuracy else { + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + guard let location = locations.last, location.horizontalAccuracy <= accuracy else { return } @@ -71,8 +71,8 @@ class LocationOperation: Operation, CLLocationManagerDelegate { finish() } - func locationManager(manager: CLLocationManager, didFailWithError error: NSError) { + private func locationManager(manager: CLLocationManager, didFailWithError error: NSError) { stopLocationUpdates() - finishWithError(error) + finishWithError(error: error) } } diff --git a/Earthquakes/Operations/MutuallyExclusive.swift b/Earthquakes/Operations/MutuallyExclusive.swift index 3300eab..5b2faa3 100644 --- a/Earthquakes/Operations/MutuallyExclusive.swift +++ b/Earthquakes/Operations/MutuallyExclusive.swift @@ -20,11 +20,11 @@ struct MutuallyExclusive: 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) } } diff --git a/Earthquakes/Operations/NSLock+Operations.swift b/Earthquakes/Operations/NSLock+Operations.swift index ef7146f..9d4d5b7 100644 --- a/Earthquakes/Operations/NSLock+Operations.swift +++ b/Earthquakes/Operations/NSLock+Operations.swift @@ -9,10 +9,10 @@ import Foundation extension NSLock { - func withCriticalScope(@noescape block: Void -> T) -> T { + func withCriticalScope(block: () -> T) -> T { lock() let value = block() unlock() return value } -} \ No newline at end of file +} diff --git a/Earthquakes/Operations/NSOperation+Operations.swift b/Earthquakes/Operations/NSOperation+Operations.swift index 33e661b..54f6662 100644 --- a/Earthquakes/Operations/NSOperation+Operations.swift +++ b/Earthquakes/Operations/NSOperation+Operations.swift @@ -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) } diff --git a/Earthquakes/Operations/NegatedCondition.swift b/Earthquakes/Operations/NegatedCondition.swift index e805b46..1a6eec9 100644 --- a/Earthquakes/Operations/NegatedCondition.swift +++ b/Earthquakes/Operations/NegatedCondition.swift @@ -32,17 +32,17 @@ struct NegatedCondition: 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)) diff --git a/Earthquakes/Operations/NoCancelledDependencies.swift b/Earthquakes/Operations/NoCancelledDependencies.swift index 92d1ce0..7941fa8 100644 --- a/Earthquakes/Operations/NoCancelledDependencies.swift +++ b/Earthquakes/Operations/NoCancelledDependencies.swift @@ -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)) diff --git a/Earthquakes/Operations/OperationCondition.swift b/Earthquakes/Operations/OperationCondition.swift index e400f17..0cd05d8 100644 --- a/Earthquakes/Operations/OperationCondition.swift +++ b/Earthquakes/Operations/OperationCondition.swift @@ -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)) } diff --git a/Earthquakes/Operations/OperationErrors.swift b/Earthquakes/Operations/OperationErrors.swift index a1cd6e2..f841804 100644 --- a/Earthquakes/Operations/OperationErrors.swift +++ b/Earthquakes/Operations/OperationErrors.swift @@ -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) } } diff --git a/Earthquakes/Operations/OperationObserver.swift b/Earthquakes/Operations/OperationObserver.swift index ee2b994..86cf0dc 100644 --- a/Earthquakes/Operations/OperationObserver.swift +++ b/Earthquakes/Operations/OperationObserver.swift @@ -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]) } diff --git a/Earthquakes/Operations/PassbookCondition.swift b/Earthquakes/Operations/PassbookCondition.swift index 30d7e31..a9ab523 100644 --- a/Earthquakes/Operations/PassbookCondition.swift +++ b/Earthquakes/Operations/PassbookCondition.swift @@ -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)) diff --git a/Earthquakes/Operations/PhotosCondition.swift b/Earthquakes/Operations/PhotosCondition.swift index d606996..e065a04 100644 --- a/Earthquakes/Operations/PhotosCondition.swift +++ b/Earthquakes/Operations/PhotosCondition.swift @@ -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() } diff --git a/Earthquakes/Operations/ReachabilityCondition.swift b/Earthquakes/Operations/ReachabilityCondition.swift index 85646a9..5b8a2a4 100644 --- a/Earthquakes/Operations/ReachabilityCondition.swift +++ b/Earthquakes/Operations/ReachabilityCondition.swift @@ -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) } diff --git a/Earthquakes/Operations/RemoteNotificationCondition.swift b/Earthquakes/Operations/RemoteNotificationCondition.swift index 707b05a..50b6349 100644 --- a/Earthquakes/Operations/RemoteNotificationCondition.swift +++ b/Earthquakes/Operations/RemoteNotificationCondition.swift @@ -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()) + addCondition(condition: MutuallyExclusive()) } 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 diff --git a/Earthquakes/Operations/SilentCondition.swift b/Earthquakes/Operations/SilentCondition.swift index 6f8a12a..cdc8f16 100644 --- a/Earthquakes/Operations/SilentCondition.swift +++ b/Earthquakes/Operations/SilentCondition.swift @@ -29,12 +29,12 @@ struct SilentCondition: 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) } } diff --git a/Earthquakes/Operations/TimeoutObserver.swift b/Earthquakes/Operations/TimeoutObserver.swift index e9f8752..edadff9 100644 --- a/Earthquakes/Operations/TimeoutObserver.swift +++ b/Earthquakes/Operations/TimeoutObserver.swift @@ -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. } } diff --git a/Earthquakes/Operations/UIUserNotifications+Operations.swift b/Earthquakes/Operations/UIUserNotifications+Operations.swift index 530fe82..ee3fcf4 100644 --- a/Earthquakes/Operations/UIUserNotifications+Operations.swift +++ b/Earthquakes/Operations/UIUserNotifications+Operations.swift @@ -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) } } diff --git a/Earthquakes/Operations/URLSessionTaskOperation.swift b/Earthquakes/Operations/URLSessionTaskOperation.swift index b743b95..550f709 100644 --- a/Earthquakes/Operations/URLSessionTaskOperation.swift +++ b/Earthquakes/Operations/URLSessionTaskOperation.swift @@ -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) { + + 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() } diff --git a/Earthquakes/Operations/UserNotificationCondition.swift b/Earthquakes/Operations/UserNotificationCondition.swift index 4b2aa78..9f17a09 100644 --- a/Earthquakes/Operations/UserNotificationCondition.swift +++ b/Earthquakes/Operations/UserNotificationCondition.swift @@ -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 diff --git a/Earthquakes/ParseEarthquakesOperation.swift b/Earthquakes/ParseEarthquakesOperation.swift index 7968a46..cb44140 100644 --- a/Earthquakes/ParseEarthquakesOperation.swift +++ b/Earthquakes/ParseEarthquakesOperation.swift @@ -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 diff --git a/Earthquakes/SplitViewController.swift b/Earthquakes/SplitViewController.swift index ab633b0..2b0739d 100644 --- a/Earthquakes/SplitViewController.swift +++ b/Earthquakes/SplitViewController.swift @@ -14,7 +14,7 @@ class SplitViewController: UISplitViewController { override func awakeFromNib() { super.awakeFromNib() - preferredDisplayMode = .AllVisible + preferredDisplayMode = .allVisible delegate = self }