Reputation: 29458
I have a networking class that does my fetching of data from the server. In the completion handler of that class, it looks something like this:
func fetchData(url: URL, completion: @escaping (Result<Data, MyError>) -> Void) {
let request = URLRequest(url: url)
fetch(request: request) { (result: Result<Data, MyError>) in
switch result {
case .success(let response):
DispatchQueue.main.async {
completion(.success(response))
}
case .failure(let error):
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
If I call this fetchData
method from my ViewController, I get the callback on the main thread and I don't have to reload my collection view on the main thread. I then tried adding a view model for my view controller. So the flow looks more like:
ViewController -> ViewModel (fetchData) -> Networking (fetchData)
where basically each class just calls a method that looks exactly like the above fetchData method, passing the completion upwards. In ViewController, do I need to check again that I'm on the main thread. Could iOS switch threads during these calls? I ask because I did get a warning about updating the UI was not called on the main thread one time. But I'm not sure if that was a false negative from this call since I have other networking calls to fetch images, and maybe I messed something else up elsewhere. But basically, I'm just asking if I don't do any other GCD type tasks, but only use completion handlers and bubble up the completion from the single networking call that calls back on the main thread, do I need to check again somewhere up the chain (like in the ViewController).
Upvotes: 1
Views: 2494
Reputation: 114836
You haven't provided the code fo "these calls", so it isn't possible to say whether code will be dispatched on another queue, however, the system doesn't arbitrarily switch to another queue while executing code. You need to explicitly or implicitly dispatch onto another queue. Your code above contains an explicit dispatch onto the main queue and an implicit dispatch onto another queue when you call fetch
(Somewhere in that code will be an implicit dispatch onto another queue, perhaps in code where you can't see the source).
As a simple answer to your question, if you dispatch onto the main queue in the completion handler shown and none of the other code called "further up" performs asynchronous work or explicitly dispatches onto a queue other than the main queue you can be certain that execution will continue on the main queue.
Also, you can simplify your code by simply calling the upstream completion handler directly:
func fetchData(url: URL, completion: @escaping (Result<Data, MyError>) -> Void) {
let request = URLRequest(url: url)
fetch(request: request) { (result: Result<Data, MyError>) in
DispatchQueue.main.async {
completion(response))
}
}
}
When designing your code you should adopt one of two approaches and stick to it:
Dispatch onto the main queue early. This approach is often taken by frameworks that may well be consumed by someone else; For example AFNetworking explicitly documents that completion handlers are dispatched onto the main queue so you don't need to worry about it. The disadvantage of this approach is that programmers may not read the documentation and may dispatch onto the main queue defensively, leading to double asynchronous dispatch or they may not be updating the UI and don't need main thread execution. This is an overhead but unlikely to be a major issue.
Never dispatch onto the main queue and rely on the calling code to dispatch if it needs to do so. This approach may be more common where all of the code is part of one solution and the programmer "knows" that they ultimately need to dispatch onto the main queue. The advantage of this approach is that you defer (and potentially avoid entirely if it isn't required) dispatching work to the main queue. The disadvantage is that if you forget to do it you will get warnings and main thread violations
Upvotes: 3
Reputation: 36287
if you're talking about this:
func fetchData(url: URL) { result in
print(result) // <-- This on main thread and should not cause any warnings
}
If you're certain that's what's happening then it's a false positive. But I highly doubt it. I've never seen it malfunction. You can easily use the Main Thread Checker and detect mistakes.
Aside from that normally functions shouldn't dictate the completionHandler's thread. ie it's on the caller to dispatch the thread. I mean if you ever wanted to dispatch this to another thread, then you'd be dispatching it twice which isn't ideal.
Upvotes: 0