How do I cancel a completion handler? - swift

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.

Related

Using GCD how can a DispatchGroup have items run on the main queue from a background queue before (or just after) the group leaves, without deadlock?

Given some class Foo that calls Bar on a background thread to perform some work, how can Bar set some work to be done on the main thread without causing a deadlock, before the enclosing function returns the value it needs to return?
E.g. how can "asdf" print before "done" prints, before true is returned from func done() -> Bool in the example below?
import Dispatch
class Foo {
/// always called from the main queue
func done() -> Bool {
let group = DispatchGroup()
group.enter()
DispatchQueue.global().async {
Bar().perform {
DispatchQueue.main.async { print("asdf") }
// "asdf" prints after "done" is printed
group.leave()
}
}
group.wait()
print("done")
return true
}
}
where Bar is simply:
struct Bar {
func perform(_ work: #escaping () -> Void) { work() }
}
I need Bar to be able to set some work that should be executed on the main queue before done() returns, without causing a deadlock (as would happen if we changed the above perform block to use DispatchQueue.main.sync, and (assuming done() is always called on the main thread, which it is).
The only solution I could come up with to print "asdf" before we print "done" is to change the done() function as follows:
func done() -> Bool {
let group1 = DispatchGroup()
var completion: (() -> Void)? = nil
group1.enter()
DispatchQueue.global().async(group: group1) {
Bar().perform {
completion = { print("asdf") }
group1.leave()
}
}
group1.wait()
completion?()
print("done")
return true
}
Here the completion block runs on the main thread before the function returns. However this feels kludgy and hacky... seems like something that GCD should just handle for us. But everything else I've tried will run after the function returns.
Thoughts?
The main queue under GCD is not re-entrant, and you cannot stop done from returning immediately except by blocking the main thread, nor (therefore) can you return a value from done after the first async method.
So, as you've been told in a comment, what you're trying to do is impossible.
You can call back into the main thread from inside the Bar completion handler, by way (for example) of another completion handler; but you cannot "wait" the way you are trying to do.
So for example:
struct Bar {
func perform(_ work: #escaping () -> Void) {
print("work")
work()
}
}
class Foo {
func done(_ f: #escaping (Bool) -> ()) {
DispatchQueue.global().async {
Bar().perform {
DispatchQueue.main.async {
// adjust the order of these two lines as desired
print("asdf")
f(true)
}
}
}
}
}
let f = Foo()
f.done { what in
print(what)
print("done")
}
Output:
work
asdf
true
done
Maybe that might work for you:
struct Bar {
func perform(work: #escaping () -> Void, completion: #escaping () -> Void) {
onBackGround(work) {
DispatchQueue.main.async {
print("asdf")
completion()
}
}
}
func done() {
print("done")
}
}
Call it:
bar = ..
bar.perform(work) {
self.done()
}

Async data loading swift

I got a function such as scrollViewDidScroll that can trigger many times. And I need to call function loadMoreDataFromRemoteServerIfNeed only single time. How could I do this more elegantly without using any "flag" variables. Maybe I should use DispathGroup|DispatchWorkItem?
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yOffset = scrollView.contentOffset.y
if yOffset > offset {
loadMoreDataFromRemoteServerIfNeed()
}
}
func loadMoreDataFromRemoteServerIfNeed() {
DispatchQueue.global(qos: .background).async {
sleep(2)
DispatchQueue.main.async {
// <Insert New Data>
}
}
}
The thing that you are trying to describe — "Do this, but only if you are not told to do it again any time in the next 2 seconds" — has a name. It's called debouncing. This is a well-solved problem in iOS programming, so now that you know its name, you can do a search and find some of the solutions.
While I'm here telling you about this, here's a solution you might not know about. Debouncing is now built in to iOS functionality! Starting in iOS 13, it's part of the Combine framework. I'm now using Combine all over the place: instead of notifications, instead of GCD, instead of Timer objects, etc. It's great!
Here's a Combine-based solution to this type of problem. Instead of a scroll view, suppose we have a button hooked up to an action handler, and we don't want the action handler to do its task unless 2 seconds has elapsed since the last time the user tapped the button:
var pipeline : AnyCancellable?
let pipelineStart = PassthroughSubject<Void,Never>()
#IBAction func doButton(_ sender: Any) {
if self.pipeline == nil {
self.pipeline = pipelineStart
.debounce(for: .seconds(2), scheduler: DispatchQueue.main)
.sink { [weak self] _ in self?.doSomething() }
}
self.pipelineStart.send()
}
func doSomething() {
print("I did it!")
}
I'm sure you can readily see how to adapt that to your own use case:
var pipeline : AnyCancellable?
let pipelineStart = PassthroughSubject<Void,Never>()
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yOffset = scrollView.contentOffset.y
if yOffset > offset {
if self.pipeline == nil {
self.pipeline = pipelineStart
.debounce(for: .seconds(2), scheduler: DispatchQueue.main)
.sink { [weak self] _ in self?.loadMoreDataFromRemoteServerIfNeed()
}
self.pipelineStart.send()
}
}
func loadMoreDataFromRemoteServerIfNeed() {
// <Insert New Data>
}
You can create a flag from DispatchWorkItem to observe loading state e.g.:
var item: DispatchWorkItem?
func loadMoreDataFromRemoteServerIfNeed() {
assert(Thread.isMainThread)
guard item == nil else { return }
item = DispatchWorkItem {
print("loading items")
Thread.sleep(forTimeInterval: 2)
DispatchQueue.main.async {
item = nil
print("insert items")
}
}
DispatchQueue.global().async(execute: item!)
}
NOTE: to synchronise item var you must change its value on the same thread for instance the main thread.
Yes, you could use DispatchWorkItem, keep a reference to the old one, and cancel prior one if necessary. If you were going to do that, I might consider Operation, too, as that handles cancelation even more gracefully and has other advantages.
But that having been said, given that the work that you are dispatching is immediately sleeping for two seconds, this might suggest a completely different pattern, namely a Timer. You can schedule your timer, invalidating previously scheduled timers, if any:
weak var timer: Timer?
func loadMoreDataFromRemoteServerIfNeed() {
// cancel old timer if any
timer?.invalidate()
// schedule what you want to do in 2 seconds
timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
// <Insert New Data>
}
}
FWIW, if you ever find yourself sleeping, you should general consider either timers or asyncAfter. This avoids tying up the global queue’s worker thread. Sleeping is an inefficient pattern.
In this case, keeping a weak reference to the prior timer (if any) is probably the best pattern.

