I'm using DispatchGroup to wait until a callback for one of my functions executes before continuing. Within that function, I'm calling Alamo fire get request. My issue occurs when I introduce the DispatchGroup, the AlamoFire closure never gets executed.
Sample
let group = DispatchGroup()
group.enter()
Networking.getInfo(userID: userID) { info in
group.leave()
}
group.wait()
Networking class:
static func getInfo(userID: Int, completion: #escaping(_ info: String) -> Void) {
// Program reaches here
Alamofire.request("https://someurl.com").responseJSON { response in
// Program does NOT get here
if let json = response.result.value {
completion("Successful request")
} else {
completion("Some Error")
}
}
}
When I don't use the DispatchGroup, it works fine. When I DO use the DispatchGroup, The getInfo function starts, but the closure of the Alamofire request never gets executed.
Not sure I'm right, but I suspect that the Alamofire response is being queued on the same queue (main) that the group has suspended (wait()). Because the queue is suspended, the completion closure never executes.
Manually writing asynchronous code like this can be quite tricky. My suggestion would be to use any one of the asynchronous libraries out there which can help with this. My personal favourite being PromiseKit which also has specific extensions to support Alamofire. Projects like this can take a lot of the headache out of asynchronous code. They may take some time to get your head around their paradigms, but it's worth doing.
I faced the same problem. In that case I used URLSession request to get ride of it. This API enables your app to perform background downloads when your app isn’t running or, in iOS, while your app is suspended.
https://developer.apple.com/documentation/foundation/urlsession
let request = try URLRequest(url: url, method: .get, headers: headers)
Alamofire.request(request) { response in
...
...
}
I changed it like this :-
let request = try URLRequest(url: url, method: .get, headers: headers)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
...
...
}
task.resume()
And then it worked fine.
Related
I have an app that uploads multiple images to the server using Alamofire. Each image needs an upload token before uploading to the server. So, in a for loop, for every image file,
I make a get request via Alamofire to get the upload token
After getting the token, I make an upload request via Alamofire, with that token.
Here is my code:
func uploadFile(image: imageToUpload, onCompletion: #escaping ((Bool) -> Void)) {
...//some code
// Alamofire request to get an upload token
getUploadToken() { uploadToken in
if uploadToken != nil {
// Alamofire request to make the upload with uploadToken and image data
makeUploadRequest(token: uploadToken, image: imageToUpload) { uploadResponse in
onCompletion(uploadResponse)
}
}
}
And this is my for loop to iterate through the images to upload:
DispatchQueue.global(qos: .background).async {
let group = DispatchGroup()
for fileToUpload in filesToUpload {
group.enter()
FileOperations.shared.uploadFile(image: fileToUpload) { hasFinished in
if hasFinished {
group.leave()
}
}
group.wait()
}
}
So I need to use queues to implement this. My code needs to work like this:
Queue 1: getUploadToken request for image 1.
Queue 2: makeUploadRequest for image 1.
Queue 3: getUploadToken request for image 2.
Queue 4: makeUploadRequest for image 2.
... and so on.
So only after image 1 in for loop completes its whole process, the image 2 can start its process. With queues, even if the app is backgrounded, it should work.
How can I achieve this kind of behaviour with queues?
I can make this work with DispatchGroup(), but it doesn't work when the app backgrounded. I researched and read everything on the internet but I couldn't get anything seem to work. So I want to make this work with queues. Any help would be appreciated.
You are right, you can do this with an OperationQueue all you'll need to do is subclass Operation to get it configured to perform the actions you want and initialize an OperationQueue and configure that to run on a background thread and only allow one concurrent task. Then, add the operations to the queue in the order you want and they will begin executing.
Can you change your code to the following and check if for example three images are uploaded correctly.
let group = DispatchGroup()
DispatchQueue.global(qos: .background).async {
for fileToUpload in filesToUpload {
group.enter()
FileOperations.shared.uploadFile(image: fileToUpload) { hasFinished in
if hasFinished {
group.leave()
}
}
}
}
group.notify(queue: .main) {
print("Finished all upload requests.")
}
Problem Desctiption:
I want to do a bunch of asynchronous tasks by 'DispatchGroup' and when all of them finished it returned the result. In addition, I want to set timeout that limits the process and send me back the successful results by that time. I used the following structure:
Code Block
let myGroup = DispatchGroup()
var result = [Data]()
for i in 0 ..< 5 {
myGroup.enter()
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
result.append(response.data)
myGroup.leave()
}
}
// Timeout for 10 seconds
myGroup.wait(timeout: DispatchTime(uptimeNanoseconds: 10000000000))
myGroup.notify(queue: .main) {
return result
}
How can I get the latest result if timeout happened?
Ok, so you are correctly using the enter/leave functionality of the DispatchGroup, but are having trouble with how to access the results of these. I think you are going wrong by trying to use both wait and notify, these two functions provide two different pieces of functionality not usually used together. After having setup up your work items, as you have done, you have two options:
The wait approach
This function blocks the calling queue and wait synchronously for either, the passed in wall time to elapse, or all work items in the group to leave. Because it is blocking the caller, it is important to always have a timeout in this function.
The notify approach
The function takes a target queue, and a block to be run when all work items in your group have completed. Here you are basically asking the system to notify you, asynchronously once all work items have been completed. Since this is asynchronous we are usually less worried about the timeout, it's not blocking anything.
Asynchronous wait (this appears to be what you want?)
If, as it seems you do, we want to be notified once all work items are complete, but also have a timeout, we have to do this ourselves, and it's not all that tricky. We can add a simple extension for the DispatchGroup class...
extension DispatchGroup {
func notifyWait(target: DispatchQueue, timeout: DispatchTime, handler: #escaping (() -> Void)) {
DispatchQueue.global(qos: .default).async {
_ = self.wait(timeout: timeout)
target.async {
handler()
}
}
}
}
This simple function dispatches asynchronously on a global background queue, then calls wait, which will wait for all work items to complete, or the specified timeout, whichever comes first. Then it will call back to your handler on the specified queue.
So that's the theory, how can you use this. We can keep your initial setup exactly the same
let myGroup = DispatchGroup()
var result = [Data]()
for i in 0 ..< 5 {
myGroup.enter()
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
result.append(response.data)
myGroup.leave()
}
}
and then use our new function to wait for the end
myGroup.notifyWait(target: .main,
timeout: DispatchTime.now() + 10) {
// here you can access the `results` list, with any data that has
// been appended by the work items above before the timeout
// was reached
}
I have a simple function loading data from Firebase.
func loadFromFireBase() -> Array<Song>? {
var songArray:Array<Song> = []
ref.observe(.value, with: { snapshot in
//Load songArray
})
if songArray.isEmpty {
return nil
}
return songArray
}
Currently this function returns nil always, even though there is data to load. It does this because it doesn't ever get to the perform the completion block where it loads the array before the function returns. I'm looking for a way to make the function only return once the completion block has been called but I can't put return in the completion block.
(Variations on this question come up constantly on SO. I can never find a good, comprehensive answer, so below is an attempt to provide such an answer)
You can't do that. Firebase is asynchronous. Its functions take a completion handler and return immediately. You need to rewrite your loadFromFirebase function to take a completion handler.
I have a sample project on Github called Async_demo (link) that is a working (Swift 3) app illustrating this technique.
The key part of that is the function downloadFileAtURL, which takes a completion handler and does an async download:
typealias DataClosure = (Data?, Error?) -> Void
/**
This class is a trivial example of a class that handles async processing. It offers a single function, `downloadFileAtURL()`
*/
class DownloadManager: NSObject {
static var downloadManager = DownloadManager()
private lazy var session: URLSession = {
return URLSession.shared
}()
/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
}
The code above uses Apple's URLSession class to download data from a remote server asynchronously. When you create a dataTask, you pass in a completion handler that gets invoked when the data task has completed (or failed.) Beware, though: Your completion handler gets invoked on a background thread.
That's good, because if you need to do time-consuming processing like parsing large JSON or XML structures, you can do it in the completion handler without causing your app's UI to freeze. However, as a result you can't do UI calls in the data task completion handler without sending those UI calls to the main thread. The code above invokes the entire completion handler on the main thread, using a call to DispatchQueue.main.async() {}.
Back to the OP's code:
I find that a function with a closure as a parameter is hard to read, so I usually define the closure as a typealias.
Reworking the code from #Raghav7890's answer to use a typealias:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler: #escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
})
}
I haven't used Firebase in a long time (and then only modified somebody else's Firebase project), so I don't remember if it invokes it's completion handlers on the main thread or on a background thread. If it invokes completion handlers on a background thread then you may want to wrap the call to your completion handler in a GCD call to the main thread.
Edit:
Based on the answers to this SO question, it sounds like Firebase does it's networking calls on a background thread but invokes it's listeners on the main thread.
In that case you can ignore the code below for Firebase, but for those reading this thread for help with other sorts of async code, here's how you would rewrite the code to invoke the completion handler on the main thread:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler:#escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
//Pass songArray to the completion handler on the main thread.
DispatchQueue.main.async() {
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
})
}
Making Duncan answer more precise. You can make the function like this
func loadFromFireBase(completionHandler:#escaping (_ songArray: [Song]?)->()) {
ref.observe(.value) { snapshot in
var songArray: [Song] = []
//Load songArray
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
}
You can return the songArray in a completion handler block.
I know this question has been asked before, but all solutions do not work for me.
I have a function with sends parameters to an API, and returns the data as a list, I have a UITableView set up to use that list, but it runs before the list is assigned to a variable.
code:
var functionResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//gradesscrollView.contentSize.height = 3000
fetchItems{ (str) in
var returnedItems = [String]()
let result = self.convertoArray(itemstoPass: str!)
for i in result{
functionResult.append(i)
}
}
self.tableofItems.delegate = self
self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.
}
I would appreciate it if it is not immediately voted as a duplicate, here is what I have tried.
Dispatch groups
Semaphore timing
running variables
including self.tableofItems.delegate = self & self.tableofItems.dataSource = self in the fetchItems{ (str) in part.
EDIT:
Fetch items has been requested,
func fetchItems(completionHandler: #escaping (String?) -> ()) -> () {
let headers = [
"Content-Type": "application/x-www-form-urlencoded"
]
//Switch to keychain
let username = UserDefaults.standard.object(forKey: "username") as! String?
let password = UserDefaults.standard.object(forKey: "password") as! String?
let usernametoSend = username!
let passwordtoSend = password!
print(usernametoSend)
print(passwordtoSend)
let parameters: Parameters = [
"username": usernametoSend,
"password": passwordtoSend
]
Alamofire.request("https://www.mywebsite.com/API/getItems", method: .post, parameters: parameters, headers: headers)
.responseString { response in
completionHandler(String(response.result.value!))
You can't - and shouldn't - wait until an async call to complete. You need to study async programming until you understand it.
An async function accepts a job to do, and returns immediately, before the job is done.
in Swift you usually write an async function to take a completion handler, which is a block of code that you want to be run one the async task is complete.
I have a project called Async_demo (link) on Github that illustrates this. It implements a DownloadManager class that handles async downloads.
The key part is the function downloadFileAtURL(), which should more properly be named downloadDataAtURL, since it returns in-memory data rather than a file.
I created that function to take a completion handler as a parameter:
/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
}
It uses an NSURLSession to download a block of data from the specified URL. The data request call I use takes a completion handler that gets executed on a background thread. In the completion handler that I pass to the data task, I invoke the completion handler that's passed in to the downloadFileAtURL() function, but on the main thread.
The code you posted is kind of confusing. It isn't clear which part is the async function, what the flow is, or what data is needed to display your table view.
If you rewrite your function that does async work to take a completion block then you could call tableView.reloadData() in your completion block. (Make sure that call is performed on the main thread.)
EDIT:
As others have said, you need to edit your question to show the code for your fetchItems() function.
I'm guessing that that function is the one that does the Async work, and that the block after it is a completion handler that gets performed asynchronously. If so, you should probably refactor your code like this:
var functionResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//I moved these lines above the call to fetchItems to make it clear
//that they run before fetchItems' completion closure is executed
self.tableofItems.delegate = self
self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.
print("Step 1")
fetchItems{ (str) in
var returnedItems = [String]()
let result = self.convertoArray(itemstoPass: str!)
for i in result{
functionResult.append(i)
}
print("Step 3")
DispatchQueue.main.async() {
tableview.reloadData() //Do this from the main thread, inside the closure of `fetchItems()`
}
}
print("Step 2")
}
You should load the data of the list in the method of loadView, so that it will be loaded early before UITableView reads.
Sometimes, viewDidLoad performs a little bit slowly. Generally, people will initialize the data of list or sth in the method of loadView to make sure the data is completed before view is created.
How to cancel Alamofire request if the downloaded file is already exists in documents folder?
Here is the code for request:
Alamofire.download(.GET, fileUrls[button.tag], destination: { (temporaryURL, response) in
if let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
let fileURL = directoryURL.URLByAppendingPathComponent(response.suggestedFilename!)
self.localFilePaths[button.tag] = fileURL
if NSFileManager.defaultManager().fileExistsAtPath(fileURL.path!) {
NSFileManager.defaultManager().removeItemAtPath(fileURL.path!, error: nil)
}
return fileURL
}
println("temporaryURL - \(temporaryURL)")
self.localFilePaths[button.tag] = temporaryURL
return temporaryURL
}).progress { _, totalBytesRead, totalBytesExpectedToRead in
println("\(totalBytesRead) - \(totalBytesExpectedToRead)")
dispatch_async(dispatch_get_main_queue()) {
self.progressBar.setProgress(Float(totalBytesRead) / Float(totalBytesExpectedToRead), animated: true)
if totalBytesRead == totalBytesExpectedToRead {
self.progressBar.hidden = true
self.progressBar.setProgress(0, animated: false)
}
}
}.response { (_, _, data, error) in
let previewQL = QLReaderViewController()
previewQL.dataSource = self
previewQL.currentPreviewItemIndex = button.tag
self.navigationController?.pushViewController(previewQL, animated: true)
}
I've also tried to create a request variable var request: Alamofire.Request? and then cancel request?.cancel() it if that file exists but it doesn't work.
Can someone help me to solve this issue?
Rather than cancelling the request, IMO you shouldn't make it in the first place. You should do the file check BEFORE you start the Alamofire request.
If you absolutely feel you need to start the request, you can always cancel immediately after starting the request.
var shouldCancel = false
let request = Alamofire.request(.GET, "some_url") { _, _ in
shouldCancel = true
}
.progress { _, _, _ in
// todo...
}
.response { _, _, _ in
// todo...
}
if shouldCancel {
request.cancel()
}
TL; DR: Canceling a request is a bit cumbersome in many cases. Even Alamofire, as far as I know, does not guarentee that request will be cancelled upon your request, immediately. However, you may use dispatch_suspend or NSOperation in order to overcome this.
Grand Central Dispatch (GCD)
This way utilizes functional programming.
Here we enlight our way with low-level programming. Apple introduced a good library, aka GCD, to do some thread-level programming.
You cannot cancel a block, unless... you suspend a queue (if it is not main or global queue).
There is a C function called dispatch_suspend, (from Apple's GCD Reference)
void dispatch_suspend(dispatch_object_t object);
Suspends the invocation of block objects on a dispatch object.
And you can also create queues (who are dispatch_object_ts) with dispatch_queue_create.
So you can do your task in user created queue, and you may suspend this queue in order to prevent CPU from doing something unnecessary.
NSOperation (also NSThread)
This way utilizes functional programming over object-oriented interface.
Apple also introduced NSOperation, where object-oriented programming may be object, whereas it is easier to cope with.
NSOperation is an abstract class, which associates code and data, according to the Apple's documentation.
In order to use this class, you should either use one of its defined subclasses, or create your own subclass: In your case particularly, I suppose NSBlockOperation is the one.
You may refer to this code block:
let block = NSBlockOperation { () -> Void in
// do something here...
}
// Cancel operation
block.cancel()
Nevertheless, it also does not guarantee stopping from whatever it is doing. Apple also states that:
This method does not force your operation code to stop. Instead, it updates the object’s internal flags to reflect the change in state. If the operation has already finished executing, this method has no effect. Canceling an operation that is currently in an operation queue, but not yet executing, makes it possible to remove the operation from the queue sooner than usual.
If you want to take advantage of flags, you should read more: Responding to the Cancel Command