Take a look at this WWDC 2018 video:
https://developer.apple.com/videos/play/wwdc2018/714/?time=1897
At about 31:30, we see a slide and hear a narration that says we can call a URLSession's task's resume on a background queue, and that the quality of service ("QoS") of this queue will be used to prioritize the request.
Cool! I didn't know that. So I can say something like this:
DispatchQueue.global(qos: .background).async {
task.resume()
}
However, the next sentence says that "all the messages that it sends to your delegates will respect this QoS."
Really? In what sense? I have tried to examine the threading of the delegate callbacks, and they are not being automatically called back on the same global queue. And I don't know how else to characterize or detect the QoS of the current queue/thread. Moreover, if you supply a delegate, you must specify a callback queue in any case. So what does that sentence mean, exactly?
Related
In iOS, we have GCD and Operation to handle concurrent programming.
looking into GCD we have QoS classes, and they're simple and straight forward, this question is about why DispatchQueue.main.async is commonly used to asynchronies X tasks in the Main Thread.
So when we usually handle updating something in the UI we usually use that function since to prevent any irresponsiveness from the application.
makes me think is writing code inside the UIViewController usually executed in the main thread ?
but also knowing that callback & completionHandler usually execute without specifying on what thread they are in, and the UI never had a problem with that !! so it is on the background ?
How Swift handles this ? and what thread am i writing on by default without specifying anything ?
Since there are more than one question here, let's attempt to answer them one by one.
why DispatchQueue.main.async is commonly used to asynchronies X tasks
in the Main Thread.
Before mentioning a direct answer, make sure that you don't have confusion of understanding:
Serial <===> Concurrent.
Sync <===> Async.
Keep in mind that DispatchQueue.main is serial queue. Using sync or async has nothing to do with determining serialization or currency of a queue, instead they refer to how the task is handled. Thus saying DispatchQueue.main.async means that:
returns control to the current queue right after task has been sent to
be performed on the different queue. It doesn't wait until the task is
finished. It doesn't block the queue.
cited from: https://stackoverflow.com/a/44324968/5501940 (I'd recommend to check it.)
In other words, async means: this will happen on the main thead and update it when it is finished. That's what makes what you said:
So when we usually handle updating something in the UI we usually use
that function since to prevent any irresponsiveness from the
application.
seems to be sensible; Using sync -instead of async- will block the main.
makes me think is writing code inside the UIViewController usually
executed in the main thread ?
First of all: By default, without specifying which thread should execute a chunk of code it would be the main thread. However your question seems to be unspecific because inside a UIViewController we can call functionalities that are not executed on the main thread by specifying it.
but also knowing that callback & completionHandler usually execute
without specifying on what thread they are in, and the UI never had a
problem with that !! so it is on the background ?
"knowing that callback & completionHandler usually execute without specifying on what thread they are in" No! You have to specify it. A good real example for it, actually that's how Main Thread Checker works.
I believe that there is something you are missing here, when dealing when a built-in method from the UIKit -for instance- that returns a completion handler, we can't see that it contains something like DispatchQueue.main.async when calling the completion handler; So, if you didn't execute the code inside its completion handler inside DispatchQueue.main.async so we should assume that it handles it for you! It doesn't mean that it is not implemented somewhere.
Another real-world example, Alamofire! When calling
Alamofire.request("https://httpbin.org/get").responseJSON { response in
// what is going on here work has to be async on the main thread
}
That's why you can call it without facing any "hanging" issue on the main thread; It doesn't mean its not handled, instead it means they handle it for you so you don't have to worry about it.
I’ve been learning Rxswift and applying it on a project since start. I would like your help to fell more assured about a concept.
I understand the changes in the UI should be performed on the Mainscheduler, and you should explicitly use .observeOn(MainSchedule… in case you don’t use Drivers.
My doubt is: normally, should I explicitly switch to a background thread when performing network requests?.
I haven’t found many literature talking about exactly this, but I’ve read some projects code and most of them don’t, but a few do. Those eventually use Drivers or .observeOn(MainSchedule… to make the changes on the UI.
In https://www.thedroidsonroids.com/blog/rxswift-examples-4-multithreading, for instance, he says
So as you may guessed, in fact everything we did was done on a MainScheduler. Why? Because our chain starts from searchBar.rx_text and this one is guaranteed to be on MainScheduler. And because everything else is by default on current scheduler – well our UI thread may get overwhelmed. How to prevent that? Switch to the background thread before the request and before the mapping, so we will just update UI on the main thread
So what he does to solve the problem he mentions, is to explicitly declare
.observeOn(ConcurrentDispatchQueueScheduler(globalConcurrentQueueQOS: .Background))
Assuming the API Request would be performed on background anyway, what this does is to perform all other computations in the background as well, right?
Is this a good practice? Should I, in every API request, explicitly change to background and then changes back to Main only when necessary?
If so, what would be best way? To observe on background and then on Main? Or to subscribe on background and observe on Main, as is done in this gist:
https://gist.github.com/darrensapalo/711d33b3e7f59b712ea3b6d5406952a4
?
Or maybe another way?
P.S.: sorry for the old code, but among the links I found, these better fit my question.
Normally, i.e. if you do not specify any schedulers, Rx is synchronous.
The rest really depends on your use case. Four instance, all UI manipulations must happen on main thread scheduler.
Background work, including network requests, should run on background schedulers. Which ones - depends on priority and preference for concurrent/serial execution.
.subscribeOn() defines where the work is being done and .observeOn() defines where the results of it are handled.
So the answer to your specific questions: in case of a network call which results will be reflected in UI, you must subscribe on background scheduler and observe on main.
You can declare schedulers like that (just an example):
static let main = MainScheduler.instance
static let concurrentMain = ConcurrentMainScheduler.instance
static let serialBackground = SerialDispatchQueueScheduler.init(qos: .background)
static let concurrentBackground = ConcurrentDispatchQueueScheduler.init(qos: .background)
static let serialUtility = SerialDispatchQueueScheduler.init(qos: .utility)
static let concurrentUtility = ConcurrentDispatchQueueScheduler.init(qos: .utility)
static let serialUser = SerialDispatchQueueScheduler.init(qos: .userInitiated)
static let concurrentUser = ConcurrentDispatchQueueScheduler.init(qos: .userInitiated)
static let serialInteractive = SerialDispatchQueueScheduler.init(qos: .userInteractive)
static let concurrentInteractive = ConcurrentDispatchQueueScheduler.init(qos: .userInteractive)
P.S. Some 3rd-party libraries may provide observables that are pre-configured to execute on a background scheduler. In that case explicitly calling .subscribeOn() is not necessary. But you need to know for sure whether this is the case.
And a recap:
normally, should I explicitly switch to a background thread when performing network requests?. - yes, unless a library does it for you
Should I, in every API request, explicitly change to background and then changes back to Main only when necessary? - yes
If so, what would be best way? [...] subscribe on background and observe on Main
You are right. Of course the actual network request, and waiting for and assembling the response, is all done on a background thread. What happens after that depends on the network layer you are using.
For example, if you are using URLSession, the response already comes back on a background thread so calling observeOn to do anything other than come back to the main thread is unnecessary and a reduction of performance. In other words, in answer to your question you don't need to change to a background thread on every request because it's done for you.
I see in the article that the author was talking in the context of Alamofire which explicitly responds on the main thread. So if you are using Alamofire, or some other networking layer that responds on the main thread, you should consider switching to a background thread if the processing of the response is expensive. If all you are doing is creating an object from the resulting dictionary and pushing it to a view the switch in context is probably overkill and could actually degrade performance considering you have already had to suffer through a context switch once.
I feel it's also important to note that calling subscribeOn is absolutely pointless for either network layer. That will only change the thread that the request is made on, not the background thread that waits for the response, nor the thread that the response returns on. The networking layer will decide what thread it uses to push the data out and subscribeOn can't change it. The best you can do is use observeOn to reroute the data flow to a different thread after the response. The subscribeOn operator is for synchronous operations, not network requests.
As it stands, NSNotifications allow for a target-action mechanism in response to one post / event.
I would like to have a notification which triggers an action (runs a function) only after two events have been triggered.
The scenario is that I have two asynchronous processes which need to complete before I can call the function. Perhaps I'm missing something, but I haven't found a way to do this. Or maybe I'm not thinking of an obvious reason why this would be a really bad idea?
Also, some of my terminology may be off, so please feel free to edit and fix it.
There are many possibilities on how you can implement this. They all center around keeping track of which processes are finished. The best way depends on how your background processes are implemented.
If you are using NSOperationQueue you could add a third operation that has the other two operations as a dependency. That way you won't have to take care of notifications at all.
Otherwise you can can count how many operations have finished and execute your code when the counter reaches the right value. GCD has dispatch groups as a nice abstraction for this.
First you create a dispatch group:
let group = dispatch_group_create()
Then you enter the group for each background process:
dispatch_group_enter(group)
Finally you can register an block that gets called when the group becomes empty, that is when each dispatch_group_enter is balanced by an dispatch_group_leave:
dispatch_group_notify(group, dispatch_get_main_queue()) {
// All processes are done.
}
After each of your processes finish you leave the group again:
dispatch_group_leave(group)
It's important to call dispatch_group_enter before calling dispatch_group_notify or your block will be scheduled immediately as the group is already empty.
After your notify block was executed you can reuse the queue or discard it.
I am registering to receive updates from a CMMotionManager like so:
motionManager.startDeviceMotionUpdatesToQueue(deviceMotionQueue) {
[unowned self] (deviceMotion, error) -> Void in
// ... handle data ...
}
where deviceMotionQueue is an NSOperationQueue with the highest quality of service, i.e. the highest possible update rate:
self.deviceMotionQueue.qualityOfService = NSQualityOfService.UserInteractive
This means that I am getting updates often. Like really often. So I was wondering: what happens if I don't handle one update fast enough? If the update interval is shorter than the execution time of 'handle data'? Will the motion manager drop some information? Or will it queue up and after a while become run out of memory? Or is this not feasable at all?
It's hard to know what the internal CoreMotion implementation will do, and given that what it does is an "implementation detail", even if you could discern its current behavior, you wouldn't want to rely on that behavior moving forward.
I think the common solution to this is to do the minimum amount of work in the motion update handler, and then manage the work/rate-limiting/etc yourself. So, for instance, if you wanted to drop interstitial updates that arrived while you were processing the last update, you could have the update handler that you pass into CoreMotion do nothing but (safely) add a copy of deviceMotion to a mutable array, and then enqueue the "real" handler on a different queue. The real handler might then have a decision tree like:
if the array is empty, return immediately
otherwise (safely) take the last element, clear all elements from the array, and do the work based on the last element
This would have the effect of letting you take only the most recent reading, but also to have knowledge of how many updates were missed, and, if it's useful, what those missed updates were. Depending on your app, it might be useful to batch process the missed events as a group.
But the takeaway is this: if you want to be sure about how a system like this behaves, you have to manage it yourself.
2 part question but related so will keep in the same thread:
I'm adding NSOperations to a NSOperationQueue. I need to know when the operation will start and when it ends in order to update the UI.
I thought about adding a "start handler" block to run in the nsoperation as well as a "completion handler" in the NSOperation
Something like
-(id)initOperationWithStartBlock:(StartBlock)startblock completionBlock:(CompletionBlock)completionBlock
but believe that there is a better way to get this from the queue itself.
How can this be done?
I would also like to know the index of the job sent by the NSOperationQueue.
I've tried doing
[[self.myQueue operations] indexForObject:operation]
but the index is always the zeroth index - because the completed jobs were removed from the nsoperationqueue array before I could check the jobs index.
Any way to preserve them?
You need to use Key-Value-Observing pattern in IOS. So for this you need to setup observers in your controller to look for changes to isFinished and isExecuting to catch start and finish hooks.
It depends if you want to perform something from within your object upon starting or elsewhere in your code. From what you are saying (you want to update the UI), this sounds like you want to act outside of your object, but I don't know your program. You have two options:
1) If you want to act in your object upon starting the operation from within the same object, use key-value observation and observe isExecuting with self as the observer and the observed. Don't forget that you will get called whether it goes from NO to YES (starting) or YES to NO (done).
2) If you want to perform an action outside of the object, I would rather recommend to use the very general NSNotification with NSNotificationCenter and within your main, post a notification such as #"willStart" and #"didComplete". In any other object, register as an observer for your notifications.
Either way, don't forget that notifications are sent in the current threads but the UI must be updated on the main thread. You don't know on what thread observe:keyPath: is called. You may need to call performSelectorOnMainThread to update the UI or you can even use the convenient and useful nsoperationqueue mainqueue with a addOperationWithBlock with your UI code. If you use the NotificationCenter, then you can simply yourself post on the main thread with nsobject performSelectorOnMainThread