func authenticate(completion:(success: Bool) -> Void) {
let qos = Int(QOS_CLASS_USER_INITIATED.value)
dispatch_async(dispatch_get_global_queue(qos, 0)){ () -> Void in
Alamofire.request(.POST, CONSTANTS.Domain+"/accounts", parameters: ["" : ""]).responseJSON { (req, res, json, error) in
dispatch_async(dispatch_get_main_queue()){
completion(success: true)
}
}
}
}
Or, can I leave out the dispatch and just keep my code simple?
Alamofire is designed to be asynchronous. On another note, if the method has as callback, most likely it is asynchronous.
So, yes you can leave out the dispatch_async calls.
Related
I perform the next code
let task = session.uploadTask(with: request, from: requestData.body) { data, response, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
All is clear, after doing network request in background I return completion to .main . But how to handle case, if I want to call completion not in .main but in thread in which session.uploadTask was initiated, because in my application it could be not only .main .
There is not a GCD mechanism to retrieve the current dispatch queue so that you can later dispatch to it. (A long time ago, there used to be a way to fetch the current queue, but it was deprecated back in iOS 7, and even then it was “Recommended for debugging and logging purposes only.”)
If you want to call the completion handlers on specific dispatch queue, I would suggest supplying an explicit DispatchQueue parameter to the method. Below, I have it default to .main, but the caller can override that with whatever it wants:
func perform(_ request: URLRequest, with data: Data, on queue: DispatchQueue = .main, completion: #escaping (Result<T, Error>) -> Void) {
let task = session.uploadTask(with: request, from: data) { data, response, error in
if let error = error {
queue.async {
completion(.failure(error))
}
}
...
}
...
}
I know that this is not precisely what you are looking for, but it is an easy way to let the caller specify on which queue your closure will be called.
If you are using operation queues, you can get the current to determine the current operation queue. And should one do this, one would use addOperation on that operation queue in order to call the completion handler.
func perform(_ request: URLRequest, with data: Data, completion: #escaping (Result<T, Error>) -> Void) {
guard let queue = OperationQueue.current else {
fatalError("Must be called from operation queue")
}
let task = session.uploadTask(with: request, from: data) { data, response, error in
if let error = error {
queue.addOperation {
completion(.failure(error))
}
}
...
}
...
}
But this pattern only works if the caller was using operation queue, not when only using dispatch queues. For this reason, I would still be inclined to adopt the pattern of supplying the target operation queue as a parameter:
func perform(_ request: URLRequest, with data: Data, on queue: OperationQueue = .main, completion: #escaping (Result<T, Error>) -> Void) {
let task = session.uploadTask(with: request, from: data) { data, response, error in
if let error = error {
queue.addOperation {
completion(.failure(error))
}
}
...
}
...
}
If you would like to call completion on the current thread simply call completion().
current thread
completion(.failure(error))
main thread
DispatchQueue.main.async {
completion(.failure(error))
}
background thread
Dispatch.global(qos: .background) {
completion(.failure(error))
}
I used this solution:
guard let currentDispatch = OperationQueue.current?.underlyingQueue else {
return
}
let task = session.uploadTask(with: request, from: requestData.body) { data, response, error in
if let error = error {
currentDispatch.async {
completion(.failure(error))
}
}
}
Have not tested it too much, but quick debugging shows what I wanted to achieve,
I have a need to implement a spinning wheel during a backend job. I have my backend-job in a separate class.
class ViewControllerA: UITableViewController {
// Code
var GetBackendRecordObj = GetBackendRecord(initparam:param);
// CODE TO START ANIMATION (SPINNING WHEEL)
self.view.addSubview(self.activityIndicator)
self.activityIndicator.bringSubview(toFront: self.view)
self.activityIndicator.startAnimating()
// CODE TO CALL THE BACKEND IS IN ANOTHER CLASS
GetBackendRecordObj.fetch_record()
}
class GetBackendRecord{
var transaction_id: String = ""
var current_email_id: String = ""
init(initparam: String) {
self.initparam = initparam
}
func fetch_record (){
do{
DispatchQueue.global(qos: .userInitiated).async {
//code
DispatchQueue.main.async { () -> Void in
//code to process response from backend
// NEED CODE TO STOP ANIMATION (SPINNING WHEEL)THAT WAS STARTED IN VIEWCONTROLLERA
})
}
}
}
How can I access the UITableViewcontroller after the backend call is done, so I can stop the animation. OR If there is a better way to start / stop animation when executing a backend-job (in a separate class) please let me know.
Add a completion handler to fetch_record:
func fetch_record(_ completionHandler: #escaping () -> Swift.Void) {
do{
DispatchQueue.global(qos: .userInitiated).async {
//code
DispatchQueue.main.async { () -> Void in
//code to process response from backend
completionHandler()
})
}
}
}
When calling it in your ViewController, you can specify what to do after completion:
GetBackendRecordObj.fetch_record() {
self.activityIndicator.stopAnimating()
}
If you need to know when the response returns so that you can stop the loader, you need to add a completion handler to the method that makes your internet call. You should generally have completion handlers on most methods that make internet calls, especially if you need UI things to happen only once you have gotten a response.
So for the fetchRecord function:
I have added a completion handler to this call. Preferably you would hand something off here just after #escaping(something like a dictionary or an array if it is a JSON response) and then process that response in another method. But if you want the code to process the response in this method with the threading that you've set up here, then I've written it accordingly.
func fetch_record(withCompletion comp: #escaping () ->()){
do{
DispatchQueue.global(qos: .userInitiated).async {
//code
DispatchQueue.main.async { () -> Void in
//code to process response from backend
//this tells whatever called this method that it is done
comp()
})
}
}
}
Then in your view controller when you call GetBackendRecordObj.fetch_record()
GetBackendRecordObj.fetch_record(withCompletion: { [weak self] () in
//when it hits this point, the process is done
self?.activityIndicator.stopAnimating()
}
Instead of activityIndicator better to use MBProgressHUD.
https://github.com/jdg/MBProgressHUD
To show MBProgressHUD
let loadingNotification = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
loadingNotification.mode = MBProgressHUDMode.Indeterminate
loadingNotification.labelText = "Loading"
To hide MBProgressHUD
DispatchQueue.main.async { () -> Void in
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
})
You can implement show MBProgressHUD in seperate class from where you initiate back end job and hide code once your back end process finish.
I am writing a Swift function using closure. A should-be-compilable code sample is like this,
import Foundation
typealias PKSynchronizeProgressBlock = (Double) -> Void
typealias PKSynchronizeCompletionBlock = (Bool, NSError?) -> Void
class X {
func synchronizeAppDataWithProgress(
progress: PKSynchronizeProgressBlock?,
completion: PKSynchronizeCompletionBlock?) {
dispatch_async(dispatch_get_main_queue(), {
// Do a lot of downloading, and during the process
// {
// If progress is updated
if (progress != nil) {
progress!(Double(0))
}
//
// If something goes wrong
if (completion != nil) {
completion!(false, nil)
}
// }
dispatch_async(dispatch_get_main_queue(), {
if (completion != nil) {
completion!(true, nil)
}
})
})
}
func foo() {
self.synchronizeAppDataWithProgress({ (progress: Double) -> Void in
self.launchProgressBar.progress = progress
}, completion: { (success: Bool, error: NSError?) -> Void in
if success {
self.launchProgressBar.progress = 1.0
}
else {
print("Failed to synchronize app data with error %#", error!)
}
})
}
}
However, this code does not compile. Xcode says that
cannot invoke 'synchronizeAppDataWithProgress' with an argument list
'(progress: (Double) -> Void, completion: (Bool, NSError?) -> Void)'
What should I do? Did I make any stupid mistake in my code?
Update:
Thanks to #Mario Zannone. I fixed the first two mistakes in my code above. That was: (1) I inserted a redundant progress: in the function call. I have removed that. (2) I updated UI in a thread other than main thread.
But the code still does not work if I don't comment out the following single line in the foo(),
self.launchProgressBar.progress = progress
Do you have any clue why?
Xcode can be picky sometimes with the way arguments are listed inside closures. I've found it best to leave the type inferred. Also be sure to use capture lists to avoid strong reference cycles in your closures.
Using the Alamofire dependency, I've rewritten your code above and it compiles.
import Alamofire
typealias ProgressBlock = (Double) -> Void
typealias CompletionBlock = (Bool, ErrorType?) -> Void
class ExampleDataSource {
func fetchData(progress: ProgressBlock?, completion: CompletionBlock?) {
// Here we use the Alamofire Dependency for progress reporting simplicity.
Alamofire.request(.GET, "https://www.myexampledomain.com")
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
// bytesRead, totalBytesRead, and totalBytesExpectedToRead are Int64
// so we must perform unit conversion
let progressPercentage = Double(totalBytesRead) / Double(totalBytesExpectedToRead)
// here we optionally call the ProgressBlock 'progress' and pass it the progressPercentage
progress?(progressPercentage)
}
.response { request, response, data, error in
// here we usually parse the data, but for simplicity we'll
// simply check to see if it exists.
let completionFlag = (data != nil)
// note that NSError? is interchangable with ErrorType?
completion?(completionFlag, error)
}
}
func performTasks() {
// first we'll set up our closures...
let progressBlock: ProgressBlock = { progress in
// here we update the UI or whatever
// the nice thing about the Alamofire dependency is
// that it's thread-safe :]
}
let completionBlock: CompletionBlock = { success, error in
// here we do whatever we need to do when the
// network operation finishes, or handle the
// errors appropriately
}
// then we'll pass them into our fetchData method
fetchData(progressBlock, completion: completionBlock)
}
}
I believe I'm having an issue where my closure is happening on a background thread and my UITableView isn't updating fast enough. I am making a call to a REST service and in my closure i have a tableView.reloadData() call but it takes a few seconds for this to happen. How do I make the data reload faster (perhaps on the main thread?)
REST Query Function - using SwiftyJSON library for Decoding
func asyncFlightsQuery() {
var url : String = "http://127.0.0.1:5000/flights"
var request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: url)
request.HTTPMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, networkData: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
// Parse with SwiftyJSON
let json = JSON(data: networkData)
// Empty out Results array
self.resultArray = []
// Populate Results Array
for (key: String, subJson: JSON) in json["flights"] {
print ("KEY: \(key) ")
print (subJson["flightId"])
print ("\n")
self.resultArray.append(subJson)
}
print ("Calling reloadData on table..??")
self.tableView.reloadData()
})
}
Once self.tableView.reloadData() is called in my debugger
UIKit isn't thread safe. The UI should only be updated from main thread:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
Update. In Swift 3 and later use:
DispatchQueue.main.async {
self.tableView.reloadData()
}
You can also reload UITableView like this
self.tblMainTable.performSelectorOnMainThread(Selector("reloadData"), withObject: nil, waitUntilDone: true)
With Swift 3 use
DispatchQueue.main.async {
self.tableView.reloadData()
}
You can also update the main thread using NSOperationQueue.mainQueue(). For multithreading, NSOperationQueue is a great tool.
One way it could be written:
NSOperationQueue.mainQueue().addOperationWithBlock({
self.tableView.reloadData()
})
Update: DispatchQueue is the way to go for this
Update 2: Use DispatchQueue solution as seen in accepted answer above
SWIFT 3:
OperationQueue.main.addOperation ({
self.tableView.reloadData()
})
DispatchQueue.main.async {
self.tableView.reloadData()
}
Do this to update the UI on the main thread. Using this method it will bring update the table view and reflect the data in the UI.
I suggest to improve it one step further (starting from swift 3):
struct UIHelper {
static func performUpdate(using closure: #escaping () -> Void) {
if Thread.isMainThread {
closure()
} else {
DispatchQueue.main.async(execute: closure)
}
}
}
And then:
UIHelper.performUpdate {
self.tableView.reloadTable()
}
The reason for this is that sometimes the call is made on main thread, so there's no need to execute UI update on async thread.
I'm using Alamofire (AF) in a concurrent operation queue to run network commands in my project. Sometimes AF's completionHandler doesn't fire, leaving my NSOperation hanging (waiting for a finish message that it will never receive).
Eg. I'll see the "response" log, but no corresponding "see me" log from AF's dispatch_async below:
public func response(priority: Int = DISPATCH_QUEUE_PRIORITY_DEFAULT, queue: dispatch_queue_t? = nil, serializer: (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> (AnyObject?, NSError?), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
NSLog("markse-response")
dispatch_async(self.delegate.queue, {
NSLog("markse-see me")
dispatch_async(dispatch_get_global_queue(priority, 0), {
if var error = self.delegate.error {
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(self.request, self.response, nil, error)
})
} else {
let (responseObject: AnyObject?, serializationError: NSError?) = serializer(self.request, self.response, self.delegate.data, nil)
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(self.request, self.response, responseObject, serializationError)
})
}
})
})
return self
}
This is my NSOperation (AsynchronousCommand is an NSOperation subclass):
import Alamofire
class SettingsListCommand: AsynchronousCommand {
override func execute() {
if cancelled { return }
let endpoint = "https://api.github.com/users/cog404/orgs"
DLogVerbose("AF request")
weak var weakSelf = self
Alamofire.request(.GET,
endpoint,
parameters:nil)
.responseJSON {(request, response, JSON, error) in
DLogVerbose("AF response")
if let strongSelf = weakSelf {
if strongSelf.cancelled {
strongSelf.finish()
return
}
DLogVerbose(JSON)
strongSelf.finish()
}
}
}
}
This only happens 'occasionally', making this very difficult to debug.
Does anyone with a good understanding of threading know what could be going wrong?
Any advice very much appreciated. A project to help illustrate the problem is here.
The request delegate's dispatch queue is serial, meaning that it will only process a single block at a time, in order of when the blocks were dispatched (FIFO). If the second log statement isn't firing, it's because the previous block didn't yet finish.