Disable buffering of repeated button clicks - objective-c

When the user clicks a button, an action is started but if the user quickly clicks the button 10 times, it will execute 10 times. I guess the disable doesn't take effect until control returns from the event.
- (IBAction)btnQuickCheckClick:(id)sender {
#try {
self.btnQuickCheck.enabled = NO ;
// Next line takes about 3 seconds to execute:
[self pollRouter] ;
}
#finally {
self.btnQuickCheck.enabled = YES ;
}
}

You can update the UI by running the run loop after disabling the button before polling:
- (IBAction)btnQuickCheckClick:(id)sender {
self.btnQuickCheck.enabled = NO;
// give some time for the update to take place
[self performSelector:#selector(pollRouterMethod) withObject:nil afterDelay:0.1];
}
- (void)pollRouterMethod {
#try {
[self pollRouter];
} #catch (NSException * e) { }
// re-enable the button
self.btnQuickCheck.enabled = YES;
}
Of course, this method is no substitute for running a time intensive task on another thread. For long tasks, multithreading is almost always the way to go.

another way to do this is with blocks :
Big Pro : you don't need to create an extra method :)
- (IBAction)btnQuickCheckClick:(id)sender {
//UI changes must be done in the main thread
self.btnQuickCheck.enabled = NO;
//do your thing in a background thread
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT ,0);
dispatch_async(queue, ^(){
#try {
//do your thing here
[self pollRouter];
} #catch (NSException * e) {
//handle the exception, if needed
} #finally {
//change to the main thread again and re-enable the UI
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^(){
self.btnQuickCheck.enabled = YES;
});
}
});
}
This will run pollRouter in a background thread. So if you are not modifying the UI or other non thread safe things in there you want to use this approach :) Otherwise go for #Alex's approach

Related

Update UITextView during recursive function is running

