Advanced-NSOperations/Earthquakes/ParseEarthquakesOperation.swift

168 lines
5.2 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Copyright (C) 2015 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
Contains the logic to parse a JSON file of earthquakes and insert them into an NSManagedObjectContext
*/
import Foundation
import CoreData
/// A struct to represent a parsed earthquake.
private struct ParsedEarthquake {
// MARK: Properties.
let date: Date
let identifier, name, link: String
let depth, latitude, longitude, magnitude: Double
// MARK: Initialization
init?(feature: [String: AnyObject]) {
guard let earthquakeID = feature["id"] as? String, !earthquakeID.isEmpty else { return nil }
identifier = earthquakeID
let properties = feature["properties"] as? [String: AnyObject] ?? [:]
name = properties["place"] as? String ?? ""
link = properties["url"] as? String ?? ""
magnitude = properties["mag"] as? Double ?? 0.0
if let offset = properties["time"] as? Double {
date = Date(timeIntervalSince1970: offset / 1000)
}
else {
date = Date.distantFuture
}
let geometry = feature["geometry"] as? [String: AnyObject] ?? [:]
if let coordinates = geometry["coordinates"] as? [Double], coordinates.count == 3 {
longitude = coordinates[0]
latitude = coordinates[1]
// `depth` is in km, but we want to store it in meters.
depth = coordinates[2] * 1000
}
else {
depth = 0
latitude = 0
longitude = 0
}
}
}
/// An `EarthquakeOperation` to parse earthquakes out of a downloaded feed from the USGS.
class ParseEarthquakesOperation: EarthquakeOperation {
let cacheFile: URL
let context: NSManagedObjectContext
/**
- 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: URL, context: NSManagedObjectContext) {
let importContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
importContext.persistentStoreCoordinator = context.persistentStoreCoordinator
/*
Use the overwrite merge policy, because we want any updated objects
to replace the ones in the store.
*/
importContext.mergePolicy = NSOverwriteMergePolicy
self.cacheFile = cacheFile
self.context = importContext
super.init()
name = "Parse Earthquakes"
}
override func execute() {
guard let stream = InputStream(url: cacheFile) else {
finish()
return
}
stream.open()
defer {
stream.close()
}
do {
let json = try JSONSerialization.jsonObject(with: stream, options: []) as? [String: AnyObject]
if let features = json?["features"] as? [[String: AnyObject]] {
parse(features: features)
}
else {
finish()
}
}
catch let jsonError as NSError {
finishWithError(error: jsonError)
}
}
private func parse(features: [[String: AnyObject]]) {
let parsedEarthquakes = features.compactMap { ParsedEarthquake(feature: $0) }
context.perform {
for newEarthquake in parsedEarthquakes {
self.insert(parsed: newEarthquake)
}
let error = self.saveContext()
self.finishWithError(error: error)
}
}
private func insert(parsed: ParsedEarthquake) {
let earthquake = NSEntityDescription.insertNewObject(forEntityName: Earthquake.entityName, into: context) as! Earthquake
earthquake.identifier = parsed.identifier
earthquake.timestamp = parsed.date
earthquake.latitude = parsed.latitude
earthquake.longitude = parsed.longitude
earthquake.depth = parsed.depth
earthquake.webLink = parsed.link
earthquake.name = parsed.name
earthquake.magnitude = parsed.magnitude
}
/**
Save the context, if there are any changes.
- returns: An `NSError` if there was an problem saving the `NSManagedObjectContext`,
otherwise `nil`.
- note: This method returns an `NSError?` because it will be immediately
passed to the `finishWithError()` method, which accepts an `NSError?`.
*/
private func saveContext() -> NSError? {
var error: NSError?
if context.hasChanges {
do {
try context.save()
}
catch let saveError as NSError {
error = saveError
}
}
return error
}
}