Andrea Mariotti
Andrea Mariotti

Reputation: 71

proper use of Alamofire queue

Here is the scenario, everything works but I get hanged up on the main queue. I have:

  1. singleton class to manage API connection. Everything works (execution time aside....)
  2. a number of view controllers calling GET API via the above singleton class to get the data
  3. I normally call the above from either viewDidLoad or viewWillAppear
  4. they all work BUT ....
  5. if I call a couple of API methods implemented with Alamofire.request() with a closure (well, I need to know when it is time to reload!), one of the two gets hung waiting for the default (main) queue to give it a thread and it can take up to 20 seconds
  6. if I call only one, do my thing and then call a POST API, this latter one ends up in the same situation as (5), it takes a long time to grab a slot in the default queue.

I am not specifying a queue in Alamofiore.request() and it sounds to me like I should so I tried it. I added a custom concurrent queue to my singleton API class and I tried adding that to my Alamofiore.request() .....and that did absolutely nothing. Help please, I must be missing something obvious?!

Here is my singleton API manager (excerpt) class:

class APIManager {
// bunch of stuff here

static let sharedInstance = APIController()
// more stuff here

let queue = DispatchQueue(label: "com.teammate.response-queue", qos: .utility, attributes: [.concurrent])
// more stuff

func loadSports(completion: @escaping (Error?) -> Void) {

        let parameters: [String: Any?] = [:]
        let headers = getAuthenticationHeaders()
        let url = api_server+api_sport_list
        Alamofire.request(url, method: .get, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseString (queue: queue) { response in
            if let json = response.result.value {
                if let r = JSONDeserializer<Response<[Sport]>>.deserializeFrom(json: json) {
                    if r.status == 200 {
                        switch r.content{
                        case let content as [Sport]:
                            self.sports = content
                            NSLog("sports loaded")
                            completion(nil)
                        default:
                            NSLog("could not read the sport list payload!")
                            completion(GenericError.reportError("could not read sports payload"))
                        }
                    }
                    else {
                        NSLog("sports not obtained, error %d %@",r.status, r.error)
                        completion(GenericError.reportError(r.error))
                    }
                }
            }
        }
    }

// more stuff
}

And this is how I call the methods from APIManager once I get the sigleton:

api.loadSports(){ error in
  if error != nil {
    // something bad happened, more code here to handle the error
  }
  else {
      self.someViewThingy.reloadData()
  }
}

Again, it all works it is just that if I make multiple Alamofire calls from the same UIViewController, the first is fast, every other call sits for ever to get a spot in the queue an run.

Upvotes: 2

Views: 2939

Answers (1)

Rob
Rob

Reputation: 437532

UI updates must happen on the main queue, so by moving this stuff to a concurrent queue is only going to introduce problems. In fact, if you change the completion handler queue to your concurrent queue and neglect to dispatch UI updates back to the main queue, it's going to just make it look much slower than it really is.

I actually suspect you misunderstand the purpose of the queue parameter of responseString. It isn't how the requests are processed (they already happen concurrently with respect to the main queue), but merely on which queue the completion handler will be run.

So, a couple of thoughts:

  1. If you're going to use your own queue, make sure to dispatch UI updates to the main queue.

  2. If you're going to use your own queue and you're going to update your model, make sure to synchronize those updates with any interaction you might be doing on the main queue. Either create a synchronization queue for that or, easier, dispatch all model updates back to the main queue.

  3. I see nothing here that justifies the overhead and hassle of running the completion handler on anything other than the main queue. If you don't supply a queue to responseString, it will use the main queue for the completion handlers (but won't block anything, either), and it solves the prior two issues.

Upvotes: 2

Related Questions