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

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

View file

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

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,28 +13,28 @@ import CloudKit
class EarthquakesTableViewController: UITableViewController {
// MARK: Properties
var fetchedResultsController: NSFetchedResultsController?
let operationQueue = OperationQueue()
var fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>?
let operationQueue = EarthquakeOperationQueue()
// MARK: View Controller
override func viewDidLoad() {
super.viewDidLoad()
let operation = LoadModelOperation { context in
// Now that we have a context, build our `FetchedResultsController`.
dispatch_async(dispatch_get_main_queue()) {
let request = NSFetchRequest(entityName: Earthquake.entityName)
DispatchQueue.main.async {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: Earthquake.entityName)
request.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
request.fetchLimit = 100
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
let controller = NSFetchedResultsController<NSFetchRequestResult>(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
self.fetchedResultsController = controller
self.updateUI()
}
}
@ -42,89 +42,89 @@ class EarthquakesTableViewController: UITableViewController {
operationQueue.addOperation(operation)
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController?.sections?.count ?? 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section = fetchedResultsController?.sections?[section]
return section?.numberOfObjects ?? 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("earthquakeCell", forIndexPath: indexPath) as! EarthquakeTableViewCell
if let earthquake = fetchedResultsController?.objectAtIndexPath(indexPath) as? Earthquake {
cell.configure(earthquake)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "earthquakeCell", for: indexPath as IndexPath) as! EarthquakeTableViewCell
if let earthquake = fetchedResultsController?.object(at: indexPath) as? Earthquake {
cell.configure(earthquake: earthquake)
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
/*
Instead of performing the segue directly, we can wrap it in a
`EarthquakeBlockOperation`. This allows us to attach conditions to the
operation. For example, you could make it so that you could only perform
the segue if the network is reachable and you have access to the user's
Photos library.
If you decide to use this pattern in your apps, choose conditions that
are sensible and do not place onerous requirements on the user.
It's also worth noting that the Observer attached to the
`EarthquakeBlockOperation` will cause the tableview row to be deselected
automatically if the `EarthquakeOperation` fails.
You may choose to add your own observer to introspect the errors reported
as the operation finishes. Doing so would allow you to present a message
to the user about why you were unable to perform the requested action.
*/
let operation = EarthquakeBlockOperation {
self.performSegueWithIdentifier("showEarthquake", sender: nil)
self.performSegue(withIdentifier: "showEarthquake", sender: nil)
}
operation.addCondition(MutuallyExclusive<UIViewController>())
let blockObserver = BlockObserver { _, errors in
operation.addCondition(condition: MutuallyExclusive<UIViewController>())
let blockObserver = BlockObserver(finishHandler: { _, errors in
/*
If the operation errored (ex: a condition failed) then the segue
isn't going to happen. We shouldn't leave the row selected.
*/
If the operation errored (ex: a condition failed) then the segue
isn't going to happen. We shouldn't leave the row selected.
*/
if !errors.isEmpty {
dispatch_async(dispatch_get_main_queue()) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
DispatchQueue.main.async {
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
}
}
}
operation.addObserver(blockObserver)
})
operation.addObserver(observer: blockObserver)
operationQueue.addOperation(operation)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let navigationVC = segue.destinationViewController as? UINavigationController,
detailVC = navigationVC.viewControllers.first as? EarthquakeTableViewController else {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let navigationVC = segue.destination as? UINavigationController,
let detailVC = navigationVC.viewControllers.first as? EarthquakeTableViewController else {
return
}
detailVC.queue = operationQueue
if let indexPath = tableView.indexPathForSelectedRow {
detailVC.earthquake = fetchedResultsController?.objectAtIndexPath(indexPath) as? Earthquake
detailVC.earthquake = fetchedResultsController?.object(at: indexPath) as? Earthquake
}
}
@IBAction func startRefreshing(sender: UIRefreshControl) {
getEarthquakes()
}
private func getEarthquakes(userInitiated: Bool = true) {
if let context = fetchedResultsController?.managedObjectContext {
let getEarthquakesOperation = GetEarthquakesOperation(context: context) {
dispatch_async(dispatch_get_main_queue()) {
DispatchQueue.main.async {
self.refreshControl?.endRefreshing()
self.updateUI()
}
@ -138,13 +138,12 @@ class EarthquakesTableViewController: UITableViewController {
We don't have a context to operate on, so wait a bit and just make
the refresh control end.
*/
let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.3 * Double(NSEC_PER_SEC)))
dispatch_after(when, dispatch_get_main_queue()) {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
self.refreshControl?.endRefreshing()
}
}
}
private func updateUI() {
do {
try fetchedResultsController?.performFetch()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,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()
}
}

View file

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

View file

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

View file

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

View file

@ -9,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()
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,25 +15,25 @@ import CoreLocation
prompt for `WhenInUse` location authorization, if the app does not already
have it.
*/
class LocationOperation: Operation, CLLocationManagerDelegate {
class LocationOperation: EarthquakeOperation, CLLocationManagerDelegate {
// MARK: Properties
private let accuracy: CLLocationAccuracy
private var manager: CLLocationManager?
private let handler: CLLocation -> Void
private let handler: (CLLocation) -> Void
// MARK: Initialization
init(accuracy: CLLocationAccuracy, locationHandler: CLLocation -> Void) {
init(accuracy: CLLocationAccuracy, locationHandler: @escaping (CLLocation) -> Void) {
self.accuracy = accuracy
self.handler = locationHandler
super.init()
addCondition(LocationCondition(usage: .WhenInUse))
addCondition(MutuallyExclusive<CLLocationManager>())
addCondition(condition: LocationCondition(usage: .WhenInUse))
addCondition(condition: MutuallyExclusive<CLLocationManager>())
}
override func execute() {
dispatch_async(dispatch_get_main_queue()) {
DispatchQueue.main.async {
/*
`CLLocationManager` needs to be created on a thread with an active
run loop, so for simplicity we do this on the main queue.
@ -48,7 +48,7 @@ class LocationOperation: Operation, CLLocationManagerDelegate {
}
override func cancel() {
dispatch_async(dispatch_get_main_queue()) {
DispatchQueue.main.async {
self.stopLocationUpdates()
super.cancel()
}
@ -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)
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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