Objective-C: Blocking a Thread until an NSTimer has completed (iOS) - objective-c

I've been searching for and attempting to program for myself, an answer to this question.
I've got a secondary thread running inside my mainView controller which is then running a timer which counts down to 0.
Whilst this timer is running the secondary thread which initiated the timer should be paused/blocked whatever.
When the timer reaches 0 the secondary thread should continue.
I've Experimented with both NSCondition and NSConditionLock with no avail, so id ideally like solutions that solve my problem with code, or point me to a guide on how to solve this. Not ones that simply state "Use X".
- (void)bettingInit {
bettingThread = [[NSThread alloc] initWithTarget:self selector:#selector(betting) object:nil];
[bettingThread start];
}
- (void)betting {
NSLog(#"betting Started");
for (int x = 0; x < [dealerNormalise count]; x++){
NSNumber *currSeat = [dealerNormalise objectAtIndex:x];
int currSeatint = [currSeat intValue];
NSString *currPlayerAction = [self getSeatInfo:currSeatint objectName:#"PlayerAction"];
if (currPlayerAction != #"FOLD"){
if (currPlayerAction == #"NULL"){
[inactivitySeconds removeAllObjects];
NSNumber *inactivitySecondsNumber = [NSNumber numberWithInt:10];
runLoop = [NSRunLoop currentRunLoop];
betLooper = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(betLoop) userInfo:nil repeats:YES];
[runLoop addTimer:[betLooper retain] forMode:NSDefaultRunLoopMode];
[runLoop run];
// This Thread needs to pause here, and wait for some input from the other thread, then continue on through the for loop
NSLog(#"Test");
}
}
}
}
- (void)threadKiller {
[betLooper invalidate];
//The input telling the thread to continue can alternatively come from here
return;
}
- (void)betLoop {
NSLog(#"BetLoop Started");
NSNumber *currentSeconds = [inactivitySeconds objectAtIndex:0];
int currentSecondsint = [currentSeconds intValue];
int newSecondsint = currentSecondsint - 1;
NSNumber *newSeconds = [NSNumber numberWithInt:newSecondsint];
[inactivitySeconds replaceObjectAtIndex:0 withObject:newSeconds];
inacTimer.text = [NSString stringWithFormat:#"Time: %d",newSecondsint];
if (newSecondsint == 0){
[self performSelector:#selector(threadKiller) onThread:bettingThread withObject:nil waitUntilDone:NO];
// The input going to the thread to continue should ideally come from here, or within the threadKiller void above
}
}

You can't run a timer on a thread and sleep the thread at the same time. You may want to reconsider whether you need a thread at all.
There's a few things that need to be pointed out here. First, when you schedule your timer:
betLooper = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(betLoop:)
userInfo:nil
repeats:YES];
it's added to and retained by the current run loop by that method, so you don't need to do that manually. Just [myRunLoop run]. Your timer's selector argument is also invalid -- a timer's "target method" needs to look like this:
- (void)timerFireMethod:(NSTimer *)tim;
This also means that you don't need to retain the timer if all you want to do is invalidate it, since you will have a reference to it from inside that method.
Second, it's not clear what you mean by "this thread needs to sleep to wait for input". When you schedule that timer, the method (betLoop) is called on the same thread. If you were to sleep the thread, the timer would stop too.
You seem to be a little mixed up regarding methods/threads. The method betting is running on your thread. It is not itself a thread, and it's possible to call other methods from betting that will also be on that thread. If you want a method to wait until another method has completed, you simply call the second method inside the first:
- (void)doSomethingThenWaitForAnotherMethodBeforeDoingOtherStuff {
// Do stuff...
[self methodWhichINeedToWaitFor];
// Continue...
}
I think you just want to let betting return; the run loop will keep the thread running, and as I said, the other methods you call from methods on the thread are also on the thread. Then, when you've done the countdown, call another method to do whatever work needs to be done (you can also invalidate the timer inside betLoop:), and finalize the thread:
- (void)takeCareOfBusiness {
// Do the things you were going to do in `betting`
// Make sure the run loop stops; invalidating the timer doesn't guarantee this
CFRunLoopStop(CFRunLoopGetCurrent());
return; // Thread ends now because it's not doing anything.
}
Finally, since the timer's method is on the same thread, you don't need to use performSelector:onThread:...; just call the method normally.
You should take a look at the Threading Programming Guide.
Also, don't forget to release the bettingThread object that you created.

NSThread has a class method + (void)sleepForTimeInterval:(NSTimeInterval)ti. Have a look at this :).
NSThread Class Reference

Related

Grand Central Dispatch Concurrency

Here's my scenario....
I have a Core MIDI app that detects Note On and Note Off messages which is working nicely.
I have have some midiSend methods that send messages back to the controller to illuminate LEDs - also working fine.
What I want to do now is on the Note Off message have the LED blink on and off. This is my code:
[midiListener performSelectorOnMainThread:#selector(startTimer:) withObject:midiMsgParts waitUntilDone:YES];
-(void)startTimer:(NSDictionary *)dict {
ledIntervalCount = 0;
ledIntervalTimer = [NSTimer scheduledTimerWithTimeInterval:0.3
target:self
selector:#selector(ledIntervalLoop:)
userInfo:dict
repeats:YES];
}
-(void)ledIntervalLoop:(NSTimer *)inboundTimer{
NSDictionary *userInfo = [NSDictionary dictionaryWithDictionary:[inboundTimer userInfo]];
NSLog(#"%#", userInfo);
UInt32 onCommand = [[userInfo objectForKey:#"noteOn"] intValue];
//UInt32 offCommand = [[userInfo objectForKey:#"noteOff"] intValue];
UInt32 theNote = [[userInfo objectForKey:#"note"] intValue];
ledIntervalCount++;
if (ledIntervalCount > 3) {
[ledIntervalTimer invalidate];
ledIntervalTimer = nil;
} else {
if(ledIntervalCount %2){
[self sendNoteOnIlluminate:onCommand midiNote:theNote];
}else{
[self sendNoteOnCommand:onCommand midiNote:theNote];
}
}
}
So I'm using an NSTimer to alternate the LED on/off commands. It works fine when I press a single button but not when I press multiple ones at the same time. It seems like it only picks on the last call to the startTimer method.
This is where I think I need to implement a dispatch queue with GCD. So that each NSTimer will execute in full without being interrupted by the method calls that follow.
Am I correct? Will GCD allow me to have the NSTimer run concurrently?
GCD is a new concept to me so some guidance on how I might implement it would help. I've read through some of the reference guides but need to see some example code in the context of my scenario. I guess what I'm asking here is, what part of my code would go in the block?
AH you invalidate the timers anyway... after 3 tries. ALL -- you need X counters for X timers, you have 1 counter for X timer
instead of one long ledIntervalCount, have a NSMutableArray with ledIntervalCounts! One per timer
then in the userInfo for the timer, provide the index to the counter that is to be used
The problem was that I was calling the class from within a method wrapped in an autorelease. I now run this on the main thread and it works fine.

Avoid blocking UI when doing a lot of operations that are required to be on main thread

I need to do a possibly long series of calls that must occur on the main thread (because otherwise UIKit will balk). By "long" I mean 10,000 operations lasting .1 second each on an iPad 3.
Obviously, It's probably not the best idea to just loop through all of them at once.
I don't know how to execute all these on the main thread while leaving enough breathing room to keep UIKit responsive and the watchdog asleep (ie. not get terminated for hogging the run loop).
Does anybody have an idea? I will be targeting iOS 5.
Specifically what I'm trying to do is cache UITextPositions, because a UITextView is apparently taking a non-cached, iterative approach at getting UITextPositions, which means it is very, very slow at doing positionFromPosition:textview.beginningOfDocument offset:600011, but much faster at getting positionFromPosition:aPositionAt600000 offset:11. In fact, in my test case, the former takes over 100 seconds (on the main thread!), while the latter is virtually instantaneous.
Why do you want to do it on the main thread? The typical answer is to do these operations on a background thread, and send UI updates back to the main thread. For example, you could use Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do my time consuming task and everytime it wants to update the UI,
// it should dispatch that back to the main queue, e.g.
for (NSInteger i = 0; i < 10000; i++)
{
// do my background work
// now update the UI
dispatch_async(dispatch_get_main_queue(), ^{
// update the UI accordingly
});
}
});
Update:
It sounds like you have to do this in the foreground, so perhaps using a NSTimer might be better. I'm not a big NSTimer guy, but it might look something like the following.
First, make sure you have a class instance variable for it:
NSTimer *_timer;
Next, you can initialize it with:
- (void)startTimer
{
_timer = [NSTimer timerWithTimeInterval:0.0 target:self selector:#selector(timerCallback:) userInfo:nil repeats:YES];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_timer forMode:NSDefaultRunLoopMode];
}
This will then invoke the timerCallback, perhaps processing a single UITextPosition on each invocation:
- (void)timerCallback:(NSTimer*)theTimer
{
BOOL moreTextPositionsToCalculate = ...;
if (moreTextPositionsToCalculate)
{
// calculate the next UITextPosition
}
else
{
[self stopTimer];
}
}
and when you're done, you could stop your timer like so:
- (void)stopTimer
{
[_timer invalidate];
_timer = nil;
}

How do I choose NSThread over NSTimer?

In other words, if I have a process that continuously runs, but users can change parameters on the GUI that effect the process operation characteristics, where is a better place to put the process, in a NSThread or NSTimer?
While NSThread and NSTimer are two separate things for different needs, lets compare the two functions:
Using NSThread:
-(void) doSomethingEverySecond {
__block int cumValue = 0; // cumulative value
__block void(^execBlock)() = ^{
while (1)
{
#try
{
// some code here that might either A: call continue to continue the loop,
// or B: throw an exception.
cumValue++;
NSLog(#"Cumulative Value is: %i", cumValue);
if (cumValue == 5)
return;
}
#finally
{
[NSThread sleepForTimeInterval:1];
}
}
};
[NSThread detachNewThreadSelector:#selector(invoke) toTarget:[execBlock copy] withObject:nil];
}
Using NSTimer:
-(void) doSomethingEverySecond {
__block NSTimer *timer = nil;
__block int cumValue = 0;
__block void (^execBlock)() = ^{
cumValue++;
NSLog(#"Cumulative Value is: %i", cumValue);
if (cumValue == 5)
[timer invalidate];
};
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[execBlock copy] selector:#selector(invoke) userInfo:nil repeats:YES];
}
Now, if we want to something only once, NSThread is the way to go, as shown in the following:
-(void) doSomethingOnce {
__block void (^execBlock)() = ^{
NSLog(#"Doing something that could take a LONG time!");
};
[NSThread detachNewThreadSelector:#selector(invoke) toTarget:[execBlock copy] withObject:nil];
}
Now, for the NSTimer variant:
-(void) doSomethingOnce {
__block void (^execBlock)() = ^{
NSLog(#"Doing something that could take a LONG time!");
};
[NSTimer scheduledTimerWithTimeInterval:0 target:[execBlock copy] selector:#selector(invoke) userInfo:nil repeats:NO];
}
The reason for this is that we have complete control over the thread while using a NSThread, but if using a NSTimer, than we are executing inside a NSRunLoop which may freeze the UI if any heavy lifting is done inside. THAT is the advantage of a NSThread over a NSTimer.
You are also guaranteed that a NSThread that is detached is executed immediately, with a NSTimer, which is based on NSRunLoop, cannot, as it may or may not be able to execute immediately.
There is a 3rd alternative (well technically a fourth too, pthreads, but I will ignore that for now), GCD, but I would suggest you RTFM on that, as it's too broad of a topic to cover in this post.
NSThread and NSTimer are not mutually exclusive or replacements for one another. NSThread allows you to control a thread of execution and NSTimer is just that, a timer.
I assume you mean running an NSTimer on a background thread rather than on the main thread? That is generally a good idea so that the timer has less potential to be delayed by things occurring on your main thread (such as user interaction with the application).
You should read Apple's Threading Programming Guide.

Simple NSThread or NSTimer

I want to run certain background tasks.
Scenario: I would like a button to activate a thread or timer, and then have the thread/timer to start repeating every second returning a NSRunInformationalAlertPanel to the user with data.
This is what I have for my timer:
-(void)workerThread:(NSTimer*) theTimer {
if(intNumberOfTicks > 0)
{
NSRunInformationalAlertPanel(#"The Serial", [NSString stringWithFormat:#"%d", intNumberOfTicks], #"OK", nil, nil);
//[txtTimeMinutes setStringValue:[NSString stringWithFormat:#"%d", intNumberOfTicks]];
intNumberOfTicks--;
}
else {
[timer invalidate];
}
}
And for starting the method...
intNumberOfTicks = 5;
timer = [[NSTimer scheduledTimerWithTimeInterval:1 target: self selector:#selector(workerThread:) userInfo:self repeats:true] retain];
// Or for threading...
///[NSThread detachNewThreadSelector:#selector(workerThread) toTarget:self withObject:nil];
Can anyone help me implement what I need, maybe providing the most basic examples for a NSThread or NSTimer. I have looked at the Apple Dev Refrences but no luck.
Using NSTimer will execute the selector in the same thread as the one which instantiated and invoked it.
If your task must be carried out in a background thread try calling performSelectorInBackground:withObject:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html#//apple_ref/occ/instm/NSObject/performSelectorInBackground:withObject:
From that background thread you can use a scheduled timer the way that you described above.

NSTextField waits until the end of a loop to update

Here's the problem: I have some code that goes like this
otherWinController = [[NotificationWindowController alloc] init];
for (int i = 0; i < 10; i++) {
[otherWinController showMessage:[NSString stringWithFormat:#"%d", i]];
NSLog(#"%d", i);
sleep(1);
}
where otherWinController is a subclass of NSWindowController that I am using to update my window as changes happen in code, and the alloc init method simply opens up the nib and shows the window. showMessage is a method that changes an NSTextView to display whatever text is in the parameter.
in the NSLog, the text changes every second and just counts to ten. However for the showMessage method, the text is blank for a full ten seconds and then just displays the number 10. any thoughts??
FTR, the showMessage method is simply
- (void)showMessage:(NSString *)text {
[[self message] setStringValue:text];
}
not that it should matter, that's pretty basic.
You can probably achieve the desired effect right inside your loop, if you explicitly give the run loop some time to run:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 0.1]];
The problem is that you’re blocking the main thread in your for loop and user interface updates happen on the main thread. The main thread run loop will only spin (and consequently user interface updates will take place) after the method containing that for loop finishes executing.
If you want to update that text field every second, you should use a timer. For instance, considering otherWinController is an instance variable, declare a counter property in your class and:
otherWinController = [[NotificationWindowController alloc] init];
self.counter = 0;
[otherWinController showMessage:[NSString stringWithFormat:#"%d", self.counter]];
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
In the same class, implement the method that’s called whenever the time has been fired:
- (void)updateCounter:(NSTimer *)timer {
self.counter = self.counter + 1;
[otherWinController showMessage:[NSString stringWithFormat:#"%d", self.counter]];
if (self.counter == 9) {
[timer invalidate];
// if you want to reset the counter,
// self.counter = 0;
}
}
Views don't get updated until the end of the run loop; your for loop doesn't let the run loop continue, so all the view updates you make are just done after your for loop exits.
You should either use an NSTimer or performSelector:withObject:afterDelay: to change the display in a loop-like fashion.
[NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(changeTextFieldsString:)
userInfo:nil
repeats:YES];
Then your timer's action will change the view's image:
- (void)changeTextFieldsString:(NSTimer *)tim {
// currStringIdx is an ivar keeping track of our position
if( currStringIdx >= maxStringIdx ){
[tim invalidate];
return;
}
[otherWinController showMessage:[NSString stringWithFormat:#"%d", currStringIdx]]
currStringIdx++;
}
You also generally don't want to use sleep unless you're on a background thread, because it will lock up the rest of the UI; your user won't be able to do anything, and if you sleep long enough, you'll get the spinning beach ball.
I think that by calling sleep(1) you block the main thread, which must draw your changes. So the display is not updated. The task manager will not interrupt your function. You shouldn't use sleep in this case. Please take a look at NSTimer class. It has a static method scheduledTimerWithTimeInterval, which you should use.
static UIViewController *controller = nil;
.....
{
.....
otherWinController = [[NotificationWindowController alloc] init];
controller = otherWinController;
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(timerTick:) userInfo:nil repeats:YES]; //set timer with one second interval
.....
}
- (void) timerTick:(NSTimer*)theTimer {
static int i = 0;
[controller showMessage:[NSString stringWithFormat:#"%d", i]];
NSLog(#"%d", i);
if (++i == 10) {
[theTimer invalidate];
i = 0;
}
}