I have this function:
func fetchPlace(coordinate: CLLocationCoordinate2D) {
let searchedTypes = ["cafe"]
let searchRadius: Double = 150
dataProvider.fetchPlacesNearCoordinate(coordinate, radius:searchRadius, types: searchedTypes) { places in
for place: GooglePlace in places {
print(place)
}
}
}
and I am attempting to simply call it twice
self.fetchPlace(CLLocationCoordinate2DMake(40.725203800000003, -74.011287899999999))
self.fetchPlace(CLLocationCoordinate2DMake(40.760920499999997, -73.988664700000001))
However, for some reason, the print statements to show places are only produced for the last call. This is the same no matter how many times I call it, it always only produces for the last method call. Can anyone explain to me why this is?
Because fetchPlacesNearCoordinate
cancels the prior request (which is running asynchronously), you have to make sure you don't initiate the second request until the first one is done.
The simplest way to do that is with a completion handler:
func fetchPlace(for coordinate: CLLocationCoordinate2D, completionHandler: @escaping () -> Void) {
let searchedTypes = ["cafe"]
let searchRadius: Double = 150
dataProvider.fetchPlacesNearCoordinate(coordinate, radius: searchRadius, types: searchedTypes) { places in
for place in places {
print(place)
completionHandler()
}
}
}
And then you can do:
fetchPlace(for: CLLocationCoordinate2DMake(40.725203800000003, -74.011287899999999)) {
self.fetchPlace(for: CLLocationCoordinate2DMake(40.760920499999997, -73.988664700000001)) {
print("done with both requests")
}
}
A more complicated, yet more generalized solution would be to wrap this fetch in a custom, asynchronous, Operation
subclass, and then you could add these requests to a serial queue dedicated for fetch requests. If you need to see what that might look like, let me know.
For example:
let fetchQueue: OperationQueue = {
let queue = OperationQueue()
queue.name = Bundle.main.bundleIdentifier! + ".fetch"
queue.maxConcurrentOperationCount = 1
return queue
}()
let provider = GoogleDataProvider()
override func viewDidLoad() {
super.viewDidLoad()
let completionOperation = BlockOperation {
print("done with both requests")
}
let coordinate1 = CLLocationCoordinate2DMake(40.725203800000003, -74.011287899999999)
let operation1 = FetchOperation(provider: provider, coordinate: coordinate1)
completionOperation.addDependency(operation1)
fetchQueue.addOperation(operation1)
let coordinate2 = CLLocationCoordinate2DMake(40.760920499999997, -73.988664700000001)
let operation2 = FetchOperation(provider: provider, coordinate: coordinate2)
completionOperation.addDependency(operation2)
fetchQueue.addOperation(operation2)
OperationQueue.main.addOperation(completionOperation)
}
Where:
class FetchOperation: AsynchronousOperation {
let provider: GoogleDataProvider
let coordinate: CLLocationCoordinate2D
init(provider: GoogleDataProvider, coordinate: CLLocationCoordinate2D) {
self.provider = provider
self.coordinate = coordinate
}
override func main() {
fetchPlace(for: coordinate)
}
func fetchPlace(for coordinate: CLLocationCoordinate2D) {
let searchedTypes = ["cafe"]
let searchRadius: Double = 150
provider.fetchPlacesNearCoordinate(coordinate, radius:searchRadius, types: searchedTypes) { places in
for place: GooglePlace in places {
print(place)
self.completeOperation()
}
}
}
}
And:
//
// AsynchronousOperation.swift
//
// Created by Robert Ryan on 9/20/14.
// Copyright (c) 2014 Robert Ryan. All rights reserved.
//
import Foundation
/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `Operation` subclass. So, to developer
/// a concurrent NSOperation subclass, you instead subclass this class which:
///
/// - must override `main()` with the tasks that initiate the asynchronous task;
///
/// - must call `completeOperation()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
/// necessary and then ensuring that `completeOperation()` is called; or
/// override `cancel` method, calling `super.cancel()` and then cleaning-up
/// and ensuring `completeOperation()` is called.
public class AsynchronousOperation : Operation {
override public var isAsynchronous: Bool { return true }
private let stateLock = NSLock()
private var _executing: Bool = false
override private(set) public var isExecuting: Bool {
get {
stateLock.withCriticalScope { _executing }
}
set {
willChangeValue(forKey: "isExecuting")
stateLock.withCriticalScope { _executing = newValue }
didChangeValue(forKey: "isExecuting")
}
}
private var _finished: Bool = false
override private(set) public var isFinished: Bool {
get {
stateLock.withCriticalScope { _finished }
}
set {
willChangeValue(forKey: "isFinished")
stateLock.withCriticalScope { _finished = newValue }
didChangeValue(forKey: "isFinished")
}
}
/// Complete the operation
///
/// This will result in the appropriate KVN of isFinished and isExecuting
public func completeOperation() {
if isExecuting {
isExecuting = false
}
if !isFinished {
isFinished = true
}
}
override public func start() {
if isCancelled {
isFinished = true
return
}
isExecuting = true
main()
}
override public func main() {
fatalError("subclasses must override `main`")
}
}
/// Asynchronous Operation base class
///
/// This class lets you perform asynchronous block operation. Make sure that the
/// the provided `block` calls `completeOperation`, or else this operation will
/// never finish.
public class AsynchronousBlockOperation : AsynchronousOperation {
private var block: ((AsynchronousOperation) -> Void)?
init(block: @escaping (AsynchronousOperation) -> Void) {
self.block = block
super.init()
}
override public func main() {
block?(self)
}
override public func completeOperation() {
block = nil
super.completeOperation()
}
}
extension NSLock {
/// Perform closure within lock.
///
/// An extension to `NSLock` to simplify executing critical code.
///
/// Adapted from Advanced NSOperations sample code in WWDC 2015's [Advanced NSOperations](https://developer.apple.com/videos/play/wwdc2015/226/).
/// Source available at https://developer.apple.com/sample-code/wwdc/2015/downloads/Advanced-NSOperations.zip
/// (FWIW, overall the source is very outdated, and has some dubious implementation details, but this
/// particular method is very useful for simple use of locks for synchronization.)
///
/// - parameter block: The closure to be performed.
func withCriticalScope<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
If you were following along with this tutorial https://www.raywenderlich.com/109888/google-maps-ios-sdk-tutorial
you can see in the code below you can see that if there is a running tasking that task is canceled and another one starts.
GoogleDataProvider.swift
var placesTask: NSURLSessionDataTask?
var session: NSURLSession {
return NSURLSession.sharedSession()
}
func fetchPlacesNearCoordinate(coordinate: CLLocationCoordinate2D, radius: Double, types:[String], completion: (([GooglePlace]) -> Void)) -> (){
var urlString = "http://localhost:10000/maps/api/place/nearbysearch/json?location=\(coordinate.latitude),\(coordinate.longitude)&radius=\(radius)&rankby=prominence&sensor=true"
let typesString = types.count > 0 ? types.joinWithSeparator("|") : "food"
urlString += "&types=\(typesString)"
urlString = urlString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
//HERE!
if let task = placesTask where task.taskIdentifier > 0 && task.state == .Running {
task.cancel()
}
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
placesTask = session.dataTaskWithURL(NSURL(string: urlString)!) {data, response, error in
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
var placesArray = [GooglePlace]()
if let aData = data {
let json = JSON(data:aData, options:NSJSONReadingOptions.MutableContainers, error:nil)
if let results = json["results"].arrayObject as? [[String : AnyObject]] {
for rawPlace in results {
let place = GooglePlace(dictionary: rawPlace, acceptedTypes: types)
placesArray.append(place)
if let reference = place.photoReference {
self.fetchPhotoFromReference(reference) { image in
place.photo = image
}
}
}
}
}
dispatch_async(dispatch_get_main_queue()) {
completion(placesArray)
}
}
placesTask?.resume()
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.