4

My moto is Auto search, I have been trying with URLSession, When i am trying to search slowly the requests are handled as expected(when there is no text the response is empty i mean the placesarray) but when i am trying to clear the text or hit any searchtext speedily then the previous request response are being appended in the placesarray. I tried with cancelling the previous request yet i am not getting the result(i.e previous response not be appended)

func autoSearch(text:String){    
   let urlRequest = URLRequest(url: self.getQueryFormedBingURL()!)

   let session = URLSession.shared
   session.getTasksWithCompletionHandler
   {
      (dataTasks, uploadTasks, downloadTasks) -> Void in
      // self.cancelTasksByUrl(tasks: dataTasks     as [URLSessionTask])
      self.cancelTasksByUrl(tasks: uploadTasks   as [URLSessionTask])
      self.cancelTasksByUrl(tasks: downloadTasks as [URLSessionTask])
   }
   let task = session.dataTask(with: urlRequest, completionHandler: { (data, response, error) -> Void in

   print("response \(response)")

   if let data = data {

      let json = try? JSONSerialization.jsonObject(with: data, options: [])

      if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {

         if let jsonDic = json as? NSDictionary {

             let status = jsonDic.returnsObjectOrNone(forKey: "statusCode") as! Int

              if status == 200 {

                 if let resourceSetsArr = jsonDic.returnsObjectOrNone(forKey: "resourceSets") as? NSArray {

                    if let placesDict = resourceSetsArr.object(at: 0) as? NSDictionary {
                       if let resourceArr = placesDict.object(forKey: "resources") as? NSArray, resourceArr.count > 0 {

                          if let _ = self.placesArray {
                             self.placesArray!.removeAll()
                          }
                          for loopCounter in 0...resourceArr.count - 1 {
                             let modalClass:BingAutoCompletePlace = BingAutoCompletePlace(responseDict: resourceArr[loopCounter] as! NSDictionary)
                             self.placesArray?.append(modalClass)
                          }
                          completion(self.placesArray!)
                       }
                       else { //URL Success, where there no places with the given search string
                          completion([])
                       }
                    }
                 }
              }
           }
        }
        else if let response = response as? HTTPURLResponse , 400...499 ~= response.statusCode {// When url fails
                if let _ = error {
                   print("error=\(error!.localizedDescription)")
                }
                completion([])
             }
             else {
                if let _ = error {
                   print("error=\(error!.localizedDescription)")
                }
                completion([])
             }
         }
     })
     task.resume()
}

//Request cancellation

private func cancelTasksByUrl(tasks: [URLSessionTask]) {
   for task in tasks
   {
      task.cancel()
   }
}
Sid Mhatre
  • 3,272
  • 1
  • 19
  • 38
iOSDev
  • 412
  • 10
  • 30

1 Answers1

4

Unfortunately, the framework does not guarantee any order in which tasks finish -- because this depends on the running time. It could also be that you're in a completion handler of a currently cancelled task.

To circumvent this, you could do the following:

  • Create a private instance variable to store the most-recent task
  • Cancel everything else as before
  • In the completion handler
    • check if the task is still the most recent task (like if (task !== self.currentTask) {return})
    • create a local Array to store the data
    • Update the view controllers array in the main thread (DispatchQueue.main.async(...))

I cleaned up you code a litte (using guard statments to minimize the nesting). Maybe you should also

  • Empty the array in all the error / empty cases (instead of simple return from the guard statement)
  • hand-in the task to the completion call and check there again if the task is still the currentTask. This would also be a good way to reset currentTask to nil.

Just adopt it to your needs :-)

var currentTask:URLSessionDataTask?

func autoSearch(text:String){
    let completion:(_ x:[AnyObject])->() =  {_ in }

    let urlRequest = URLRequest(url: self.getQueryFormedBingURL()!)

    let session = URLSession.shared
    session.getTasksWithCompletionHandler
        {
            (dataTasks, uploadTasks, downloadTasks) -> Void in
            //                self.cancelTasksByUrl(tasks: dataTasks     as [URLSessionTask])
            self.cancelTasksByUrl(tasks: uploadTasks   as [URLSessionTask])
            self.cancelTasksByUrl(tasks: downloadTasks as [URLSessionTask])
    }

    var task:URLSessionDataTask!
    task = session.dataTask(with: urlRequest, completionHandler: { (data, response, error) -> Void in

        print("response \(response)")

        if (task !== self.currentTask) {
            print("Ignore this task")
            return
        }

        if let error = error {
            print("response error \(error)")
        }

        guard let data = data else { return }

        let json = try? JSONSerialization.jsonObject(with: data, options: [])

        var newPlacesArray = [AnyObject]() // Empty array of whichever type you want

        if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {

            guard let jsonDic = json as? NSDictionary else { return }

            let status = jsonDic.returnsObjectOrNone(forKey: "statusCode") as! Int

            if status == 200 {

                guard let resourceSetsArr = jsonDic.returnsObjectOrNone(forKey: "resourceSets") as? NSArray else { return }
                guard let placesDict = resourceSetsArr.object(at: 0) as? NSDictionary else { return }
                guard let resourceArr = placesDict.object(forKey: "resources") as? NSArray, resourceArr.count > 0 else {
                    //URL Success, where there no places with the given search string
                    DispatchQueue.main.async {completion(newPlacesArray)}
                    return
                }

                for loopCounter in 0...resourceArr.count - 1 {
                    let modalClass:BingAutoCompletePlace = BingAutoCompletePlace(responseDict: resourceArr[loopCounter] as! NSDictionary)
                    newPlacesArray.append(modalClass)
                }
                DispatchQueue.main.async {completion(newPlacesArray)}
            }
        }
        else if let response = response as? HTTPURLResponse , 400...499 ~= response.statusCode {// When url fails
            if let _ = error {
                print("error=\(error!.localizedDescription)")
            }
            DispatchQueue.main.async {completion(newPlacesArray)}
        }
        else {
            if let _ = error {
                print("error=\(error!.localizedDescription)")
            }
            DispatchQueue.main.async {completion(newPlacesArray)}
        }
    })

    self.currentTask = task
    task.resume()
}
Andreas Oetjen
  • 9,889
  • 1
  • 24
  • 34