简体   繁体   中英

swift function is only being called once

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM