How do I create a NSTimer on a background thread? - objective-c

I have a task that needs to be performed every 1 second. Currently I have an NSTimer firing repeatedly every 1 sec. How do I have the timer fire in a background thread (non UI-thread)?
I could have the NSTimer fire on the main thread then use NSBlockOperation to dispatch a background thread, but I'm wondering if there is a more efficient way of doing this.

If you need this so timers still run when you scroll your views (or maps), you need to schedule them on different run loop mode. Replace your current timer:
[NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(timerFired:)
userInfo:nil repeats:YES];
With this one:
NSTimer *timer = [NSTimer timerWithTimeInterval:0.5
target:self
selector:#selector(timerFired:)
userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
For details, check this blog post: Event tracking stops NSTimer
EDIT :
second block of code, the NSTimer still runs on the main thread, still on the same run loop as the scrollviews. The difference is the run loop mode. Check the blog post for a clear explanation.

If you want to go pure GCD and use a dispatch source, Apple has some sample code for this in their Concurrency Programming Guide:
dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer)
{
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
Swift 3:
func createDispatchTimer(interval: DispatchTimeInterval,
leeway: DispatchTimeInterval,
queue: DispatchQueue,
block: #escaping ()->()) -> DispatchSourceTimer {
let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: 0),
queue: queue)
timer.scheduleRepeating(deadline: DispatchTime.now(),
interval: interval,
leeway: leeway)
// Use DispatchWorkItem for compatibility with iOS 9. Since iOS 10 you can use DispatchSourceHandler
let workItem = DispatchWorkItem(block: block)
timer.setEventHandler(handler: workItem)
timer.resume()
return timer
}
You could then set up your one-second timer event using code like the following:
dispatch_source_t newTimer = CreateDispatchTimer(1ull * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Repeating task
});
making sure to store and release your timer when done, of course. The above gives you a 1/10th second leeway on the firing of these events, which you could tighten up if you desired.

The timer would need to be installed into a run loop operating on an already-running background thread. That thread would have to continue to run the run loop to have the timer actually fire. And for that background thread to continue being able to fire other timer events, it would need to spawn a new thread to actually handle events anyway (assuming, of course, that the processing you're doing takes a significant amount of time).
For whatever it's worth, I think handling timer events by spawning a new thread using Grand Central Dispatch or NSBlockOperation is a perfectly reasonable use of your main thread.

This should work,
It repeats a method every 1 second in a background queue without using NSTimers :)
- (void)methodToRepeatEveryOneSecond
{
// Do your thing here
// Call this method again using GCD
dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, q_background, ^(void){
[self methodToRepeatEveryOneSecond];
});
}
If you are in the main queue and you want to call above method you could do this so it changes to a background queue before is run :)
dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(q_background, ^{
[self methodToRepeatEveryOneSecond];
});
Hope it helps

For swift 3.0,
Tikhonv's answer does not explain too much. Here adds some of my understanding.
To make things short first, here is the code. It is DIFFERENT from Tikhonv's code at the place where I create the timer. I create the timer using constructer and add it into the loop. I think the scheduleTimer function will add the timer on to the main thread's RunLoop. So it is better to create timer using the constructor.
class RunTimer{
let queue = DispatchQueue(label: "Timer", qos: .background, attributes: .concurrent)
let timer: Timer?
private func startTimer() {
// schedule timer on background
queue.async { [unowned self] in
if let _ = self.timer {
self.timer?.invalidate()
self.timer = nil
}
let currentRunLoop = RunLoop.current
self.timer = Timer(timeInterval: self.updateInterval, target: self, selector: #selector(self.timerTriggered), userInfo: nil, repeats: true)
currentRunLoop.add(self.timer!, forMode: .commonModes)
currentRunLoop.run()
}
}
func timerTriggered() {
// it will run under queue by default
debug()
}
func debug() {
// print out the name of current queue
let name = __dispatch_queue_get_label(nil)
print(String(cString: name, encoding: .utf8))
}
func stopTimer() {
queue.sync { [unowned self] in
guard let _ = self.timer else {
// error, timer already stopped
return
}
self.timer?.invalidate()
self.timer = nil
}
}
}
Create Queue
First, create a queue to make timer run on background and store that queue as a class property in order to reuse it for stop timer. I am not sure if we need to use the same queue for start and stop, the reason I did this is because I saw a warning message here.
The RunLoop class is generally not considered to be thread-safe and
its methods should only be called within the context of the current
thread. You should never try to call the methods of an RunLoop object
running in a different thread, as doing so might cause unexpected
results.
So I decided to store the queue and use the same queue for the timer to avoid synchronization issues.
Also create an empty timer and stored in the class variable as well. Make it optional so you can stop the timer and set it to nil.
class RunTimer{
let queue = DispatchQueue(label: "Timer", qos: .background, attributes: .concurrent)
let timer: Timer?
}
Start Timer
To start timer, first call async from DispatchQueue. Then it is a good practice to first check if the timer has already started. If the timer variable is not nil, then invalidate() it and set it to nil.
The next step is to get the current RunLoop. Because we did this in the block of queue we created, it will get the RunLoop for the background queue we created before.
Create the timer. Here instead of using scheduledTimer, we just call the constructor of timer and pass in whatever property you want for the timer such as timeInterval, target, selector, etc.
Add the created timer to the RunLoop. Run it.
Here is a question about running the RunLoop. According to the documentation here, it says it effectively begins an infinite loop that processes data from the run loop's input sources and timers.
private func startTimer() {
// schedule timer on background
queue.async { [unowned self] in
if let _ = self.timer {
self.timer?.invalidate()
self.timer = nil
}
let currentRunLoop = RunLoop.current
self.timer = Timer(timeInterval: self.updateInterval, target: self, selector: #selector(self.timerTriggered), userInfo: nil, repeats: true)
currentRunLoop.add(self.timer!, forMode: .commonModes)
currentRunLoop.run()
}
}
Trigger Timer
Implement the function as normal. When that function gets called, it is called under the queue by default.
func timerTriggered() {
// under queue by default
debug()
}
func debug() {
let name = __dispatch_queue_get_label(nil)
print(String(cString: name, encoding: .utf8))
}
The debug function above is use to print out the name of the queue. If you ever worry if it has been running on the queue, you can call it to check.
Stop Timer
Stop timer is easy, call validate() and set the timer variable stored inside class to nil.
Here I am running it under the queue again. Because of the warning here I decided to run all the timer related code under the queue to avoid conflicts.
func stopTimer() {
queue.sync { [unowned self] in
guard let _ = self.timer else {
// error, timer already stopped
return
}
self.timer?.invalidate()
self.timer = nil
}
}
Questions related to RunLoop
I am somehow a little bit confused on if we need to manually stop the RunLoop or not. According to the documentation here, it seems that when no timers attached to it, then it will exits immediately. So when we stop the timer, it should exists itself. However, at the end of that document, it also said:
removing all known input sources and timers from the run loop is not a
guarantee that the run loop will exit. macOS can install and remove
additional input sources as needed to process requests targeted at the
receiver’s thread. Those sources could therefore prevent the run loop
from exiting.
I tried the solution below that provided in the documentation for a guarantee to terminate the loop. However, the timer does not fire after I change .run() to the code below.
while (self.timer != nil && currentRunLoop.run(mode: .commonModes, before: Date.distantFuture)) {};
What I am thinking is that it might be safe for just using .run() on iOS. Because the documentation states that macOS is install and remove additional input sources as needed to process requests targeted at the receiver's thread. So iOS might be fine.

Today after 6 years, I try to do same thing, here is alternative soltion: GCD or NSThread.
Timers work in conjunction with run loops, a thread's runloop can be get from the thread only, so the key is that schedule timer in the thread.
Except main thread's runloop, runloop should start manually; there should be some events to handle in running runloop, like Timer, otherwise runloop will exit, and we can use this to exit a runloop if timer is the only event source: invalidate the timer.
The following code is Swift 4:
Solution 0: GCD
weak var weakTimer: Timer?
#objc func timerMethod() {
// vefiry whether timer is fired in background thread
NSLog("It's called from main thread: \(Thread.isMainThread)")
}
func scheduleTimerInBackgroundThread(){
DispatchQueue.global().async(execute: {
//This method schedules timer to current runloop.
self.weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
//start runloop manually, otherwise timer won't fire
//add timer before run, otherwise runloop find there's nothing to do and exit directly.
RunLoop.current.run()
})
}
Timer has strong reference to target, and runloop has strong reference to timer, after timer invalidate, it release target, so keep weak reference to it in target and invalidate it in appropriate time to exit runloop(and then exit thread).
Note: as an optimization, syncfunction of DispatchQueue invokes the block on the current thread when possible. Actually, you execute above code in main thread, Timer is fired in main thread, so don't use sync function, otherwise timer is not fired at the thread you want.
You could name thread to track its activity by pausing program executing in Xcode. In GCD, use:
Thread.current.name = "ThreadWithTimer"
Solution 1: Thread
We could use NSThread directly. Don't afraid, code is easy.
func configurateTimerInBackgroundThread(){
// Don't worry, thread won't be recycled after this method return.
// Of course, it must be started.
let thread = Thread.init(target: self, selector: #selector(addTimer), object: nil)
thread.start()
}
#objc func addTimer() {
weakTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
RunLoop.current.run()
}
Solution 2: Subclass Thread
If you want to use Thread subclass:
class TimerThread: Thread {
var timer: Timer
init(timer: Timer) {
self.timer = timer
super.init()
}
override func main() {
RunLoop.current.add(timer, forMode: .defaultRunLoopMode)
RunLoop.current.run()
}
}
Note: don't add timer in init, otherwise, timer is add to init's caller's thread's runloop, not this thread's runloop, e.g., you run following code in main thread, if TimerThread add timer in init method, timer will be scheduled to main thread's runloop, not timerThread's runloop. You can verify it in timerMethod() log.
let timer = Timer.init(timeInterval: 1, target: self, selector: #selector(timerMethod), userInfo: nil, repeats: true)
weakTimer = timer
let timerThread = TimerThread.init(timer: timer)
timerThread.start()
P.S About Runloop.current.run(), its document suggest don't call this method if we want runloop to terminate, use run(mode: RunLoopMode, before limitDate: Date), actually run() repeatedly invoke this method in the NSDefaultRunloopMode, what's mode? More details in runloop and thread.

My Swift 3.0 solution for iOS 10+, timerMethod() will be called in background queue.
class ViewController: UIViewController {
var timer: Timer!
let queue = DispatchQueue(label: "Timer DispatchQueue", qos: .background, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
override func viewDidLoad() {
super.viewDidLoad()
queue.async { [unowned self] in
let currentRunLoop = RunLoop.current
let timeInterval = 1.0
self.timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(self.timerMethod), userInfo: nil, repeats: true)
self.timer.tolerance = timeInterval * 0.1
currentRunLoop.add(self.timer, forMode: .commonModes)
currentRunLoop.run()
}
}
func timerMethod() {
print("code")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
queue.sync {
timer.invalidate()
}
}
}

Swift only (although can probably be modified to use with Objective-C)
Check out DispatchTimer from https://github.com/arkdan/ARKExtensions, which "Executes a closure on specified dispatch queue, with specified time intervals, for specified number of times (optionally). "
let queue = DispatchQueue(label: "ArbitraryQueue")
let timer = DispatchTimer(timeInterval: 1, queue: queue) { timer in
// body to execute until cancelled by timer.cancel()
}

class BgLoop:Operation{
func main(){
while (!isCancelled) {
sample();
Thread.sleep(forTimeInterval: 1);
}
}
}

If you want your NSTimer to run in even background, do the following-
call [self beginBackgroundTask] method in applicationWillResignActive methods
call [self endBackgroundTask] method in applicationWillEnterForeground
That's it
-(void)beginBackgroundTask
{
bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundTask];
}];
}
-(void)endBackgroundTask
{
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}

