I have been developing an app on iPhone using Swift. I am a newbie and I don't have much experience. I have a problem now. Once I started my app, I used timers for background processes. For instance I was calling functions to check if the response arrived from the server every 0.2 seconds. And that is not the proper way as you know. Now I'm trying to learn treading in swift. What I exactly need is, I need to know when a background process is finished and I need to start another background process.
DispatchQueue.global(cos: .userInteractive).async{
//some request and parsing json
}
Now, when the task is finished I have to start another task. In detail, I will check the last version required, than I will make a login request, than I will pull some images from background, than I will finish the animation. That's why I need to know how to know a background thread is finished so I can start other thread. I think I should use serial queue right?
One more thing, I have to check if process took so much time, so will warn the user about connection.
DispatchQueueHelper.delay(byseconds: x, cos:.background){
// will this work for killing the thread
}
You can use a dispatch group to keep track of your actions:
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
dispatchGroup.leave()
dispatchGroup.notify(queue: .main) {
// Back on main
}
But this is often when you create multiple requests at once. Normally network requests have their own completion block that will alert you when it's completed. Something like:
URLSession.shared.dataTask(with: url) {(data, response, error) in
// Task is done
}
In your function that you call to execute the request you should leave an completionBlock as:
func execute(request url: URLRequest, completion: (() -> ()) {
URLSession.shared.dataTask(with: url) {(data, response, error) in
completion()
}
}
You of course shouldn't call the completion immediately as you want to handle the data and send it inside.
In swift 5 they introduced Result<Any, Error> which is a great thing to send in your completionBlock completion: ((Result<Model, Error>) -> ())
On the other side it will look something like:
execute(request: request { result in
switch result {
case .success(let model):
// Handle data
case .failure(let error):
// Handle error
}
})
You can use sync instead of async and add multiple tasks in the queue.
This way they will be executed one after another.
On the other hand, async runs them in the same time, and it is more difficult to know which one finished when.
Related
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).
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
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.
So I've recently come back to Swift & iOS after a hiatus and I've run into an issue with asynchronous execution. I'm using Giphy's iOS SDK to save myself a lot of work, but their documentation is pretty much nonexistent so I'm not sure what might be happening under the hood in their function that calls their API.
I'm calling my function containing the below code from the constructor of a static object (I don't think that's the problem as I've also tried calling it from a cellForItemAt method for a Collection View).
My issue is that my function is returning and execution continues before the API call is finished. I've tried utilizing DispatchQueue.main.async and removing Dispatch entirely, and DispatchGroups, to no avail. The one thing that worked was a semaphore, but I think I remember reading that it wasn't best practice?
Any tips would be great, I've been stuck on this for waaaaaay too long. Thanks so much in advance
GiphyCore.shared.gifByID(id) { (response, error) in
if let media = response?.data {
DispatchQueue.main.sync {
print(media)
ret = media
}
}
}
return ret
My issue is that my function is returning and execution continues before the API call is finished.
That's the whole point of asynchronous calls. A network call can take an arbitrary amount of time, so it kicks off the request in the background and tells you when it's finished.
Instead of returning a value from your code, take a callback parameter and call it when you know the Giphy call has finished. Or use a promise library. Or the delegate pattern.
The one thing that worked was a semaphore, but I think I remember reading that it wasn't best practice?
Don't do this. It will block your UI until the network call completes. Since you don't know how long that will take, your UI will be unresponsive for an unknown amount of time. Users will think your app has crashed on slow connections.
You could just add this inside a method and use a completion handler and therefore do you not need to wait for the response. You could do it like this:
func functionName(completion: #escaping (YOURDATATYPE) -> Void) {
GiphyCore.shared.gifByID(id) { (response, error) in
if let media = response?.data {
completion(media)
return
}
}
}
Call your method like this
functionName() { response in
DispatchQueue.main.async {
// UPDATE the UI here
}
}
I have a situation where I need to make two HTTP GET requests and handle their results only after both are finished. I have a completion handler on each individual network request but it isn't helpful as I don't know when data from both requests are retrieved.
I have limited experience with GCD but now that Swift 3 is out, I am trying to figure out how to run multiple tasks and have a single completion handler for them. My research has shown that GCD or NSOperationQueue may be the solution I'm looking for. Can anyone help suggest which tool fits the job and what the code might look like in Swift 3?
You should use dispatch groups, entering the group before you issue the request, and leaving the group in the completion handler for the request. So, let's assume, for a second, that you had some method that performed an asynchronous request, but supplied a completion handler parameter that was a closure that will be called when the network request is done:
func perform(request: URLRequest, completionHandler: #escaping () -> Void) { ... }
To start these two concurrent requests, and be notified when they're done, you'd do something like:
let group = DispatchGroup()
group.enter()
perform(request: first) {
group.leave()
}
group.enter()
perform(request: second) {
group.leave()
}
group.notify(queue: .main) {
print("both done")
}
Clearly, your implementation of perform(request:) may vary significantly (e.g. you might have the closure pass the data back), but the pattern is the same whether you are writing your own networking code with URLSession or using Alamofire. Just use GCD groups, entering the group when you create the requests, and leaving the group in the completion handler of the asynchronous request.
Source: How do I write dispatch_after GCD in Swift 3?
You can use dispatch_group for that.
For example (ObjC code):
dispatch_group_t group = dispatch_group_create();
//startOperation1
dispatch_group_enter(group);
//finishOpeartion1
dispatch_group_leave(group);
//startOperation2
dispatch_group_enter(group);
//finishOpeartion2
dispatch_group_leave(group);
//Handle both operations completion
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//code here
});
I have cells that have buttons that trigger the downloading of their respective PDF from online. I want it so that only one download can occur at a time, and the other ones (if their button is clicked) wait for it to finish.
I cannot use any sort of queue, because the queue operation calls the download methods but does not wait for them to complete before moving on.
Is there any way that I can only move on once the did finish download function says that it is ready by passing a boolean or something? I am pretty lost here so any direction is greatly appreciated.
I cannot use any sort of queue, because the queue operation calls the download methods but does not wait for them to complete before moving on.
This can be accomplished using NSOperation Queues. The key is that your download tasks have to be async NSOperation subclasses where you mark the operation as finished when the download finishes. More importantly, these operations should be queued on a serial queue. Then, operations will be executed only one at a time in FIFO order.
However, it takes a bit of boilerplate to get NSOperations setup this way. Another good way to do it is using Dispatch Groups.
// A serial queue ensures only one operation is executed at a time, FIFO
let downloadsQueue = dispatch_queue_create("com.youapp.pdfdownloadsqueue", DISPATCH_QUEUE_SERIAL)
let downloadGroup = dispatch_group_create()
func queueDownload(from url: NSURL) {
// Register this download task with the group
dispatch_group_enter(downloadGroup)
// Async dispatch the download task to our serial queue,
// so that it returns control back without blocking the main thread
dispatch_async(downloadsQueue) {
downloadPDF(with: url) { (pdf, error) in
// handle PDF data / error
// { .. }
// leave the dispatch group in the completion method,
// notifying the group that this task is finished
dispatch_group_leave(downloadGroup)
}
}
}
func downloadPDF(with url: NSURL, completion: (pdf: NSData?, error: ErrorType?) -> ()) {
// make network request
// call completion with PDF data or error when the download request returns
}
I'm getting unpredictable results and I'd like to understand why. I'm calling two functions. The first time, the second function completes first, but after that, it works and they complete in order. I'm new to Swift, so I'm lost.
Here are the two calls:
updateValue(value: row, picker: picker.tag)
updateResult()
updateValue calls a web service that writes data using:
...
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue()) {(response, data, error) -> Void in
if error != nil {
println("error")
} else {
println(response)
}
...
then updateResult reads the data also using a web service call.
...
var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {
(data, response, error) -> Void in
...
Since updateValue is asynchronous (and I've read here that's the way it should be), I'm not sure what I should do. And I'm also confused as to why it only fails on the first use. If the order is unpredictable, why is the behaviour so predictable?
Thanks
Now I may be wrong but it looks to me that sendAsynchronousRequest could very well be an async call :-)
In other words, if farms off the task to another "thread" of execution and may indeed return to you before the actual work is done.
If you want synchronous behaviour, you're probably better off calling it synchronously or at least waiting until the async operation is finished before performing the dependent step (with a completionHandler for example).