Alamofire request blocking during application lifecycle - ios

I'm having troubles with Alamofire using Operation and OperationQueue.
I have an OperationQueue named NetworkingQueue and I push some operation (wrapping AlamofireRequest) into it, everything works fine, but during application living, at one moment all Alamofire request are not sent. My queue is getting bigger and bigger and no request go to the end.
I do not have a scheme to reproduce it anytime.
Does anybody have a clue for helping me?
Here is a sample of code
The BackgroundAlamoSession
let configuration = URLSessionConfiguration.background(withIdentifier: "[...].background")
self.networkingSessionManager = Alamofire.SessionManager(configuration: configuration)
AbstractOperation.swift
import UIKit
import XCGLogger
class AbstractOperation:Operation {
private let _LOGGER:XCGLogger = XCGLogger.default
enum State:String {
case Ready = "ready"
case Executing = "executing"
case Finished = "finished"
var keyPath: String {
get{
return "is" + self.rawValue.capitalized
}
}
}
override var isAsynchronous:Bool {
get{
return true
}
}
var state = State.Ready {
willSet {
willChangeValue(forKey: self.state.rawValue)
willChangeValue(forKey: self.state.keyPath)
willChangeValue(forKey: newValue.rawValue)
willChangeValue(forKey: newValue.keyPath)
}
didSet {
didChangeValue(forKey: oldValue.rawValue)
didChangeValue(forKey: oldValue.keyPath)
didChangeValue(forKey: self.state.rawValue)
didChangeValue(forKey: self.state.keyPath)
}
}
override var isExecuting: Bool {
return state == .Executing
}
override var isFinished:Bool {
return state == .Finished
}
}
A concrete Operation implementation
import UIKit
import XCGLogger
import SwiftyJSON
class FetchObject: AbstractOperation {
public let _LOGGER:XCGLogger = XCGLogger.default
private let _objectId:Int
private let _force:Bool
public var object:ObjectModel?
init(_ objectId:Int, force:Bool) {
self._objectId = objectId
self._force = force
}
convenience init(_ objectId:Int) {
self.init(objectId, force:false)
}
override var desc:String {
get{
return "FetchObject(\(self._objectId))"
}
}
public override func start(){
self.state = .Executing
_LOGGER.verbose("Fetch object operation start")
if !self._force {
let objectInCache:objectModel? = Application.main.collections.availableObjectModels[self._objectId]
if let objectInCache = objectInCache {
_LOGGER.verbose("object with id \(self._objectId) founded on cache")
self.object = objectInCache
self._LOGGER.verbose("Fetch object operation end : success")
self.state = .Finished
return
}
}
if !self.isCancelled {
let url = "[...]\(self._objectId)"
_LOGGER.verbose("Requesting object with id \(self._objectId) on server")
Application.main.networkingSessionManager.request(url, method : .get)
.validate()
.responseJSON(
completionHandler: { response in
switch response.result {
case .success:
guard let raw:Any = response.result.value else {
self._LOGGER.error("Error while fetching json programm : Empty response")
self._LOGGER.verbose("Fetch object operation end : error")
self.state = .Finished
return
}
let data:JSON = JSON(raw)
self._LOGGER.verbose("Received object from server \(data["bId"])")
self.object = ObjectModel(objectId:data["oId"].intValue,data:data)
Application.main.collections.availableobjectModels[self.object!.objectId] = self.object
self._LOGGER.verbose("Fetch object operation end : success")
self.state = .Finished
break
case .failure(let error):
self._LOGGER.error("Error while fetching json program \(error)")
self._LOGGER.verbose("Fetch object operation end : error")
self.state = .Finished
break
}
})
} else {
self._LOGGER.verbose("Fetch object operation end : cancel")
self.state = .Finished
}
}
}
The NetworkQueue
class MyQueue {
public static let networkQueue:SaootiQueue = SaootiQueue(name:"NetworkQueue", concurent:true)
}
How I use it in another operation and wait for for result
let getObjectOperation:FetchObject = FetchObject(30)
SaootiQueue.networkQueue.addOperations([getObjectOperation], waitUntilFinished: true)
How I use it the main operation using KVO
let getObjectOperation:FetchObject = FetchObject(30)
operation.addObserver(self, forKeyPath: #keyPath(Operation.isFinished), options: [.new], context: nil)
operation.addObserver(self, forKeyPath: #keyPath(Operation.isCancelled), options: [.new], context: nil)
queue.addOperation(operation)
//[...]
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let operation = object as? FetchObject {
operation.removeObserver(self, forKeyPath: #keyPath(Operation.isFinished))
operation.removeObserver(self, forKeyPath: #keyPath(Operation.isCancelled))
if keyPath == #keyPath(Operation.isFinished) {
//Do something
}
}
A few clarifications:
My application is a radio player and I need, while playing music and the background, to fetch the currently playing program. This is why I need background Session.
In fact I also use the background session for all the networking I do when the app is foreground. Should I avoid that ?
The wait I'm using is from another queue and is never used in the main queue (I know it is a threading antipattern and I take care of it).
In fact it is used when I do two networking operation and the second one depends of the result of the second. I put a wait after the first operation to avoid KVO observing. Should I avoid that ?
Additional edit:
When I say "My queue is getting bigger and bigger and no request go to the end", it means that at one moment during application livecycle, random for the moment (I can not find a way to reproduce it at every time), Alamofire request don't reach the response method.
Because of that the Operation wrapper don't end and the queue is growing.
By the way I'm working on converting Alamofire request into URLRequest for having clues and I founded some problem on using the main queue. I have to sort what is due to the fact that Alamofire use the main queue for reponse method and I'll see if I find a potential deadlock
I'll keep you informed. Thanks

There are minor issues, but this operation implementation looks largely correct. Sure, you should make your state management thread-safe, and there are other stylistic improvements you could make, but I don't think this is critical to your question.
What looks worrisome is addOperations(_:waitUntilFinished:). From which queue are you waiting? If you do that from the main queue, you will deadlock (i.e. it will look like the Alamofire requests never finish). Alamofire uses the main queue for its completion handlers (unless you override the queue parameter of responseJSON), but if you're waiting on the main thread, this can never take place. (As an aside, if you can refactor so you never explicitly "wait" for operations, that not only avoids the deadlock risk, but is a better pattern in general.)
I also notice that you're using Alamofire requests wrapped in operations in conjunction with a background session. Background sessions are antithetical to operations and completion handler closure patterns. Background sessions continue after your app has been jettisoned and you have to rely solely upon the SessionDelegate closures that you set when you first configure your SessionManager when the app starts. When the app restarts, your operations and completion handler closures are long gone.
Bottom line, do you really need background session (i.e. uploads and downloads that continue after your app terminates)? If so, you may want to lose this completion handler and operation based approach. If you don't need this to continue after the app terminates, don't use background sessions. Configuring Alamofire to properly handle background sessions is a non-trivial exercise, so only do so if you absolutely need to. Remember to not conflate background sessions and the simple asynchronous processing that Alamofire (and URLSession) do automatically for you.
You asked:
My application is a radio player and I need, while playing music and the background, to fetch the currently playing program. This is why I need background Session.
You need background sessions if you want downloads to proceed while the app is not running. If your app is running in the background, though, playing music, you probably don't need background sessions. But, if the user chooses to download a particular media asset, you may well want background session so that the download proceeds when the user leaves the app, whether the app is playing music or not.
In fact I also use the background session for all the networking I do when the app is foreground. Should I avoid that ?
It's fine. It's a little slower, IIRC, but it's fine.
The problem isn't that you're using background session, but that you're doing it wrong. The operation-based wrapping of Alamofire doesn't make sense with a background session. For sessions to proceed in the background, you are constrained as to how you use URLSession, namely:
You cannot use data tasks while the app is not running; only upload and download tasks.
You cannot rely upon completion handler closures (because the entire purpose of background sessions is to keep them running when your app terminates and then fire up your app again when they're done; but if the app was terminated, your closures are all gone).
You have to use delegate based API only for background sessions, not completion handlers.
You have to implement the app delegate method to capture the system provided completion handler that you call when you're done processing background session delegate calls. You have to call that when your URLSession tells you that it's done processing all the background delegate methods.
All of this is a significant burden, IMHO. Given that the system is keeping you app alive for background music, you might contemplate using a standard URLSessionConfiguration. If you're going to use background session, you might need to refactor all of this completion handler-based code.
The wait I'm using is from another queue and is never used in the main queue (I know it is a threading antipattern and I take care of it).
Good. There's still serious code smell from ever using "wait", but if you are 100% confident that it's not deadlocking here, you can get away with it. But it's something you really should check (e.g. put some logging statement after the "wait" and make sure you're getting past that line, if you haven't already confirmed this).
In fact it is used when I do two networking operation and the second one depends of the result of the second. I put a wait after the first operation to avoid KVO observing. Should I avoid that ?
Personally, I'd lose that KVO observing and just establish addDependency between the operations. Also, if you get rid of that KVO observing, you can get rid of your double KVO notification process. But I don't think this KVO stuff is the root of the problem, so maybe you defer that.

Related

Make multiple asynchronous requests but wait for only one

I have a question concerning asynchronous requests. I want to request data from different sources on the web. Each source might have the data I want but I do not know that beforehand. Because I only want that information once, I don't care about the other sources as soon as one source has given me the data I need. How would I go about doing that?
I thought about doing it with a didSet and only setting it once, something like this:
var dogPicture : DogPicture? = nil {
didSet {
// Do something with the picture
}
}
func findPictureOfDog(_ sources) -> DogPicture? {
for source in sources {
let task = URL.Session.shared.dataTask(with: source) { (data, response, error) in
// error handling ...
if data.isWhatIWanted() && dogPicture == nil {
dogPicture = data.getPicture()
}
}
task.resume()
}
}
sources = ["yahoo.com", "google.com", "pinterest.com"]
findPictureOfDog(sources)
However it would be very helpful, if I could just wait until findPictureOfDog() is finished, because depending on if I find something or not, I have to ask the user for more input.
I don't know how I could do it in the above way, because if I don't find anything the didSet will never be called, but I should ask the user for a picture then.
A plus: isWhatIWanted() is rather expensive, so If there was a way to abort the execution of the handler once I found a DogPicture would be great.
I hope I made myself clear and hope someone can help me out with this!
Best regards and thank you for your time
A couple of things:
First, we’re dealing with asynchronous processes, so you shouldn’t return the DogPicture, but rather use completion handler pattern. E.g. rather than:
func findPictureOfDog(_ sources: [String]) -> DogPicture? {
...
return dogPicture
}
You instead would probably do something like:
func findPictureOfDog(_ sources: [String], completion: #escaping (Result<DogPicture, Error>) -> Void) {
...
completion(.success(dogPicture))
}
And you’d call it like:
findPictureOfDog(sources: [String]) { result in
switch result {
case .success(let dogPicture): ...
case .failure(let error): ...
}
}
// but don’t try to access the DogPicture or Error here
While the above was addressing the “you can’t just return value from asynchronous process”, the related observations is that you don’t want to rely on a property as the trigger to signal when the process is done. All of the “when first process finishes” logic should be in the findPictureOfDog routine, and call the completion handler when it’s done.
I would advise against using properties and their observers for this process, because it begs questions about how one synchronizes access to ensure thread-safety, etc. Completion handlers are unambiguous and avoid these secondary issues.
You mention that isWhatIWanted is computationally expensive. That has two implications:
If it is computationally expensive, then you likely don’t want to call that synchronously inside the dataTask(with:completionHandler:) completion handler, because that is a serial queue. Whenever dealing with serial queues (whether main queue, network session serial queue, or any custom serial queue), you often want to get in and out as quickly as possible (so the queue is free to continue processing other tasks).
E.g. Let’s imagine that the Google request came in first, but, unbeknownst to you at this point, it doesn’t contain what you wanted, and the isWhatIWanted is now slowly checking the result. And let’s imagine that in this intervening time, the Yahoo request that came in. If you call isWhatIWanted synchronously, the result of the Yahoo request won’t be able to start checking its result until the Google request has failed because you’re doing synchronous calls on this serial queue.
I would suggest that you probably want to start checking results as they came in, not waiting for the others. To do this, you want a rendition of isWhatIWanted the runs asynchronously with respect to the network serial queue.
Is the isWhatIWanted a cancelable process? Ideally it would be, so if the Yahoo image succeeded, it could cancel the now-unnecessary Pinterest isWhatIWanted. Canceling the network requests is easy enough, but more than likely, what we really want to cancel is this expensive isWhatIWanted process. But we can’t comment on that without seeing what you’re doing there.
But, let’s imagine that you’re performing the object classification via VNCoreMLRequest objects. You might therefore cancel any pending requests as soon as you find your first match.
In your example, you list three sources. How many sources might there be? When dealing with problems like this, you often want to constrain the degree of concurrency. E.g. let’s say that in the production environment, you’d be querying a hundred different sources, you’d probably want to ensure that no more than, say, a half dozen running at any given time, because of the memory and CPU constraints.
All of this having been said, all of these considerations (asynchronous, cancelable, constrained concurrency) seem to be begging for an Operation based solution.
So, in answer to your main question, the idea would be to write a routine that iterates through the sources, and calling the main completion handler upon the first success and make sure you prevent any subsequent/concurrent requests from calling the completion handler, too:
You could save a local reference to the completion handler.
As soon as you successfully find a suitable image, you can:
call that saved completion handler;
nil your saved reference (so in case you have other requests that have completed at roughly the same time, that they can’t call the completion handler again, eliminating any race conditions); and
cancel any pending operations so that any requests that have not finished will stop (or have not even started yet, prevent them from starting at all).
Note, you’ll want to synchronize the the above logic, so you don’t have any races in this process of calling and resetting the completion handler.
Make sure to have a completion handler that you call after all the requests are done processing, in case you didn’t end up finding any dogs at all.
Thus, that might look like:
func findPictureOfDog(_ sources: [String], completion: #escaping DogPictureCompletion) {
var firstCompletion: DogPictureCompletion? = completion
let synchronizationQueue: DispatchQueue = .main // note, we could have used any *serial* queue for this, but main queue is convenient
let completionOperation = BlockOperation {
synchronizationQueue.async {
// if firstCompletion not nil by the time we get here, that means none of them matched
firstCompletion?(.failure(DogPictureError.noneFound))
}
print("done")
}
for source in sources {
let url = URL(string: source)!
let operation = DogPictureOperation(url: url) { result in
if case .success(_) = result {
synchronizationQueue.async {
firstCompletion?(result)
firstCompletion = nil
Queues.shared.cancelAllOperations()
}
}
}
completionOperation.addDependency(operation)
Queues.shared.processingQueue.addOperation(operation)
}
OperationQueue.main.addOperation(completionOperation)
}
So what might that DogPictureOperation might look like? I might create an asynchronous custom Operation subclass (I just subclass a general purpose AsynchronousOperation subclass, like the one here) that will initiate network request and then run an inference on the resulting image upon completion. And if canceled, it would cancel the network request and/or any pending inferences (pursuant to point 3, above).
If you care about only one task use a completion handler, call completion(nil) if no picture was found.
var dogPicture : DogPicture?
func findPictureOfDog(_ sources, completion: #escaping (DogPicture?) -> Void) {
for source in sources {
let task = URL.Session.shared.dataTask(with: source) { (data, response, error) in
// error handling ...
if data.isWhatIWanted() && dogPicture == nil {
let picture = data.getPicture()
completion(picture)
}
}
task.resume()
}
}
sources = ["yahoo.com", "google.com", "pinterest.com"]
findPictureOfDog(sources) { [weak self] picture in
if let picture = picture {
self?.dogPicture = picture
print("picture set")
} else {
print("No picture found")
}
}
You can use DispatchGroup to run a check when all of your requests have returned:
func findPictureOfDog(_ sources: [String]) -> DogPicture? {
let group = DispatchGroup()
for source in sources {
group.enter()
let task = URLSession.shared.dataTask(with: source) { (data, response, error) in
// error handling ...
if data.isWhatIWanted() && dogPicture == nil {
dogPicture = data.getPicture()
}
group.leave()
}
task.resume()
}
group.notify(DispatchQueue.main) {
if dogPicture == nil {
// all requests came back but none had a result.
}
}
}

How to pause operation queue in Swift

I have a couple of operations to perform on the IoT device from iOS App. So All my operations are in OperationsQueue with serial operations.
Here I want to perform one operation at a time and each operation needs to wait until I get a response from the IoT device.
Here IoT device response will take time to send back. so how to wait for the current operation in operation queue until I get a response from IoT.
So is there any way to pause current running operation until getting a response from IoT and then I will resume it so that the next operation in operation queue will start.
I tried with Sleep operation But it required time, but we can not guarantee about IoT device response.
Any suggestions would appreciate it. Thank you in advance.
The basic idea is that you don’t pause (or wait, or sleep), but rather you define a “concurrent” operation (see discussion of concurrent operations in the documentation) that doesn’t trigger the isFinished KVO until the device responds.
A simple way to do this is to write a concurrent operation class, like the one shown in this answer. Then your IoT operation can subclass that AsynchronousOperation class, and just call finish() when the device responds.
Then your operation queue (which presumably has a maxConcurrentOperationCount of 1, or perhaps is using dependencies), will not start an operation until the prior operation has finished.
As Rob said, you can implement Asynchronous Operation class and subclass from it your IoT operation. For me it looks like the most preferred way to implement yr case.
As an alternative, in cases where you need to continue the process only after some asynchronous event in another thread completed, you can use NSCondition. This is a mechanism from obj-c that provide an easy way to wait for a condition to occur.
Here is example:
let cond = NSCondition()
var available = false
var sharedString = ""
class WriterThread: Thread {
override func main() {
for _ in 0..<5 {
cond.lock()
sharedString = "😅"
available = true
cond.signal() // Notify and wake up the waiting thread/s
cond.unlock()
}
}
}
class PrinterThread: Thread {
override func main(){
for _ in 0..<5 { //Just do it 5 times
cond.lock()
while(!available) { //Protect from spurious signals
cond.wait()
}
print(SharedString)
sharedString = ""
available = false
cond.unlock()
}
}
}
let writet = WriterThread()
let printt = PrinterThread()
printt.start()
writet.start()
You could use a DispatchQueue and call .suspend() when you send the operation, and have the code that gets the response call .resume(). Then wherever you want to wait for the response before continuing, just put a dummy queue.sync({ print("done waiting")}) and it will automatically wait until .resume() has been called before printing and continuing.
import Dispatch
var queue = DispatchQueue(label: "queue")
func sendOperationToIoTDevice(){
//send the operation
//...
queue.suspend()
}
...
//whatever code gets the response:
//get response
//...
queue.resume()
...
//main code
sendOperationToIoTDevice()
queue.sync { print("done waiting") } // will hang here until .resume() is called

NSAsynchronousFetchRequest - should update be explicitly done on main thread

I am creating an NSAsynchronousFetchRequest which has a completion block inside it.
I have seen various examples where some include using dispatch queue on the main thread and others don't. For example the Ray Wenderlich core data book doesn't call the result on the main thread.
Should I go back on the main thread when executing the result. Initially I thought I had to but now I don't. Some definitive clarity would be great.
fun exampleFetch(_ completionHandler: #escaping () -> () {
let fetchRequest = NSFetchRequest<NSDictionary>(entityName: "Example")
let asyncFetchRequest = NSAsynchronousFetchRequest<NSDictionary>(fetchRequest: fetchRequest) { result in
// DispatchQueue.main.async { // is this needed
completion()
//}
}
managedContext.performChanges {
do {
try self.managedContext.execute(asyncFetchRequest)
} catch let error {
print("error trying to fetch saving objects:", error.localizedDescription)
}
}
}
You should not explicitly call the completion handler on the main queue. Let the caller decide how to handle it. If anything, document that the completion handler will be called on an arbitrary queue. Then the client calling your exampleFetch method knows that it is their responsibility to be sure that process the result on whatever queue it needs.
This gives the client more control.
This also prevents a lot of needless thread switching. A client may call exampleFetch from a background queue and it may want to process the results in the background. If you explicitly put the completion on the main queue, the client then needs to explicitly switch back to a background queue to process the result. That's two needless queue switches and it's wasted effort on the main queue.

API calls blocks UI thread Swift

I need to sync web database in my coredata, for which I perform service api calls. I am using Alamofire with Swift 3. There are 23 api calls, giving nearly 24k rows in different coredata entities.
My problem: These api calls blocks UI for a minute, which is a long time for a user to wait.
I tried using DispatchQueue and performing the task in background thread, though nothing worked. This is how I tried :
let dataQueue = DispatchQueue.init(label: "com.app.dataSyncQueue")
dataQueue.async {
DataSyncController().performStateSyncAPICall()
DataSyncController().performRegionSyncAPICall()
DataSyncController().performStateRegionSyncAPICall()
DataSyncController().performBuildingRegionSyncAPICall()
PriceSyncController().performBasicPriceSyncAPICall()
PriceSyncController().performHeightCostSyncAPICall()
// Apis which will be used in later screens are called in background
self.performSelector(inBackground: #selector(self.performBackgroundTask), with: nil)
}
An API call from DataSyncController:
func performStateSyncAPICall() -> Void {
DataSyncRequestManager.fetchStatesDataWithCompletionBlock {
success, response, error in
self.apiManager.didStatesApiComplete = true
}
}
DataSyncRequestManager Code:
static func fetchStatesDataWithCompletionBlock(block:#escaping requestCompletionBlock) {
if appDelegate.isNetworkAvailable {
Util.setAPIStatus(key: kStateApiStatus, with: kInProgress)
DataSyncingInterface().performStateSyncingWith(request:DataSyncRequest().createStateSyncingRequest() , withCompletionBlock: block)
} else {
//TODO: show network failure error
}
}
DataSyncingInterface Code:
func performStateSyncingWith(request:Request, withCompletionBlock block:#escaping requestCompletionBlock)
{
self.interfaceBlock = block
let apiurl = NetworkHttpClient.getBaseUrl() + request.urlPath!
Alamofire.request(apiurl, parameters: request.getParams(), encoding: URLEncoding.default).responseJSON { response in
guard response.result.isSuccess else {
block(false, "error", nil )
return
}
guard let responseValue = response.result.value else {
block (false, "error", nil)
return
}
block(true, responseValue, nil)
}
}
I know many similar questions have been already posted on Stackoverflow and mostly it is suggested to use GCD or Operation Queue, though trying DispatchQueues didn't work for me.
Am I doing something wrong?
How can I not block UI and perform the api calls simultaneously?
You can do this to run on a background thread:
DispatchQueue.global(qos: .background).async {
// Do any processing you want.
DispatchQueue.main.async {
// Go back to the main thread to update the UI.
}
}
DispatchQueue manages the execution of work items. Each work item submitted to a queue is processed on a pool of threads managed by the system.
I usually use NSOperationQueue with Alamofire, but the concepts are similar. When you set up an async queue, you allow work to be performed independently of the main (UI) thread, so that your app doesn't freeze (refuse user input). The work will still take however long it takes, but your program doesn't block while waiting to finish.
You really have only put one item into the queue.
You are adding to the queue only once, so all those "perform" calls wait for the previous one to finish. If it is safe to run them concurrently, you need to add each of them to the queue separately. There's more than one way to do this, but the bottom line is each time you call .async {} you are adding one item to the queue.
dataQueue.async {
DataSyncController().performStateSyncAPICall()
}
dataQueue.async {
DataSyncController(). performRegionSyncAPICall l()
}

Swift 3 multithreading using which queue?

I'm going through Stanford CP 193P, looking at a Twitter client.
When a network is called, I assumed it would always be called on the main queue unless invoked on another queue. However without dispatch back onto the main queue (as below) the App does not work as expected - meaning we must not be on the main queue. How?
When tweets are fetched the following closure is used - and to update the UI means that the work needs to be done on the main thread (DispatchQueue.main.async)
request.fetchTweets { [weak self] (newTweets) in
DispatchQueue.main.async {
if request == self?.lastTwitterRequest {
self?.tweets.insert(newTweets, at: 0)
self?.tableView.insertSections([0], with: .fade)
}
}
}
This calls a convenience function that is commented as "handler is not necessarily invoked on the main queue". I can't find anywhere that declares which queue it is invoked on, so I assume it is on the main queue?
// convenience "fetch" for when self is a request that returns Tweet(s)
// handler is not necessarily invoked on the main queue
open func fetchTweets(_ handler: #escaping ([Tweet]) -> Void) {
fetch { results in
var tweets = [Tweet]()
var tweetArray: NSArray?
if let dictionary = results as? NSDictionary {
if let tweets = dictionary[TwitterKey.Tweets] as? NSArray {
tweetArray = tweets
} else if let tweet = Tweet(data: dictionary) {
tweets = [tweet]
}
} else if let array = results as? NSArray {
tweetArray = array
}
if tweetArray != nil {
for tweetData in tweetArray! {
if let tweet = Tweet(data: tweetData as? NSDictionary) {
tweets.append(tweet)
}
}
}
handler(tweets)
}
}
I did not write the Twitter framework, and it appears to have been authored by the Stanford instructor.
You ask:
This calls a convenience function that is commented as "handler is not necessarily invoked on the main queue". I can't find anywhere that declares which queue it is invoked on, so I assume it is on the main queue?
No, you cannot assume it is on the main queue. In fact, it sounds like it's explicitly warning you that it isn't. The only time you can be assured it's on the main queue, is if it explicitly says so.
For example, if the underlying framework is using URLSession, it, by default, does not use the main queue for its completion handlers. The init(configuration:​delegate:​delegate​Queue:​) documentation warns us that the queue parameter is as follows:
An operation queue for scheduling the delegate calls and completion handlers. The queue should be a serial queue, in order to ensure the correct ordering of callbacks. If nil, the session creates a serial operation queue for performing all delegate method calls and completion handler calls.
And for a given framework, it may be completely unrelated to URLSession queue behavior. It might also be using its own queues for completion handlers.
Bottom line, if the framework doesn't explicitly assure you that the closure always runs on the main queue, you should never assume it does. So, yes, in the absence of any assurances to this effect, you'd want to dispatch any UI stuff to the main queue and do the appropriate synchronization for any model objects.
You can, if you have code that must run on a particular thread and you want to make sure this is the case, you can add a dispatchPrecondition to test if it's on the main thread. The behavior of this changes between debug builds and release builds, but it's a quick way of quickly testing if it's using the queue you think it is:
dispatchPrecondition(condition: .onQueue(.main))

Resources