I generally use the following code to update UI change or pop up some dialog box:
dispatch_async(dispatch_get_main_queue())
{
...
}
I am clear to use it in the following scenario:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Add some method process in global queue - normal for data processing
dispatch_async(dispatch_get_main_queue(), ^(){
//Add method, task you want perform on mainQueue
//Control UIView, IBOutlet all here
});
//Add some method process in global queue - normal for data processing
});
However, how about the other cases, e.g., in some closures or callback functions?
autocomplete(sbYouTube.text!) { (results, status) -> Void in
if status == "OK"
{
if let results = results
{
addAutocompletes(results)
}
dispatch_async(dispatch_get_main_queue())
{
self.tvAutocomplete.reloadData()
}
}
else
{
NSLog("%#", status)
}
}
or
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
{
dispatch_async(dispatch_get_main_queue())
{
self.downloadedSize = totalBytesWritten
self.sizeToDownload = totalBytesExpectedToWrite
self.downloadProcess.angle = Double(totalBytesWritten) * 360.0 / Double(totalBytesExpectedToWrite)
self.lbPercent.text = "\(totalBytesWritten * 100 / totalBytesExpectedToWrite)%"
}
}
dispatch_get_main_queue() function will return the main queue where your UI is running.
The dispatch_get_main_queue() function is very useful for updating the iOS app’s UI as UIKit methods are not thread safe (with a few exceptions) so any calls you make to update UI elements must always be done from the main queue.
for more see this link
https://www.hackingwithswift.com/read/9/4/back-to-the-main-thread-dispatch_get_main_queue
You are safe to use this block:
dispatch_async(dispatch_get_main_queue(), ^(){
//Add method, task you want perform on mainQueue
//Control UIView, IBOutlet all here
});
everywhere, in closures and callback functions. It makes sure that the code in it gets executed on the main thread. You can also use NSOperationQueue.mainQueue.performBlock as it does the same thing
Related
I'm running a background upload task but I found it's blocking the main thread. After looking I suspect this happens because 3rd party library (Firebase in this case) must be scheduling its async callback on the main thread.
Is there a way to explicitly make the callback run on the global thread?
Here's how I start the task from the main thread:
DispatchQueue.global(qos: .background).async {
PhotoUploadOperation().start()
}
Here's an oversimplified version of the upload task:
class PhotoUploadOperation {
func uploadCameraRoll() {
for element in photos {
self.uploadPhoto(element.image, uid) { url in
// Some work
if let url = url {
let photo = Photo(uid: uid, url: url, creationDate: element.date)
self.sendPhoto(photo: photo) { success in
// Some work
}
}
}
}
}
}
Could you try this solution?
Firebase or other 3party framework use "method swizling" to accomplish some network log or other things. But your scenario is a little bit different.
extension DispatchQueue {
static func background(delay: Double = 0.0, background: (()->Void)? = nil, completion: (() -> Void)? = nil) {
DispatchQueue.global(qos: .background).async {
background?()
if let completion = completion {
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {
completion()
})
}
}
}
}
Usage:
DispatchQueue.background(delay: 3.0, background: {
// do something in background
}, completion: {
// when background job finishes, wait 3 seconds and do something in main thread
})
DispatchQueue.background(background: {
// do something in background
}, completion:{
// when background job finished, do something in main thread
})
DispatchQueue.background(delay: 3.0, completion:{
// do something in main thread after 3 seconds
})
And you dont forget allow background processing from Signing and Capabilities
I have a scenario where I want to perform three distinct asynchronous tasks in parallel. Once all three tasks are complete, I then want the calling method to be aware of this and to call its own completion handler.
Below is a very simplified version of the logic for this:
class ViewController: UIViewController {
func doTasks(with object: Object, completionHandler: () -> Void) {
// Once A, B & C are done, then perform a task
wrapupTask()
// When task is complete, call completionHandler
completionHandler()
}
}
fileprivate extension ViewController {
func taskA(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
func taskB(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
func taskC(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
}
I could easily chain the handlers together, but then the task will likely take longer and the code will suck.
Another item I considered was a simple counter that incremented each time a task completed, and then once it hit 3, would then call the wrapupTask() via something like this:
var count: Int {
didSet {
if count == 3 {
wrapupTask()
}
}
}
Another option I have considered is to create an operation queue, and to then load the tasks into it, with a dependency for when to run my wrap up task. Once the queue is empty, it will then call the completion handler. However, this seems like more work than I'd prefer for what I want to accomplish.
My hope is that there is something better that I am just missing.
Just to pick up on what OOPer said, you are looking for DispatchGroup. In the following, the calls to taskA, taskB, and taskC are pseudo-code, but everything else is real:
func doTasks(with object: Object, completionHandler: () -> Void) {
let group = DispatchGroup()
group.enter()
taskA() {
// completion handler
group.leave()
}
group.enter()
taskB() {
// completion handler
group.leave()
}
group.enter()
taskC() {
// completion handler
group.leave()
}
group.notify(queue: DispatchQueue.main) {
// this won't happen until all three tasks have finished their completion handlers
completionHandler()
}
}
Every enter is matched by a leave at the end of the asynchronous completion handler, and only when all the matches have actually executed do we proceed to the notify completion handler.
How do I run an asynchronous thread that only runs as long as the view that uses it is presented?
I want the view to run this asynchronous thread. However, as soon as the view disappears, I want that thread to stop running. What's the best way to do this? I'm not sure where to start and might be thinking about this the wrong way. Nevertheless, what I described is how I want it to behave to the user.
You can use NSOperation to achieve what you want, NSOperation and NSOperationQueue are built on top of GCD. As a very general rule, Apple recommends using the highest-level abstraction, and then dropping down to lower levels when measurements show they are needed.
For example, You want to download images asynchronously when the view is loaded and cancel the task when the view is disappeared. First create a ImageDownloader object subclass to NSOperation. Notice that we check if the operation is cancelled twice, this is because the NSOperation has 3 states: isReady -> isExecuting -> isFinish and when the operation starts executing, it won't be cancelled automatically, we need to do it ourself.
class ImageDownloader: NSOperation {
//1
var photoRecord: NSURL = NSURL(string: "fortest")!
//2
init(photoRecord: NSURL) {
self.photoRecord = photoRecord
}
//3
override func main() {
//4
if self.cancelled {
return
}
//5
let imageData = NSData(contentsOfURL:self.photoRecord)
//6
if self.cancelled {
return
}
}
}
Then you can use it like: downloader.cancel(), downloader.start(). Notice that we need to check if the operation is cancelled in the completion block.
import UIKit
class ViewController: UIViewController {
let downloder = ImageDownloader(photoRecord: NSURL(string: "test")!)
override func viewDidLoad() {
super.viewDidLoad()
downloder.completionBlock = {
if self.downloder.cancelled {
return
}
print("image downloaded")
}
//Start the task when the view is loaded
downloder.start()
}
override func viewWillDisappear(animated: Bool) {
//Cancel the task when the view will disappear
downloder.cancel()
}
}
Once DetailViewController is presented, the asyncOperation method will be executed asynchronously.
Note: currently the asyncOperation method is executed every second so if you want the method to be called only once, you must change the repeats property to false.
class DetailViewController: UIViewController {
// timer that will execute
// asynchronously an operation
var timer: NSTimer!
// counter used in the async operation.
var counter = 0
// when view is about to appear
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// setting up the timer
timer = NSTimer.scheduledTimerWithTimeInterval(
1.0,
target: self,
selector: #selector(asyncOperation),
userInfo: nil,
repeats: true //set up false if you don't want the operation repeats its execution.
)
}
// when view is about to disappear
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
// stopping the timer
timer.invalidate()
}
// async operation that will
// be executed
func asyncOperation() {
counter += 1
print("counter: \(counter)")
}
}
Source: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/
Result:
I am currently playing around with Grand Central Dispatch and discovered a class called DispatchWorkItem. The documentation seems a little incomplete so I am not sure about using it the right way. I created the following snippet and expected something different. I expected that the item will be cancelled after calling cancel on it. But the iteration continues for some reason. Any ideas what I am doing wrong? The code seems fine for me.
#IBAction func testDispatchItems() {
let queue = DispatchQueue.global(attributes:.qosUserInitiated)
let item = DispatchWorkItem { [weak self] in
for i in 0...10000000 {
print(i)
self?.heavyWork()
}
}
queue.async(execute: item)
queue.after(walltime: .now() + 2) {
item.cancel()
}
}
GCD does not perform preemptive cancelations. So, to stop a work item that has already started, you have to test for cancelations yourself. In Swift, cancel the DispatchWorkItem. In Objective-C, call dispatch_block_cancel on the block you created with dispatch_block_create. You can then test to see if was canceled or not with isCancelled in Swift (known as dispatch_block_testcancel in Objective-C).
func testDispatchItems() {
let queue = DispatchQueue.global()
var item: DispatchWorkItem?
// create work item
item = DispatchWorkItem { [weak self] in
for i in 0 ... 10_000_000 {
if item?.isCancelled ?? true { break }
print(i)
self?.heavyWork()
}
item = nil // resolve strong reference cycle of the `DispatchWorkItem`
}
// start it
queue.async(execute: item!)
// after five seconds, stop it if it hasn't already
queue.asyncAfter(deadline: .now() + 5) {
item?.cancel()
item = nil
}
}
Or, in Objective-C:
- (void)testDispatchItem {
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
static dispatch_block_t block = nil; // either static or property
__weak typeof(self) weakSelf = self;
block = dispatch_block_create(0, ^{
for (long i = 0; i < 10000000; i++) {
if (dispatch_block_testcancel(block)) { break; }
NSLog(#"%ld", i);
[weakSelf heavyWork];
}
block = nil;
});
// start it
dispatch_async(queue, block);
// after five seconds, stop it if it hasn't already
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (block) { dispatch_block_cancel(block); }
});
}
There is no asynchronous API where calling a "Cancel" method will cancel a running operation. In every single case, a "Cancel" method will do something so the operation can find out whether it is cancelled, and the operation must check this from time to time and then stop doing more work by itself.
I don't know the API in question, but typically it would be something like
for i in 0...10000000 {
if (self?.cancelled)
break;
print(i)
self?.heavyWork()
}
DispatchWorkItem without DispatchQueue
let workItem = DispatchWorkItem{
//write youre code here
}
workItem.cancel()// For Stop
DispatchWorkItem with DispatchQueue
let workItem = DispatchWorkItem{
//write youre code here
}
DispatchQueue.main.async(execute: workItem)
workItem.cancel()// For Stop
Execute
workItem.perform()// For Execute
workItem.wait()// For Delay Execute
I want to enhance the code below: when i click the "submitData" button, the added code should cancel the completion handler.
func returnUserData(completion:(result:String)->Void){
for index in 1...10000 {
print("\(index) times 5 is \(index * 5)")
}
completion(result: "END");
}
func test(){
self.returnUserData({(result)->() in
print("OK")
})
}
#IBAction func submintData(sender: AnyObject) {
self.performSegueWithIdentifier("TestView", sender: self)
}
Can you tell me how to do this?
You can use NSOperation subclass for this. Put your calculation inside the main method, but periodically check cancelled, and if so, break out of the calculation.
For example:
class TimeConsumingOperation : NSOperation {
var completion: (String) -> ()
init(completion: (String) -> ()) {
self.completion = completion
super.init()
}
override func main() {
for index in 1...100_000 {
print("\(index) times 5 is \(index * 5)")
if cancelled { break }
}
if cancelled {
completion("cancelled")
} else {
completion("finished successfully")
}
}
}
Then you can add the operation to an operation queue:
let queue = NSOperationQueue()
let operation = TimeConsumingOperation { (result) -> () in
print(result)
}
queue.addOperation(operation)
And, you can cancel that whenever you want:
operation.cancel()
This is, admittedly, a fairly contrived example, but it shows how you can cancel your time consuming calculation.
Many asynchronous patterns have their built-in cancelation logic, eliminating the need for the overhead of an NSOperation subclass. If you are trying to cancel something that already supports cancelation logic (e.g. NSURLSession, CLGeocoder, etc.), you don't have to go through this work. But if you're really trying to cancel your own algorithm, the NSOperation subclass handles this quite gracefully.