Consider following code:
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.5 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
NSLog(#"Popped after %llu time!", popTime);
});
And it output not 2,5 seconds, but 4445236369163.
How could that be? Dispatch_time_t is just typedef for uint64_t, which simply an unsigned integer, without sign extension bit.
Even with that weird value, app works just fine, outputing log after 2,5 seconds.
If you look into documentation, dispatch_time_t is:
"A somewhat abstract representation of time."
You should not thought about it as a time expressed in second or nanosecond, so there is nothing wrong with your output.
Related
I have some Objective-C code that works on Intel Macs, but fails on Macs with the M1 chip.
dispatch_time_t now = dispatch_time(DISPATCH_TIME_NOW, 0);
dispatch_time_t delta = (uint64_t)(1 * NSEC_PER_SEC);
dispatch_time_t when = dispatch_time(now, delta);
NSLog(#"now: %llu", now);
NSLog(#"delta: %llu", delta);
NSLog(#"when: %llu", when);
NSLog(#"real delta: %llu", when-now);
It'll print something like this:
now: 1272914827933
delta: 1000000000
when: 1272938827933
real delta: 24000000
1272914827933 + 1000000000 = 1273914827933, NOT 1272938827933. Why the hell is this failing, and only on some Macs?
Edit: this is also broken when running on an actual iPhone device. Just create a new Xcode project using Obj-C, paste the code into the AppDelegate, and run on your phone.
dispatch_time() second argument (delta) is in nanoseconds, however the returned dispatch_time_t depends on the CPU architecture. For Intel, the returned value is measured in nanoseconds. For Apple Silicon, the value is in ticks.
Since the two values are not measured the same way, you can not calculate numbers this way.
Here is a new test. Notice mac_absolute_time is always in advance of the dispatch_time now time.
// cc -framework Foundation -o dispatch_time dispatch_time.m
#include <time.h>
#include <mach/mach_time.h>
#import <Foundation/Foundation.h>
int main() {
dispatch_time_t now = dispatch_time(DISPATCH_TIME_NOW, 0);
dispatch_time_t delta = (uint64_t) (1 * NSEC_PER_SEC);
dispatch_time_t when = dispatch_time(now, delta);
NSLog(#"mac_absolute_time: %llu", mach_absolute_time());
NSLog(#"now: %llu", now);
NSLog(#"delta: %llu", delta);
NSLog(#"when: %llu", when);
NSLog(#"invalid: when-now delta: %llu", when-now);
return 0;
}
I was just wondering, if there was any way of pausing a for loop.
For instance,
for (i = 0; i<10 ; i++) {
NSLog(i);
//Pause should go here
}
The outcome should be:
1
(wait 1 sec)
2
(wait 1 sec)
etc.
Firstly, I thought that you could add SKAction *wait = [SKAction waitForDuration:1 completion:^{
, but then I wasn't sure if you could resume the loop from inside the completion brackets.
I have looked around and found NSTimer, but I am not sure how you could implement this, because I would have to call the loop after each interval and it i would never be more than 0.
Is there any way that you could pause the loop for an amount of time, then have it resume enumerating?
As a matter of style you'd normally reformat the loop to be tail recursive. So, instead of:
for(i = 0; i < 10; i++) {
// ... work here ...
}
You'd reformulate as:
void doWorkForIteration(int i, int limit) {
if(i == limit) return;
// ... work here ...
doWorkForIteration(i+1, limit);
}
...
doWorkForIteration(0, 10);
It recurses because it call itself. It is tail recursive because its recursive call is the very last thing it does.
Once you've done that, you can switch from a direct recursive call to a delayed one. E.g. with GCD:
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
doWorkForIteration(i+1, limit);
});
... or use [self performSelector: withObject: afterDelay:] or any of the other deferred call approaches.
To do literally what you want, you could use C's sleep call. But that thread will block for the number of seconds specified. So don't do it on the main thread unless you want your application to lock up.
This question already has answers here:
NSTextField waits until the end of a loop to update
(4 answers)
Closed 9 years ago.
I wish to create a loop that increments an integer every ten seconds, and does this one hundred times. But when I use this code:
- (IBAction)loopTest:(id)sender {
}
- (IBAction)beginLoop:(id)sender {
for (i=0;i<100 ;i++ ) {
testingLoops++;
NSString *feed = [NSString stringWithFormat: #"%d", testingLoops];
self.feedLabel.stringValue = feed;
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(loopTest:) userInfo:nil repeats:NO];
}
}
the application just displays the integer as 100 straight away. I have it so that it runs the beginLoop when I press a button. What's going on?
Your statement:
[NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:#selector(loopTest:)
userInfo:nil
repeats:NO];
does not delay your loop - rather for every iteration of the loop it schedules a timer to call loopTest:, a method you've defined to do nothing.
To use a timer to delay a loop you need to schedule a method which performs the remainder of the loop. In other words a non-loop method which performs the equivalent of one iteration of your loop and then schedules a time to perform the remainder.
Following your approach, but switching to use an implicit timer provided by performSelector:withObject:afterDelay as it is more convenient here, this gives us:
- (IBAction)beginLoop:(id)sender
{
// start "loop"
// note we only pass the current index and not the limit or delay
// as there is no performSelector version which directly supports
// passing three values to the selector
[self doLoopIndex:#0];
}
- (void) doLoopIndex:(NSNumber *)objIndex
{
// extract int from NSNumber - we use the later as the argument type so we can use performSelector below
int index = objIndex.intValue;
// do the work of one iteration
NSString *feed = [NSString stringWithFormat: #"%d", index];
self.feedLabel.stringValue = feed;
// increment "loop" counter and schedule next iteration if needed
// note the use of #(index) to create an NSNumber as an object is required
index++;
if (index < 100)
[self performSelector:#selector(doLoopIndex:) withObject:#(index) afterDelay:1];
}
This is not the only way to achieve your goal. Using blocks and "Grand Central Dispatch" (GCD) is another and in this case has the advantage that passing three values: current index, limit and delay; is easier.
For example, a general loop with delay might be written as:
- (void) doLoop:(int)index // starting index
limit:(int)limit // limit
delay:(NSTimeInterval)delayInSeconds // delay each iteration
body:(void (^)(int))body // block for loop body
{
// invoke the body block
body(index);
// increment index and schedule next "iteration" if needed
index++;
if (index < 100)
{
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
[self doLoop:index limit:limit delay:delayInSeconds body:body];
});
}
}
See the documentation for the details of the dispatch_X methods and types.
This might look more complicated, but that is because it is more general. Using the above your particular loop becomes just:
- (IBAction)beginLoop:(id)sender
{
[self doLoop:0 limit:100 delay:1 body:^(int index)
{
NSString *feed = [NSString stringWithFormat: #"%d", index];
self.feedLabel.stringValue = feed;
}];
}
Which of the above approaches, or using an explicit timer as your original code did, is appropriate for your use case is your choice - there is no single "right" answer.
HTH.
Try this
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, i * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
yourtextfield.text=[NSString stringWithFormat:#"%d", i];
});
So the situation is I need to run the code for 5 seconds but if I match the condition then want it to immediately return back. I am doing this in KIF test steps and I don't want this to block my applications main thread.
Sample pseudo Code -
+ (BOOL) isVerified:(NSString*)label;
{
if(<condition match>)
return YES;
else if(X seconds not passed)
<make sure m running this function for X seconds>
else // X seconds passed now..
return NO;
}
If you don't want to block the main thread in the case that NO should be returned after 5 sec delay, then structure that API asynchronously.
typedef void(^CCFVerificationCallbackBlock)(BOOL verified);
#interface CCFVerifier : NSObject
- (void)verifyLabel:(NSString *)label withCallbackBlock:(CCFVerificationCallbackBlock)block;
#end
static const int64_t ReturnDelay = 5.0 * NSEC_PER_SEC;
#implementation CCFVerifier
- (void)verifyLabel:(NSString *)label withCallbackBlock:(CCFVerificationCallbackBlock)block {
NSParameterAssert(block);
if( [label isEqualToString:#"moo"] )
block(YES);
else {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, ReturnDelay);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
block(NO);
});
}
}
#end
To use:
_verifier = [[CCFVerifier alloc] init];
[_verifier verifyLabel:#"foo" withCallbackBlock:^(BOOL verified) {
NSLog(#"verification result: %d",verified);
}];
Don't block or poll.
set a timer for 5 seconds
if whatever condition is met, check the condition after 5 seconds and do the fail case or success case if necessary
if you want to take action immediately on completion, then use any of the various "perform thing on main thread" constructs to do so (also setting the condition to let the timer firing no that the task was done)
you can invalidate the timer to keep it from firing at all, if you want.
I basically want to press a button, that starts timecode at 30fps. (called every 1/30th of a second). I want the timecode to be referenced to the clock built in to the computer. I could easily get the current time in HH:mm:ss using NSDate, but I need the counter to start from zero and implement frames- formatted like HH:mm:ss:ff
Thoughts?
Use a CVDisplayLink to generate a pulse with the video card's accuracy, this will be much more accurate than an NSTimer or a dispatch queue. CoreMedia/CoreVideo also talks SMPTE natively.
CVReturn MyDisplayCallback(CVDisplayLinkRef displayLink,
const CVTimeStamp *inNow,
const CVTimeStamp *inOutputTime,
CVOptionFlags flagsIn,
CVOptionFlags *flagsOut,
void *displayLinkContext) {
CVSMPTETime timecodeNow = inNow->smpteTime; // it's that easy!
DoStuffWith(timecodeNow); // you might have to modulo this run a bit if the display framerate is greater than 30fps.
return kCVReturnSuccess;
}
CVDisplayLinkRef _link;
CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(),&_link);
CVDisplayLinkSetOutputCallback(_link, MyDisplayCallback, NULL);
CVDisplayLinkStart(_link);
EDIT: After playing with this a bit, I've noticed that the SMPTE fields from the displaylink aren't getting filled out, but OTOH the host time is accurate. Just use:
inNow->videoTime / inNow->videoTimeScale;
to obtain the number of seconds uptime, and
inNow->videTime % inNow->videoTimeScale
to get the remainder.
Here's as far as I got:
#implementation JHDLTAppDelegate
CVReturn MYCGCallback(CVDisplayLinkRef displayLink,
const CVTimeStamp *inNow,
const CVTimeStamp *inOutputTime,
CVOptionFlags flagsIn,
CVOptionFlags *flagsOut,
void *displayLinkContext) {
dispatch_async(dispatch_get_main_queue(), ^{
JHDLTAppDelegate *obj = (__bridge JHDLTAppDelegate *)displayLinkContext;
uint64_t seconds = inNow->videoTime / inNow->videoTimeScale;
[obj.outputView setStringValue:[NSString stringWithFormat:#"days: %llu/hours: %llu/seconds: %llu (%llu:%u)",
seconds / (3600 * 24),
seconds / 3600,
seconds,
inNow->videoTime, inNow->videoTimeScale]];
});
return kCVReturnSuccess;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &_ref);
CVDisplayLinkSetOutputCallback(_ref, MYCGCallback, (__bridge void *)self);
CVDisplayLinkStart(_ref);
}
- (void)dealloc
{
CVDisplayLinkStop(_ref);
CVDisplayLinkRelease(_ref);
}
#end
This should work using an NSTimer object and doing the visual updates in the invocation. You can set the timer to fire every 3.3333 milliseconds. The only problem I see is over very long stretches, the timecode will be slightly off. Also, if this is video related, be careful because some video is encoded at 24 fps. I would then have a counter do a +1 in the method fired by the timer unless counter = 30, then I would have it reset to 1. You should be able to initialize an NSDateFormatter object with a custom format string to insert the current time and your counter variable in the format you want to display to the user.