mirror of
https://github.com/samsonjs/Advanced-NSOperations.git
synced 2026-03-25 08:25:47 +00:00
168 lines
5.2 KiB
Swift
168 lines
5.2 KiB
Swift
/*
|
||
Copyright (C) 2015 Apple Inc. All Rights Reserved.
|
||
See LICENSE.txt for this sample’s 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
|
||
}
|
||
}
|