Related

How to skip scheduled call to a method when an asynchronous task is ongoing on that method itself?

Suppose I have a method that does some asynchronous tasks. Let's say it refreshes user's access permission and it may take several minutes depending on the internet connection speed or whatever.
I have to call this method periodically (i.e. scheduled call using NSTimer's method scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:)
-(void)refreshPermission {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do something that takes a few minutes
});
}
Now I call this method as timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(refreshPermission) userInfo:nil repeats:YES];. That is, this call is fired every 10 seconds.
What I need to do is, I need to somehow skip one (or, more than one)
scheduled call to this method if something is happening inside that
asynchronous block (Let's say, user's access permission hasn't been
updated).
But once the block is done (that is, user's access permission has been updated), scheduled call with timer should resume.
Any idea or any sample on how to accomplish this??
I think you can do it by using a Bool variable. You can declare Bool variable globally and by using its state you can manage your task in function call.
In method refreshPermission
-(void)refreshPermission {
if(!isExecuting){
isExecuting = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Perform your tasks
isExecuting = NO;
}
}
}
I've come up with this approach. Got the idea from #Sunny's answer.
It worked for me. But any suggestion regarding this implementation is appreciated.
-(void)refresh {
NSLog(#"Refresh called");
NSLock *theLock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Async task assigned");
if(!isExecuting){
[theLock lock];
isExecuting = YES;
[theLock unlock];
// Perform your tasks
NSLog(#"Async task started");
[NSThread sleepForTimeInterval: 13.0]; // for testing purpose
NSLog(#"Async task completed");
[theLock lock];
isExecuting = NO;
[theLock unlock];
}
});
}
Here isExecuting is an instance variable of the containing class. And it was set to isExecuting = NO; before setting the actual scheduled timer for calling the method periodically.
Here I used NSLock for the assurance that no other thread can change the value of isExecuting while a thread is in execution of it's task. I added this locking because every time -(void)refresh method is invoked, there is a possibility that multiple threads become eligible for the execution and changing value of isExecuting. So it's better to make it thread save when changing value of shared variable.

Invalidating a NSTimer from within its invocation method

I have scheduled a timer using [NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:] and want to invalidate it at some point when it fires.
- (id)init
{
[NSTimer scheduledTimerWithInterval:1 target:self selector:#selector(fired:) userInfo:nil repeats:YES];
}
- (void)fired:(NSTimer *)timer
{
if (someCondition) {
[timer invalidate];
}
}
Is this allowed? The documentation states
You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.
If this is not a proper way to accomplish this task: What is the proper way?
Calling [timer invalidate] from within the fire method is just fine, that code will be executed in the same thread as the one used when you created the timer.
The Apple Doc you quoted only warns you that if you create a separate thread and invalidate the timer from it, then, and only then, should expect unpredictable behaviour.
Ex.
// Create the background queue
dispatch_queue_t queue = dispatch_queue_create("do not do this", NULL);
// Start work in new thread
dispatch_async(queue, ^ {
// !! do not do this !!
if (someCondition) {
[yourTimer invalidate];
}
// or this
[self fire:yourTimer];
});
// won’t actually go away until queue is empty
dispatch_release(queue);
It is fine to invalidate it from the fired method, as the fired method is on the same thread the timer was scheduled on:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode.

Cocoa threads being cantankerous

In short, I would like to, in Objective-C cocoa, program something that functions the same way as the following Java pseudocode:
public class MainClass
{
public void mainmethod() //Gets called at start of program
{
UILabel label = CreateAButton();
new DaemonClass(label).start();
//Do things without being interrupted by the Daemon class sleeping or lagging
}
}
public class DaemonClass extends Thread
{
public UILabel label;
public DaemonClass(UILabel lbl)
{
setDaemon(true);
label = lbl;
}
public void run()
{
int i = 0;
while(true)
{
i++;
i = i%2;
UILabel.setText("" + i);
Thread.sleep(1000);
}
}
}
In other words... I'd like to spawn a daemon thread that can be as slow as it likes, without interrupting the progress or speed of any other threads, INCLUDING, the main one.
I have tried using things like the Dispatch Queue, as well as NSThread.
When using either of these, I tried to create a simple label-changer thread that toggled the label's text from 1 to 0, indefinitely. It appeared to me, the user, to constantly be locked either at 1, or 0, randomly chosen at startup.
When using either of these, and attempting to use [NSThread sleepForTimeInterval:1];, the thread would stop executing all together after the sleepForTimeInterval call.
Furthermore, having skimmed the docs, I picked up on the fact that the run loop is not called while [NSThread sleep... is sleeping!
If it is any help, I was invoking my threads from the - (void)viewDidLoad; method.
My question for you is:
How do I stop [NSThread sleepForTimeInterval:1]; from crashing my thread, OR:
How do I start a daemon thread that invokes a method or code block (preferably a code block!)
P.S. if it makes any difference, this is for iOS
The reason for the problems you've seen is most likely that UIKit isn't thread-safe, i.e. you can only use a UILabel from the main thread. The easiest way to do that is to enqueue a block on the main queue (which is associated with the main thread) using GCD:
dispatch_async(dispatch_get_main_queue(), ^{
myLabel.text = #"whatever";
});
First of all you cannot manage UIKit in the background threads. In order to set the text to UILabel you need to use main thread.
Judging by the type of task you want to achieve, you should use NSTimer. You can set the time interval it should be called and stop and resume it anytime.
#property (strong, nonatomic) NSTimer *timer; //in your .h file
- (void)startChangingLabelText {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(checkTime:)
userInfo:nil
repeats:YES];
}
- (void)stopChangingLabelText {
[timer invalidate], self.timer = nil;
}
- (void)checkTime:(NSTimer *)timer {
int rand = arc4random() % 2;
if (rand)
label.text = #"true";
else
label.text = #"false";
}

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.

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

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