Proper use of Dispatch to show activity indicator during a long task

During a computationally intensive task, I wish to show the user an activity indicator. What is the best way to do this?
My task (contrived of course), lasts a couple of seconds:
func startThinking(howMany: Int) {
for i in 0...howMany {
let p:Double = Double(i)
let _ = p / Double.pi
}
delegate?.finishedThinking()
}
This is called on a button tap:
#IBAction func startTap(_ sender: Any) {
Thinker.sharedInstance.startThinking(howMany: 500000000)
myActivity.startAnimating()
}
And stopped when the thinking task is finished:
func finishedThinking() {
print ("finished thinking")
myActivity.stopAnimating()
}
But the activity indicator is not showing up; the UI is blocked by the difficult thinking task.
I've tried putting the startAnimating on the main thread:
DispatchQueue.main.async {
self.myActivity.startAnimating()
}
or the difficult task onto its own thread:
DispatchQueue.global().async {
Thinker.sharedInstance.startThinking(howMany: 500000000)
}
and various other combinations that I've run across in Stack. What am I doing wrong?
Firstly, I would move the call to start animating to before the thinker call, and verify that it works if you don't start thinking. You also need to stop the animation from the main thread.
#IBAction func startTap(_ sender: Any) {
myActivity.startAnimating()
DispatchQueue.global(qos: .userInitiated).async {
Thinker.sharedInstance.startThinking(howMany: 500000000)
}
}
func finishedThinking() {
DispatchQueue.main.async {
myActivity.stopAnimating()
}
}
I adjusted a few things:
moved the .startAnimating() call to be first. It is already on the main thread since it was called from the interface
specify the qos as .userInitiated
run the .stopAnimating() on the main thread

Swift loop closure function, then completion [duplicate]

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.

rxswift error handle issue

I have a BehaviorSubject named createObservable in my view model. And my view controller subscribe it.
viewModel!.createObservable.subscribe(onNext: {[unowned self] (obj:PassbookModelType?) -> Void in
if let _ = obj{
self.dismissVC()
}
}, onError: { (error) -> Void in
print(error)
}).addDisposableTo(self.dispose)
I have a function named saveObject() also in the view model. If I click the navigation bar right item it will be emitted. And there is an error will send to createObservable's observer.
func saveObject(){
```````
```````
if condition {
createObservable.on(Event.Next(model))
createObservable.onCompleted()
}else{
createObservable.onError(MyError.someError)
}
}
The problem is that if the error happened the createObservable will be closed, so I won't receive any Next event in the future. I tried to use retry(), but it seems will cause deadlock, view controller can't response any touch event any more. So can some one tell me how to fix this issue? Thanks a lot
viewModel!.createObservable.retry().subscribe(onNext: {[unowned self] (obj:PassbookModelType?) -> Void in
if let _ = obj{
self.dismissVC()
}
}, onError: { (error) -> Void in
print(error)
}).addDisposableTo(self.dispose)
I suggest to make the type of createObservable PublishSubject<Observable<PassbookModelType>>, instead of BehaviorSubject<PassbookModelType?> which, I guess, accidentally flattens two Rx streams conceptually separatable each other: the saveObject process itself (an one-shot process) and starting the saveObject process initiated by user action repeatedly. I've written a short example to demonstrate it.
let createObservable = PublishSubject<Observable<Int>>()
override func viewDidLoad() {
super.viewDidLoad()
createObservable.flatMap {
$0.map { obj in
print("success: \(obj)")
}
.catchError { err in
print("failure: \(err)")
return empty()
}
}.subscribe()
}
// Simulates an asynchronous proccess to succeed.
#IBAction func testSuccess(sender: UIView!) {
let oneShot = PublishSubject<Int>()
createObservable.onNext(oneShot)
callbackAfter3sec { res in
oneShot.onNext(1)
oneShot.onCompleted()
}
}
// Simulates an asynchronous process to fail.
#IBAction func testFailure(sender: UIView!) {
let oneShot = PublishSubject<Int>()
createObservable.onNext(oneShot)
callbackAfter3sec { res in
oneShot.onError(NSError(domain: "Error", code: 1, userInfo: nil))
}
}
func callbackAfter3sec(completion: Int -> ()) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC * 3)), dispatch_get_main_queue()) {
completion(2)
}
}
There is an important merit with that: If the one-shot process would become in the Rx style (for example, like as callbackAfter3sec() -> Observable<Int>) in the future, there were no need to re-write the use-side code like in the viewDidLoad above. There is an only one change to do is to pass an Observable<> object to createObservable.onNext(...).
Sorry for my poor English skill. I hope this makes sense to you.