I have a for loop that I want to add a delay between iterations. I have changed waitUntilDone to YES and get the same results. My array only has two numbers in it and both are called after the five seconds instead of:
0s - nothing
5s - Block called
10s- Block called
for(NSNumber* transaction in gainsArray) {
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSLog(#"Block");
[self performSelectorOnMainThread:#selector(private_addTransactionToBankroll:)
withObject:transaction waitUntilDone:NO];
});
}
2015-06-16 20:11:06.485 TestApp[97027:6251126] Block
2015-06-16 20:11:06.485 TestApp[97027:6251127] Block
I am using Cocos2d if that matters
The for loop will dispatch one right after the other so they will essentially delay for the same time.
Instead set a different increasing delay for each:
double delayInSeconds = 0.0;
for(NSNumber* transaction in gainsArray)
{
delayInSeconds += 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
NSLog(#"Block");
[self performSelectorOnMainThread:#selector(private_addTransactionToBankroll:)
withObject:transaction
waitUntilDone:NO];
});
}
#zaph has a pretty good solution. I thought I'd try from a different angle. Since Objective-C is Objective-C, why not define some kind of object to do this timed looping? Hint: this exists. We can use NSTimer and its userInfo property to work this out. I think the solution is sort of elegant, if not a nasty hack.
// Somewhere in code.... to start the 'loop'
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
action:#selector(processNextTransaction:)
userInfo:#{
#"gains": [gainsArray mutableCopy]
}
repeats:NO];
// What handles each 'iteration' of your 'loop'
- (void)processNextTransaction:(NSTimer *)loopTimer {
NSMutableArray *gains = [loopTimer.userInfo objectForKey:#"gains"];
if(gains && gains.count > 0) {
id transaction = [gains firstObject];
[gains removeObjectAtIndex:0]; // NSMutableArray should really return the object we're removing, but it doesn't...
[self private_addTransactionToBankroll:transaction];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
action:#selector(processNextTransaction:)
userInfo:#{
#"gains": gains
}
repeats:NO];
}
}
I would check that the NSTimer is retained by being added to the run-loop. If that's not the case, you should store a reference to it as a property on whatever class is managing all of this.
It's also worth noting that because NSTimers get installed on the main run loop by default, you don't need to worry about all the GCD stuff. Then again, if this work is pretty difficult work, you may want -processNextTransaction: to offload its work onto another GCD queue and then come back to the main queue to initialize the NSTimer instance.
Be sure to use the -scheduledTimer... method; timer... class methods on NSTimer don't install it on any loop, and the objects just sit in space doing nothing. Don't do repeats:YES, that would be tragic, as you'd have timers attached to the run loop willy-nilly, with no references pointing to them to know how or where to stop them. This is generally a bad thing.
To avoid EXC_BAD_ACCESS exceptions, never dealloc the object whose method an NSTimer is going to call, if that timer hasn't fired yet. You may want to store the pending NSTimer in a property on your class so that you can handle this sort of thing. If it's a ViewController that is managing all this (which it typically is), then I would use the following code to clean up the timer on -viewWillDisappear. (This assumes that you're setting a new timer to some #property, self.timer)
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if(self.timer) {
[self.timer invalidate]; // -invalidate removes it from the run loop.
self.timer = nil; // Stop pointing at it so ARC destroys it.
}
}
Related
Very similar issue is already discussed here. The problem at hand and what I am trying to achieve is to call a function on a given object in the thread it is created at. Here is the complete case:
an instance of class A is created in a given NSThread (Thread A) (not the main one). The instance keeps its creating NSThread as a member variable.
an instance of class B has one of its member functions executing in another NSThread - Thread B, and wants to call a function of A in A's creation thread. Thus B's currently executing function issues the following call:
[_a performSelector: #(fun)
onThread: _a.creationThread
withObject: nil
waitUntilDone: NO];
If the creation thread of A's instance is not the main one, fun never gets called. If the creation thread is the main one it is always called. First I was thinking whether the thread that created A's instance has been destroyed and the pointer points to an invalid thread but actually calling any functions on the thread object (Thread A) produces valid results and no crashes. Also checking the object is valid according to this check. Any suggestions?
Update:
What I'm doing is creating a timer on a background thread:
_timer = [NSTimer timerWithTimeInterval:60.0 target:self selector:#selector(fun:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
This code is the one starting the timer. The timer is not started in any specific background thread. It just happens that the function that creates the timer could be called in any thread. Thus the timer should be invalidated in the exactly same one as the NSTimer documentation states: "you should always call the invalidate method from the same thread on which the timer was installed."
To run timer on background thread, you have two options.
Use dispatch timer source:
#property (nonatomic, strong) dispatch_source_t timer;
and you can then configure this timer to fire every two seconds:
- (void)startTimer {
dispatch_queue_t queue = dispatch_queue_create("com.domain.app.timer", 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.timer, ^{
// call whatever you want here
});
dispatch_resume(self.timer);
}
- (void)stopTimer {
dispatch_cancel(self.timer);
self.timer = nil;
}
Run NSTimer on background thread. To do this, you can do something like:
#property (atomic) BOOL shouldKeepRunning;
#property (nonatomic, strong) NSThread *timerThread;
#property (nonatomic, strong) NSTimer *timer;
And
- (void)startTimerThread {
self.timerThread = [[NSThread alloc] initWithTarget:self selector:#selector(startTimer:) object:nil];
[self.timerThread start];
}
- (void)stopTimerThread {
[self performSelector:#selector(stopTimer:) onThread:self.timerThread withObject:nil waitUntilDone:false];
}
- (void)startTimer:(id)__unused object {
#autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(handleTimer:) userInfo:nil repeats:YES];
[runLoop addTimer:self.timer forMode:NSDefaultRunLoopMode];
self.shouldKeepRunning = YES;
while (self.shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
;
self.timerThread = nil;
}
}
- (void)handleTimer:(NSTimer *)timer {
NSLog(#"tick");
}
- (void)stopTimer:(id)__unused object {
[self.timer invalidate];
self.timer = nil;
self.shouldKeepRunning = FALSE;
}
I'm not crazy about the shouldKeepRunning state variable, but if you look at the Apple documentation for the run method, they discourage the reliance upon adding sources/timers to run loops:
If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
Personally, I'd recommend the dispatch timer approach.
is it possible that performSelector:withObject:afterDelay: doesn't work in subthreads?
I'm still new to objective c and Xcode so maybe I've missed something obvious... :/ I'd really appreciate some help.
All I want to do is to show an infolabel for 3 seconds, after that it shall be hidden. In case a new info is set the thread that hides the label after 3 seconds shall be canceled. (I don't want new information hidden through old threads.)
Sourcecode:
- (void) setInfoLabel: (NSString*) labelText
{
// ... update label with text ...
infoLabel.hidden = NO;
if(appDelegate.infoThread != nil) [appDelegate.infoThread cancel]; // cancel last hide-thread, if it exists
NSThread *newThread = [[NSThread alloc] initWithTarget: self selector:#selector(setInfoLabelTimer) object: nil];// create new thread
appDelegate.infoThread = newThread; // save reference
[newThread start]; // start thread
[self performSelector:#selector(testY) withObject: nil afterDelay:1.0];
}
-(void) setInfoLabelTimer
{
NSLog(#"setInfoLabelTimer");
[self performSelector:#selector(testX) withObject: nil afterDelay:1.0];
[self performSelector:#selector(hideInfoLabel) withObject: nil afterDelay:3.0];
NSLog(#"Done?");
}
-(void) testX
{
NSLog(#"testX testX testX testX testX");
}
-(void) testY
{
NSLog(#"testY testY testY testY testY");
}
-(void) hideInfoLabel
{
NSLog(#"f hideInfoLabel");
if(!([[NSThread currentThread] isCancelled])) {
AppDelegate *appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
appDelegate.infoThread = nil;
appDelegate.infoLabel.hidden = YES;
[NSThread exit];
}
}
Console-Output:
setInfoLabelTimer
Done?
testY testY testY testY testY
As you can see performSelector:withObject:afterDelay: DOES work (--->"testY testY testY testY testY"), but not in the subthread (which runs (--->"setInfoLabelTimer" and"Done?"))
Does anyone know why performSelector:withObject:afterDelay: doesn't work in subthreads? (Or what's my fault? :()
Best regards,
Teapot
If you want to call performSelector:withObject:afterDelay on thread, this thread must has a running RunLoop. Check out the Thread Programming Guide from Apple. Here is also an example for RunLoop and NSThread.
You can add the following code in the setInfoLabelTimer:
while (!self.isCancelled)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
If you're running a 'sub' thread (a thread which isn't the main thread) it can run in one of 2 ways:
It runs a single method and then terminates
It runs a run loop and handles items from a queue
If the thread runs in form 1, your use of performSelector puts an item onto a queue (or tries to at least) but it will never get handled, the thread will just terminate.
If you wanted to use performSelector on the thread you'd need to do additional work. Or, you could push the item onto the main thread where a run loop is running.
As an aside, you might want to consider working with Grand Central Dispatch, GCD, instead. If you want to do something in three seconds, you can:
double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// do stuff here, and because it's in the main queue, you can do UI stuff, too
});
I'd also refer you to Migrating Away From Threads in the Concurrency Programming Guide.
Alternatively, rather than using a GCD, you can use an animation block, in which you can designate what you want to happen in 3.0 seconds. You can also animate that transition (in my example, 0.25 seconds), so that the removal of the control is a little more graceful:
[UIView animateWithDuration:0.25
delay:3.0
options:0
animations:^{
// you can, for example, visually hide in gracefully over a 0.25 second span of time
infoLabel.alpha = 0.0;
}
completion:^(BOOL finished) {
// if you wanted to actually remove the view when the animation was done, you could do that here
[infoLabel removeFromSuperview];
}];
There is no need for threads or GCD at all to do what you want to do.
Simply use performSelector:withObject:afterDelay: directly on the main thread, us an animation as #Rob indicated, use dispatch_after on the main queue, or an NSTimer.
Many, if not most, web services have a rate limit for clients. Delicious says a client can make one request per second; Twitter has limits per end-point; I'm sure Facebook and Flickr and Foursquare have their own idea.
You can easily limit an iOS application to a single request at a time using an NSOperationQueue.
But how do you limit an application to making, say, only one request per second?
I've looked at the sample code by Apple, AFNetworking, ASINetwork and a few others, and none seem to solve this problem. This seems odd to me. I'll concede that I could be missing something very obvious...
Some parameters:
Assume I have an NSOperationQueue for network operations and the request is an NSOperation (could also be a GCD queue I suppose, but this is what I've mostly been working with)
The same rate limit is used for each request in the queue
I'm looking for a solution in iOS, but general ideas might be useful
Possible solutions:
sleep statement in the NSOperation (it's a queue/thread so this wouldn't block anything else)
NSTimer in the NSOperation
performSelector: in the NSOperation (I patched ASINetworking to use this approach, though I'm not using it and didn't push the change upstream)
Start/stop the queue (using KVO?) to make sure the rate limit is not exceeded
Special "sleep" NSOperation. This would be a task that the next network operation would be dependent upon
Completely ignore the rate limit and just pause a bit when you get the "exceeded rate limit" error response
These all seem quite messy. Operations that sleep would likely prevent forms of "priority" queue. Starting/stopping the queue seems fragile. Ignoring the limit is rude.
To be clear, I have solved this problem. But the solution seems "messy" and somewhat fragile. I'd like to know if there's a better, cleaner option.
Ideas?
#implementation SomeNSOperationSubClass {
BOOL complete;
BOOL stopRunLoop;
NSThread *myThread;
}
-(void) rateLimitMonitor:(NSTimer *)theTimer {
[theTimer invalidate];
}
-(void) main {
myThread = [NSThread currentThread];
NSTimer *myTimer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(rateLimitMonitor:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];
[self doAsyncThing];
while ((!stopRunLoop || [myTimer isValid]) && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
complete = YES;
}
-(void) internalComplete {
stopRunLoop = YES;
}
-(void) setComplete {
[self performSelector:#selector(internalComplete) onThread:myThread withObject:nil waitUntilDone:NO];
}
-(BOOL) isFinished {
return complete;
}
#end
and in your async callback
[myNSOperationSubClass setComplete];
New in iOS 13, this functionality is built in. Pass your communication trigger through the Combine framework's debounce operator and you're all set.
This is a fix for the almost working solution provided by Edwin
- (void)main {
for (double delay = 0.0; delay < 10.0; delay+=1) {
[self networkCallWithDelay:delay];
}
}
- (void)networkCallWithDelay:(double)delay {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Your asynchronous network call goes here
});
}
Possible solution, assuming you've already implemented your network module using other technologies other than NSOperation.
Use GCD to solve this problem. The following code introduces a 1 second delay to each network call. Pay close attention to the parameter for popTime
- (void)main {
for (NSInteger index = 0; index < 10; index++) {
[self networkCallWithDelay:1*index];
}
}
// Your network code goes here
- (void)networkCallWithDelay:(double)delay {
double delayInSeconds = delay / 10.0f;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Your asynchronous network call goes here
});
}
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.
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