How to Terminate loop/thread - objective-c

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.

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")
}
}
}

NSDocument saveDocumentWithDelegate deadlocked during App termination

NSDocument continues to be a software maintenance nightmare.
Anyone else having a problem where they want certain blocking dialogs to be handled SYNCHRONOUSLY?
BEGIN EDIT: I may have found a solution that allows me to wait synchronously
Can anyone verify that this would be an "Apple approved" solution?
static BOOL sWaitingForDidSaveModally = NO;
BOOL gWaitingForDidSaveCallback = NO; // NSDocument dialog calls didSave: when done
...
gWaitingForDidSaveCallback = true;
[toDocument saveDocumentWithDelegate:self
didSaveSelector:#selector(document:didSave:contextInfo:)
contextInfo:nil];
if ( gWaitingForDidSaveCallback )
{
// first, dispatch any other potential alerts synchronously
while ( gWaitingForDidSaveCallback && [NSApp modalWindow] )
[NSApp runModalForWindow: [NSApp modalWindow]];
if ( gWaitingForDidSaveCallback )
{
sWaitingForDidSaveModally = YES;
[NSApp runModalForWindow: [NSApp mbWindow]]; // mbWindow is our big (singleton) window
sWaitingForDidSaveModally = NO;
}
}
...
- (void)document:(NSDocument *)doc didSave:(BOOL)didSave contextInfo:(void *)contextInfo
{
[self recordLastSaveURL];
gWaitingForDidSaveCallback = NO;
if ( sWaitingForDidSaveModally )
[NSApp stopModal];
}
END EDIT
I have to support Snow Leopard/Lion/ML
App termination is an ugly process.
When the user decides to quit, and the document has changes that need saving, I call this:
gWaitingForDidSaveCallback = true;
[toDocument saveDocumentWithDelegate:self
didSaveSelector:#selector(document:didSave:contextInfo:)
contextInfo:nil];
I really really really want this call to be synchronous, but in latest Lion, this hangs my app:
while ( gWaitingForDidSaveCallback )
{
// didSave: callback clears sWaitingForDidSaveCallback
// do my own synchronous wait for now
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceReferenceDate:0.05]];
}
My best guess for the hang is that the mouseDown: of a window close button
is confusing the NSDocument.
So now, I have to return, and pepper my apps main loop with unmaintainable state machine logic to prevent user from executing various dangerous hotkeys.
Ok, so I grin and bear it, and run into yet another roadblock!
In previous OS versions/SDKs, [NSApp modalWindow] would return a window when it
was in this state. Now it doesn't! Grrrrr...
NSDocument has no API to test when it is in this state!
So, now there is no mechanism to globally check this state!
I have to add yet another state variable to my state machine.
Anyone have a cleaner solution for this problem that works in all OS versions and all present (and future) SDKs?
The better way is to save unsaved documents in chain. It is very easy:
// Catch application terminate event
-(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
NSDocumentController *dc = [NSDocumentController sharedDocumentController];
for (NSInteger i = 0; i < [[dc documents] count]; i++)
{
Document *doc = [[dc documents] objectAtIndex:i];
if ([doc isDocumentEdited])
{
// Save first unsaved document
[doc saveDocumentWithDelegate:self
didSaveSelector:#selector(document:didSave:contextInfo:)
contextInfo:(__bridge void *)([NSNumber numberWithInteger:i + 1])]; // Next document
return NSTerminateLater; // Wait until last document in chain will be saved
}
}
return NSTerminateNow; // All documents are saved or there are no open documents. Terminate.
}
...
// Document saving finished
-(void)document:(NSDocument *)doc didSave:(BOOL)didSave contextInfo:(void *)contextInfo
{
if (didSave) // Save button pressed
{
NSDocumentController *dc = [NSDocumentController sharedDocumentController];
NSInteger nextIndex = [(__bridge NSNumber *)contextInfo integerValue];
for (NSInteger i = nextIndex; i < [[dc documents] count]; i++)
{
Document *doc = [[dc documents] objectAtIndex:nextIndex];
if ([doc isDocumentEdited])
{
// Save next unsaved document
[doc saveDocumentWithDelegate:self
didSaveSelector:#selector(document:didSave:contextInfo:)
contextInfo:(__bridge void *)([NSNumber numberWithInteger:nextIndex + 1])]; // Next document
return;
}
}
[NSApp replyToApplicationShouldTerminate:YES]; // All documents saved. Terminate.
}
else [NSApp replyToApplicationShouldTerminate:NO]; // Saving canceled. Terminate canceled.
}
Maybe this answer is too late to be useful but... In one of my apps I implemented -(IBAction)terminate:(id)sender in my NSApplication derived class which would conditionally call [super terminate] to actually close the application only if all open documents were cleanly saved. I may have found some of this in the Apple docs or other examples.
The terminate override will go through each document and either close it (because it's saved), or call the document's canCloseDocumentWithDelegate method in the NSDocument derived class passing 'self' and 'terminate' as the didSaveSelector. Since the terminate method falls through and does nothing except make the document present an NSAlert, the alert in the document class will callback and re-run the terminate routine if the user clicks YES or NO. If all documents are clean, the app will terminate since [super terminate] will get called. If any more dirty documents exist, the process repeats.
For example:
#interface MyApplication : NSApplication
#end
#implementation MyApplication
- (IBAction)terminate:(id)sender
{
//Loop through and find any unsaved document to warn the user about.
//Close any saved documents along the way.
NSDocument *docWarn = NULL;
NSArray *documents = [[NSDocumentController sharedDocumentController] documents];
for(int i = 0; i < [documents count]; i++)
{
NSDocument *doc = [documents objectAtIndex:i];
if([doc isDocumentEdited])
{
if(docWarn == NULL || [[doc windowForSheet] isKeyWindow])
docWarn = doc;
}
else
{
//close any document that doesn't need saving. this will
//also close anything that was dirty that the user answered
//NO to on the previous call to this routine which triggered
//a save prompt.
[doc close];
}
}
if(docWarn != NULL)
{
[[docWarn windowForSheet] orderFront:self];
[[docWarn windowForSheet] becomeFirstResponder];
[docWarn canCloseDocumentWithDelegate:self shouldCloseSelector:#selector(terminate:) contextInfo:NULL];
}
else
{
[super terminate:sender];
}
}
#end
Later in the document derived class:
typedef struct {
void * delegate;
SEL shouldCloseSelector;
void *contextInfo;
} CanCloseAlertContext;
#interface MyDocument : NSDocument
#end
#implementation MyDocument
- (void)canCloseDocumentWithDelegate:(id)inDelegate shouldCloseSelector:(SEL)inShouldCloseSelector contextInfo:(void *)inContextInfo
{
// This method may or may not have to actually present the alert sheet.
if (![self isDocumentEdited])
{
// There's nothing to do. Tell the delegate to continue with the close.
if (inShouldCloseSelector)
{
void (*callback)(id, SEL, NSDocument *, BOOL, void *) = (void (*)(id, SEL, NSDocument *, BOOL, void *))objc_msgSend;
(callback)(inDelegate, inShouldCloseSelector, self, YES, inContextInfo);
}
}
else
{
NSWindow *documentWindow = [self windowForSheet];
// Create a record of the context in which the panel is being
// shown, so we can finish up when it's dismissed.
CanCloseAlertContext *closeAlertContext = malloc(sizeof(CanCloseAlertContext));
closeAlertContext->delegate = (__bridge void *)inDelegate;
closeAlertContext->shouldCloseSelector = inShouldCloseSelector;
closeAlertContext->contextInfo = inContextInfo;
// Present a "save changes?" alert as a document-modal sheet.
[documentWindow makeKeyAndOrderFront:nil];
NSBeginAlertSheet(#"Would you like to save your changes?", #"Yes", #"Cancel", #"No", documentWindow, self,
#selector(canCloseAlertSheet:didEndAndReturn:withContextInfo:), NULL, closeAlertContext, #"%");
}
}
- (void)canCloseAlertSheet:(NSWindow *)inAlertSheet didEndAndReturn:(int)inReturnCode withContextInfo:(void *)inContextInfo
{
CanCloseAlertContext *canCloseAlertContext = inContextInfo;
void (*callback)(id, SEL, NSDocument *, BOOL, void* ) = (void (*)(id, SEL, NSDocument *, BOOL, void* ))objc_msgSend;
if (inAlertSheet) [inAlertSheet orderOut:self];
// The user's dismissed our "save changes?" alert sheet. What happens next depends on how the dismissal was done.
if (inReturnCode==NSAlertAlternateReturn)
{
//Cancel - do nothing.
}
else if (inReturnCode==NSAlertDefaultReturn)
{
//Yes - save the current document
[self saveDocumentWithDelegate:(__bridge id)canCloseAlertContext->delegate
didSaveSelector:canCloseAlertContext->shouldCloseSelector contextInfo:canCloseAlertContext->contextInfo];
}
else
{
// No - just clear the dirty flag and post a message to
// re-call the shouldCloseSelector. This should be
// the app:terminate routine.
[self clearDirtyFlag];
if (canCloseAlertContext->shouldCloseSelector)
{
(callback)((__bridge id)canCloseAlertContext->delegate,
canCloseAlertContext->shouldCloseSelector, self, YES, canCloseAlertContext->contextInfo);
}
}
// Free up the memory that was allocated in -canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo:.
free(canCloseAlertContext);
}
#end
And that should do it - No loops... no waiting...

Disable buffering of repeated button clicks

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

Pausing iteration of a for loop to wait for user input

I wrote a for loop which is iterating through an array of objects.
Now I am asking myself if it's possible to break the iteration of the loop until the user clicks on a button which calls a IBAction?
for (int i = 0; i < [array count]; i++) {
// do something with the object
// wait for action method called
// user clicked action so go on
}
You can adapt the code to fit your case. It basically "unrolls" the loop into multiple messages. Start the sequence with [self doItForIndex:[NSNumber numberWithInt:0]];
- (BOOL)canDoitForIndex:(NSNumber *)i {
// return YES if you want to go ahead
// (e.g. test a BOOL you set in response to the user tapping a button
}
- (void)waitForIndex:(NSNumber *)i {
if ([self canDoItForIndex:i]) {
// do anything to clean up for i
// then repeat for i+1:
[self doItForIndex:[NSNumber numberWithInt:[i intValue]+1]];
} else {
[self performSelector:_cmd withObject:i afterDelay:0.01f;
}
}
- (void)doItForIndex:(NSNumber *)i {
if ([i intValue] < lastIndex) {
// do what you have to do
[self waitForIndex:i];
}
// else you're done
}
Apple's NSRunLoop concept expects you to complete processing pretty quickly. If you tie up the main thread by waiting for something, nothing else in your app can happen. The above code breaks the "wait" into multiple message sends, and keeps your app responsive.
ODRM algorithm works very well.
I just changed this line :
[self performSelector:_cmd withObject:i afterDelay:0.01f];
with this :
[NSThread sleepForTimeInterval:0.25];
[NSThread detachNewThreadSelector:_cmd toTarget:self withObject:i];
As I had UI elements to be updated, it was better for we to force waiting to be in a background thread.