I have a view controller with a UITextView loaded into the frame. I want to update the text of the UITextView every time the function calls itself.
I attempt to update the UITextView on the main thread but it doesn't seem to set the text of the View UNTIL after the recursive function is done running.
'Maze.h' is the object that defines the protocol and 'MainViewController.m' is the delegate.
Heres the code for the controller:
'MainViewController.m'
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
Maze *aMaze = [[Maze alloc] initWithMaze:#"Maze.txt" andUpdate:true everyXSeconds:1];
[aMaze setDelegate:self];
if (![aMaze parseFile]) {
exit(2);
}
if ([aMaze solve:-1 y:-1 z:-1]){
NSLog(#"%#", [aMaze printMazeHorizontally]);
NSLog(#"Escapable: %#", [aMaze getMoveSequence]);
} else {
NSLog(#"Unescapable");
exit(1);
}
}
- (void)didMakeMove:(NSString *)maze {
NSLog(#"%#", maze);
dispatch_async(dispatch_get_main_queue(), ^{
[self.maze setText:maze];
});
}
'Maze.m'
- (BOOL)solve:(NSInteger)x y:(NSInteger)y z:(NSInteger)z
{
...
...
[self.delegate didMakeMove:self.printMazeVertically];
...
...
}
The UITextView just doesn't seem to update until -(BOOL)solve::: is done running. Which only updates once instead of multiple times.
Not sure why this is happening.
Any ideas on how to update the UITextView?
I thought that updating the UI should be done on the main thread?
Solution:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([aMaze solve:-1 y:-1 z:-1]){
NSLog(#"%#", [aMaze printMazeHorizontally]);
NSLog(#"Escapable: %#", [aMaze getMoveSequence]);
} else {
NSLog(#"Unescapable");
exit(1);
}
});
Drawing is performed later in the runloop run, or on the next runloop run. Thus, if you block the main thread while your recursion is running, UI will not update until after you end your recursion.
Consider changing your design. Move the taxing recursion to a background thread, and update the UI using GCD.

"disable" button-->method until operation is done

I am using the following method that invoked by pressing a button thru sprite builder.
- (void)method {
//static dispatch_once_t pred; //
//dispatch_once(&pred, ^{ // run only once code below
[self performSelector:#selector(aaa) withObject:nil afterDelay:0.f];
[self performSelector:#selector(bbb) withObject:nil afterDelay:1.f];
[self performSelector:#selector(ccc) withObject:nil afterDelay:1.5f];
[self performSelector:#selector(ddd) withObject:nil afterDelay:4.f];
[self performSelector:#selector(eee) withObject:nil afterDelay:4.5f];
CCLOG(#"Received a touch");
//}); //run only once code above
}
as you can see from the comments i tried running it once. that works good, but if a user comes back to this scene, it's disabled until you restart the app.
how can i block this method from being executed a second time until the first time is done.
i know the code is rough, i'm just learning here....
thanks in advance.
Add a BOOL instance variable which serves as a flag as to whether or not this action is taking place. As soon as the method starts, check the flag. If you need to execute, set the flag.
Add another performSelector:withObject:afterDelay: which calls a method to reset the flag back.
#implementation SomeClass {
BOOL _onceAtATime;
}
- (void)method {
#synchronized(self) {
if (!_onceAtATime) {
_onceAtATime = YES;
// do all the stuff you need to do
[self performSelector:#selector(resetOnceAtATime)
withObject:nil
afterDelay:delay];
// where delay is sufficiently long enough for all the code you
// are executing to complete
}
}
}
- (void)resetOnceAtATime {
_onceAtATime = NO;
}
#end
A simpler way would be to use a serial NSOperationQueue as such (in Swift):
class ViewController: UIViewController {
let queue: NSOperationQueue
required init(coder aDecoder: NSCoder) {
queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 1
super.init(coder: aDecoder)
}
#IBAction func go(sender: AnyObject) {
if (queue.operationCount == 0) {
queue.addOperationWithBlock() {
// do the first slow thing here
}
queue.addOperationWithBlock() {
// and the next slow thing here
}
// ..and so on
}
else {
NSLog("busy doing those things")
}
}
}

Wait for two async methods to complete

I'd like to init a model, let the model do some async stuff and present a new viewcontroller once completed. But how do i wait for the two async methods to be completed and how do I setup the callback method?
Pseudocode
In my StartViewController.m:
-(void)openArticle
{
article = [Article initWithObject:someObject];
article.callback = changeView;
}
-(void)changeView
{
[self presentViewController:someController];
}
In my ArticleModel.m:
-(void)initWithObject:someObject
{
[self loadImage]
[self geoCode]
}
-(void)loadImage
{
runAsyncMethod: success:^() // This one is actually a AFNetworking setImageWithURLRequest
}
-(void)geoCode
{
runAnotherAsyncMethod: success:^() // This one is actually a geocodeAddressString operation
}
You can achieve this using dispatch_groups
- (void)initWithObject:(id)someObject
{
self = [super init];
if (self) {
self.dispatch_group = dispatch_group_create();
[self loadImage]
[self geoCode]
dispatch_group_notify(self.dispatch_group, dispatch_get_main_queue(), ^{
NSLog(#"Push new view controller");
});
}
return self;
}
- (void)loadImage
{
dispatch_group_enter(self.dispatch_group);
__weak __typeof(self) weakSelf = self;
runAsyncMethod: success:^{
__typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.dispatch_group) {
dispatch_group_leave(strongSelf.dispatch_group); // You need to ensure that this is called in both success and failure
}
}
}
- (void)geoCode
{
dispatch_group_enter(self.dispatch_group);
__weak __typeof(self) weakSelf = self;
runAnotherAsyncMethod: success:^{
__typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.dispatch_group) {
dispatch_group_leave(strongSelf.dispatch_group);
}
}
}
You do not wait. If you wait, it isn't asynchronous! You would be losing the entire point of asynchronous if you were to wait.
What you do is, when your success handler is called, you step out to the main thread (just in case you got called back on a background thread) and now do whatever you need to do. In other words, you just let your success handler get called whenever it happens to get called.
In your case, you might like to chain the things you want to do:
Call loadImage
In its callback, call geoCode
In its callback, step out to the main thread and present the new view controller.
You can use dispatch_group so that when a method is over, it just leaves the group. I use a similar code myself and it works like a charm.
- (void)initWithObject:someObject {
// Create a dispatch group
dispatch_group_t group = dispatch_group_create();
[self loadImageWithDispatchGroup:group];
[self geoCodeWithDispatchGroup:group];
// Here we wait for all the requests to finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Do whatever you need to do when all requests are finished
});
}
- (void)loadImageWithDispatchGroup:(dispatch_group_t)group {
dispatch_group_enter(group);
runAsyncMethod: success:^() // This one is actually a AFNetworking setImageWithURLRequest
// In your success or failure AFNetworking method, call this as soon as the request ended
dispatch_group_leave(group);
}
- (void)geoCodeWithDispatchGroup:(dispatch_group_t)group {
dispatch_group_enter(group);
runAnotherAsyncMethod: success:^() // This one is actually a geocodeAddressString operation
// In your success async geocode callback method, call this as soon as the request ended
dispatch_group_leave(group);
}
I do not known your needs but native GCD way to wait several asynch tasks is
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_barrier_async

How to Terminate loop/thread

Here is part of my program. Loop in findDuplicates starts in background thread after button was pressed. Is there any way to stop/kill thread/loop by pressing another button?
- (IBAction)countDups:(id)sender {
[self performSelectorInBackground:#selector(findDuplicates) withObject:nil];
}
-(void)findDuplicates
{
...
for(int index=0;index<self.resultList.count;index++)
{ ... }
...
}
You can return from background thread. create one member variable, initialize it with NO.
- (IBAction)countDups:(id)sender {
mCancel = NO;
[self performSelectorInBackground:#selector(findDuplicates) withObject:nil];
}
-(IBAction)stop
{
mCancel = YES; //BOOL member variable;
}
-(void)findDuplicates
{
...
for(int index=0;index<self.resultList.count;index++)
{
If(mCancel)
return; // return for thread to end
... }
...
}
Read up in the Threading Programming Guide under Terminating a Thread. performSelectorInBackground:withObject: essentially creates a new thread and runs the code on it.

Interrupt execution of a method in objective C

I'm writing an objective C program. Below I have included a method that I'm running after clicking on a button in that program. Also there is a stop button in that program and when someone clicks on the stop button I wanna stop execution of this method and bring the UI of the application back to it's normal state which was before running the method. Can anyone help me to do this?
-(JobStatus)beginUploadingTask{
void (^progressBlock)(void);
progressBlock = ^{
#try{
do {
// calls to some method
dispatch_async(dispatch_get_main_queue(), ^{
//execute some code
});
} while (index<fileSize);
}
#catch (NSException *ex) {
[self taskErrorWithMessage:#"Error in uploading your file. Please try again"];
return;
}
#finally {
NSLog(# "finally block executed");
}
};
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, progressBlock);
return TaskStateUploaded;
}