Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopt Codable #127

Merged
merged 7 commits into from Dec 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
86 changes: 52 additions & 34 deletions MapboxGeocoder/MBGeocoder.swift
@@ -1,3 +1,5 @@
import Foundation

typealias JSONDictionary = [String: Any]

/// Indicates that an error occurred in MapboxGeocoder.
Expand Down Expand Up @@ -170,17 +172,22 @@ open class Geocoder: NSObject {
- parameter completionHandler: The closure (block) to call with the resulting placemarks. This closure is executed on the application鈥檚 main thread.
- returns: The data task used to perform the HTTP request. If, while waiting for the completion handler to execute, you no longer want the resulting placemarks, cancel this task.
*/

@discardableResult
@objc(geocodeWithOptions:completionHandler:)
open func geocode(_ options: GeocodeOptions, completionHandler: @escaping CompletionHandler) -> URLSessionDataTask {
let url = urlForGeocoding(options)
let task = dataTaskWithURL(url, completionHandler: { (json) in
let featureCollection = json as! JSONDictionary
assert(featureCollection["type"] as? String == "FeatureCollection")
let features = featureCollection["features"] as! [JSONDictionary]
let attribution = featureCollection["attribution"] as? String

let placemarks = features.flatMap { GeocodedPlacemark(featureJSON: $0) }
completionHandler(placemarks, attribution, nil)

let task = dataTaskWithURL(url, completionHandler: { (data) in
guard let data = data else { return }
let decoder = JSONDecoder()
do {
let result = try decoder.decode(GeocodeResult.self, from: data)
assert(result.type == "FeatureCollection")
completionHandler(result.placemarks, result.attribution, nil)
} catch {
completionHandler(nil, nil, error as NSError)
}
}) { (error) in
completionHandler(nil, nil, error)
}
Expand All @@ -199,17 +206,24 @@ open class Geocoder: NSObject {
- parameter completionHandler: The closure (block) to call with the resulting placemarks. This closure is executed on the application鈥檚 main thread.
- returns: The data task used to perform the HTTP request. If, while waiting for the completion handler to execute, you no longer want the resulting placemarks, cancel this task.
*/
@discardableResult
open func batchGeocode<T: GeocodeOptions>(_ options: T, completionHandler: @escaping BatchCompletionHandler) -> URLSessionDataTask where T: BatchGeocodeOptions {
let url = urlForGeocoding(options)
let task = dataTaskWithURL(url, completionHandler: { (json) in
let featureCollections = json as! [JSONDictionary]
let placemarksByQuery = featureCollections.map { (featureCollection) -> [GeocodedPlacemark] in
assert(featureCollection["type"] as? String == "FeatureCollection")
let features = featureCollection["features"] as! [JSONDictionary]
return features.flatMap { GeocodedPlacemark(featureJSON: $0) }

let task = dataTaskWithURL(url, completionHandler: { (data) in
guard let data = data else { return }
let decoder = JSONDecoder()

do {
let result = try decoder.decode([GeocodeResult].self, from: data)
let placemarks = result.map { $0.placemarks }
let attributionsByQuery = result.map { $0.attribution }
completionHandler(placemarks, attributionsByQuery, nil)

} catch {
completionHandler(nil, nil, error as NSError)
}
let attributionsByQuery = featureCollections.map { $0["attribution"] as! String }
completionHandler(placemarksByQuery, attributionsByQuery, nil)

}) { (error) in
completionHandler(nil, nil, error)
}
Expand All @@ -226,34 +240,38 @@ open class Geocoder: NSObject {
- returns: The data task for the URL.
- postcondition: The caller must resume the returned task.
*/
fileprivate func dataTaskWithURL(_ url: URL, completionHandler: @escaping (_ json: Any) -> Void, errorHandler: @escaping (_ error: NSError) -> Void) -> URLSessionDataTask {
fileprivate func dataTaskWithURL(_ url: URL, completionHandler: @escaping (_ data: Data?) -> Void, errorHandler: @escaping (_ error: NSError) -> Void) -> URLSessionDataTask {
var request = URLRequest(url: url)
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
return URLSession.shared.dataTask(with: request) { (data, response, error) in
var json: JSONDictionary = [:]
if let data = data, let mimeType = response?.mimeType, mimeType == "application/json" || mimeType == "application/vnd.geo+json" {
do {
json = try JSONSerialization.jsonObject(with: data, options: []) as! JSONDictionary
} catch {
assert(false, "Invalid data")
}
}

guard let data = data else { return }
let decoder = JSONDecoder()

let apiMessage = json["message"] as? String
guard data != nil && error == nil && apiMessage == nil else {
let apiError = Geocoder.descriptiveError(json, response: response, underlyingError: error as NSError?)
do {
let result = try decoder.decode(GeocodeAPIResult.self, from: data)
guard result.message == nil else {
let apiError = Geocoder.descriptiveError(["message": result.message!], response: response, underlyingError: error as NSError?)
DispatchQueue.main.async {
errorHandler(apiError)
}
return
}
DispatchQueue.main.async {
errorHandler(apiError)
completionHandler(data)
}
} catch {
DispatchQueue.main.async {
errorHandler(error as NSError)
}
return
}

DispatchQueue.main.async {
completionHandler(json)
}
}
}

internal struct GeocodeAPIResult: Codable {
let message: String?
}

/**
The HTTP URL used to fetch the geocodes from the API.
*/
Expand Down