I am trying to schedule a GNUStep Objective-C method call to run every second for a variable number of seconds. I am trying to use NSTimer to schedule the method call, but the handler method never gets called.
Here is my code:
Timer.m:
- (id) init {
self = [super init];
if(self) {
_ticks = 0;
}
return self;
}
- (void) startWithTicks: (unsigned int) ticks {
_ticks = ticks; //_ticks is an unsigned int instance variable
if(_ticks > 0) {
[NSTimer scheduledTimerWithTimeInterval: 1.0
target: self
selector: #selector(onTick:)
userInfo: nil
repeats: YES];
}
}
- (void) onTick: (NSTimer*) timer {
NSLog(#"tick");
_ticks--;
if(_ticks == 0) {
[timer invalidate];
timer = nil;
}
}
main.m:
int main(int argc, const char* argv[]) {
Timer* t = [[Timer alloc] init];
NSLog(#"Setting timer");
[t startWithTicks: 3];
usleep(5000);
NSLog(#"End of timer");
return 0;
}
I would expect the output to be
Setting timer
tick
tick
tick
End of timer
However, the output is
Setting timer
End of timer
Why is this and how can I fix it?
The timer won't run while your thread is sleeping.
Your timer class code works fine if you're using it from a ViewController.
If instead you'd like to use it within the main method, you'll want to explicitly run the mainRunLoop. Try adjusting your main method to this:
int main(int argc, char * argv[]) {
Timer *timer = [[Timer alloc] init];
NSLog(#"Setting Timer");
[timer startWithTicks:3];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
NSLog(#"End of Timer");
return 0;
}
to run the mainRunLoop running for 3 seconds, which should produce your desired output.
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
https://developer.apple.com/documentation/foundation/nsrunloop
Related
I want to implement a while loop that exits either when a particular condition is met, or when a timer times out.
If I just start the timer (to set an object variable on timeout), and then start the while loop (checking the object variable), it doesn't work, because the timer never times out.
I've tried 3 of the solutions suggested in How to wait for a thread to finish in Objective-C to make the loop run in a separate function on another thread, but they fail in various different ways. I have not yet managed to get a test run where the timer times out.
The simple implementation was
//object variable
BOOL m_readTimedOut;
- (void) someFunction
{
m_readTimedOut = NO;
float timeoutS = 0.1;
//Start the timer
SEL readTimeoutSelector = sel_registerName("onReadTimeout:");
[NSTimer scheduledTimerWithTimeInterval:timeoutS
target:self
selector:readTimeoutSelector
userInfo:nil
repeats:NO];
int numBytesToRead = 1;
BOOL exit = NO;
int counter = 0;
while ((counter < numBytesToRead) && (exit == NO))
{
#try
{
#synchronized (readBufferLock)
{
//m_readBuffer is an object variable that can be altered on another thread
if ([m_readBuffer length] > 0)
{
//Do stuff here
counter++;
}
} //end synchronised
}
#catch (NSException *exception)
{
//Log exception
}
if (counter == numBytesToRead || m_readTimedOut == YES)
{
exit = YES;
}
} //end while
}
- (void)onReadTimeout:(NSTimer *)timer
{
NSLog(#"Read timer timed out");
m_readTimedOut = YES;
}
Just a try on the timed exit only - how about
NSDate * start = [[NSDate alloc] init]; // When we started
while ( counter < something )
{
// do stuff ...
// Check time
NSDate * now = [[NSDate alloc] init];
// Been here more than 10s since start
if ( [now timeIntervalSinceDate:start] > 10 )
{
// Timed exit
break;
}
}
I have an async method called getCount: which goes to a web URL, counts some stuff, and invokes a callback with the count when it's done.
I have another method which is synchronous and needs to take those results, puts them into a message, and returns that message. Here are the two together:
- (NSString *)describe {
__block bool gotCount = NO;
[self getCount:^(int count) {
NSLog(#"Got the count: %i", count);
_count = count; // _count is an ivar of the object with this method.
gotCount = YES;
}];
// Pause here until the count has been fetched.
while (!gotCount) {
[NSThread sleepForTimeInterval:0.05];
}
return [NSString stringWithFormat:#"The count is %i", _count];
}
My callback is never called when I try this. It never prints
Got the count 0
or any other value for count in this scenario.
If I comment out the while loop, that message does get printed out. So I know that the getCount: method works, there's just something wrong with my loop waiting for it to arrive.
I need getCount: to remain asynchronous (there's other places it gets used where that's more important) and I need describe to remain synchronous. How can I handle this?
one possible thing: if your describe method is in the main thread then you call getCount method also from the main thread and all web callbacks are in the main thread. BUT you block the main thread with the thread sleep -> you can not get call back from the web to get a count.
Edited:
try to call getCount method from another thread. use e.g.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getCount:^(int count) {
NSLog(#"Got the count: %i", count);
_count = count; // _count is an ivar of the object with this method.
gotCount = YES;
}];
});
Edited 2:
I tried this code and it works fine -> something probably wrong with the threads in your getCount method.
- (NSString *)describe {
__block bool gotCount = NO;
__block NSInteger _count;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:5.00];
_count = 5;
gotCount = YES;
});
// Pause here until the count has been fetched.
while (!gotCount) {
[NSThread sleepForTimeInterval:0.05];
}
return [NSString stringWithFormat:#"The count is %li", _count];
}
a way that would work but is QUITE the hack (which apple employed in older APIs on the mac all the time) would be to run the runloop while waiting:
note: this relies on the callback being on the same queue as the describe method
see: JSON async request [SAME ISSUE]
a self contained WORKING example:
#import <Foundation/Foundation.h>
#interface T : NSObject
- (NSString *)describe;
#end
#implementation T {
int _count;
}
- (void)getCount:(void (^)(int c)) handler {
dispatch_async(dispatch_get_global_queue(0,0), ^ {
sleep(5);
dispatch_sync(dispatch_get_main_queue(), ^{
handler(55);
});
});
}
- (NSString *)describe {
__block bool gotCount = NO;
[self getCount:^(int count) {
NSLog(#"Got the count: %i", count);
_count = count; // _count is an ivar of the object with this method.
gotCount = YES;
}];
// Pause here until the count has been fetched.
while (!gotCount) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
return [NSString stringWithFormat:#"The count is %i", _count];
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
T *t = [T new];
NSLog(#"describe: %#", [t describe]);
}
}
Trying to record the time it takes for a user to press one button, and then the next.
Here's part of my .m file... I can't get theDate to hold its variable (The start time) All that happens is I get 0.000 seconds in the log & text label.
- (IBAction)start:(id)sender
{
NSDate *theDate = [[NSDate date] init];
}
- (IBAction)stop:(id)sender
{
NSTimeInterval timeInterval = [theDate timeIntervalSinceNow];
NSString *time = [NSString stringWithFormat:#"%f", timeInterval];
_timeLabel.text = time;
NSLog(time);
}
I also have
#property(retain, nonatomic) NSDate *theDate;
in the header file and
#synthesize theDate;
in the top of the .m file.
Thanks!
BTW I'm trying to go off of this post: Getting the time elapsed (Objective-c)
NSDate *theDate = [[NSDate date] init];
Creates a local variable so it doesn't use your property. In addition, as #Catfish_Man pointed out, [[NSDate date] init] should just be [NSDate date].
In addition, you should use self.theDate to use the property.
- (IBAction)start:(id)sender
{
self.theDate = [NSDate date];
}
- (IBAction)stop:(id)sender
{
NSTimeInterval timeInterval = [self.theDate timeIntervalSinceNow];
NSString *time = [NSString stringWithFormat:#"%f", timeInterval];
_timeLabel.text = time;
NSLog(time);
}
You are redefining date with a local scope in
- (IBAction)start:(id)sender
You should be using
self.date = [NSDate date];
You can get really fine timing (seconds.parts of seconds) using this StopWatch class. It uses the high-precision timer in the iPhone. Using NSDate will only get you second(s) accuracy.
StopWatch.h
#import <Foundation/Foundation.h>
#interface StopWatch : NSObject
{
uint64_t _start;
uint64_t _stop;
uint64_t _elapsed;
}
-(void) Start;
-(void) Stop;
-(void) StopWithContext:(NSString*) context;
-(double) seconds;
-(NSString*) description;
+(StopWatch*) stopWatch;
-(StopWatch*) init;
#end
StopWatch.m
#import "StopWatch.h"
#include <mach/mach_time.h>
#implementation StopWatch
-(void) Start
{
_stop = 0;
_elapsed = 0;
_start = mach_absolute_time();
}
-(void) Stop
{
_stop = mach_absolute_time();
if(_stop > _start)
{
_elapsed = _stop - _start;
}
else
{
_elapsed = 0;
}
_start = mach_absolute_time();
}
-(void) StopWithContext:(NSString*) context
{
_stop = mach_absolute_time();
if(_stop > _start)
{
_elapsed = _stop - _start;
}
else
{
_elapsed = 0;
}
NSLog([NSString stringWithFormat:#"[%#] Stopped at %f",context,[self seconds]]);
_start = mach_absolute_time();
}
-(double) seconds
{
if(_elapsed > 0)
{
uint64_t elapsedTimeNano = 0;
mach_timebase_info_data_t timeBaseInfo;
mach_timebase_info(&timeBaseInfo);
elapsedTimeNano = _elapsed * timeBaseInfo.numer / timeBaseInfo.denom;
double elapsedSeconds = elapsedTimeNano * 1.0E-9;
return elapsedSeconds;
}
return 0.0;
}
-(NSString*) description
{
return [NSString stringWithFormat:#"%f secs.",[self seconds]];
}
+(StopWatch*) stopWatch
{
StopWatch* obj = [[[StopWatch alloc] init] autorelease];
return obj;
}
-(StopWatch*) init
{
[super init];
return self;
}
#end
The class has a static stopWatch method that returns an autoreleased object.
Once you call start, use the seconds method to get the elapsed time. Call start again to restart it. Or stop to stop it. You can still read the time (call seconds) anytime after calling stop.
Example In A Function (Timing call of execution)
-(void)SomeFunc
{
StopWatch* stopWatch = [StopWatch stopWatch];
[stopWatch Start];
... do stuff
[stopWatch StopWithContext:[NSString stringWithFormat:#"Created %d Records",[records count]]];
}
There is an equivalent version in C++ (for .mm or .cpp implementations) if you need it. You can find that here.
Even if you don't use the class specifically, the general technique will allow you to get high accurate time differences for events.
NOTE I could cache the mach_timebase_info_data_t if it is the first time and save the call to get it. This is older code and does not have that (minor) optimization.
I'm writing a command line foundation tool in Mac OS X and would like the tool to quit on a keypress such as 'q'. The code is launching an asynchronous request for retrieving data from a remote server. This necessitates the NSRunLoop. At least that's what I understand I need to do.
Can someone tell me how to stop the runloop on the specific keypress?
Below is the code snippet.
int main (int argc, const char * argv[]) {
BOOL keepRunning = YES;
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Requestor *myRequestor = [[Requestor alloc] init];
[myRequestor GetData];
NSRunLoop *runLoop;
runLoop = [NSRunLoop currentRunLoop];
while (keepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
[pool drain];
return 0;
}
Thank you!
I haven't done this myself, but I would expect that you want to use [[NSFileHandle fileHandleWithStandardInput] readInBackgroundAndNotify] and register to receive the NSFileHandleReadCompletionNotification notification. If you receive a 'q', do what ever cleanup you need to and call exit().
If you haven't already considered libcurses, perhaps that will help you. It's really straightforward to catch keypresses with it, but what I'm not 100% about is if you can get it to work without the entire terminal window being used.
The curses bit alone is just:
#include <ncurses.h>
initscr();
/* snip */
char c;
while (c = getch()) {
if (c == 'q') {
// Put your cleanup and shutdown logic here
}
/* any other keypresses you might want to handle */
}
EDIT | You probably don't wanna put that tight loop inside your run loop... just call getch() each time the runloop ticks over.
Well, my initial theories and experiments starting with your existing code didn't turn up much in the way of usable code.
I'm imagining that what you're looking for is something like how, on Windows, you can run something in a command line shell window, and when the process has finished, it says something like "Press the Q key to continue...". When you bring the window forward (if it isn't already frontmost), and press the Q key, the window closes.
Are you planning on calling this command line tool from your primary application, or is this command line tool something the end-user will be interacting with directly? (For example, if the latter, they'd be calling it from a Terminal window, hmm, then I think Ken's code could probably be combined with mine to make the following. Note that in its current form, this only works after you press Q and then hit Return?
#import <Cocoa/Cocoa.h>
#interface Requestor : NSObject <NSApplicationDelegate> {
BOOL gotData;
NSFileHandle *stdIn;
}
- (void)getData;
- (void)requestorGotData:(id)sender;
#end
#implementation Requestor
- (id)init {
if (self = [super init]) {
gotData = NO;
stdIn = nil;
[NSApp setDelegate:self];
}
return self;
}
- (void)getData {
NSLog(#"getting data.........");
gotData = NO;
[self performSelector:#selector(requestorGotData:)
withObject:nil
afterDelay:5.0];
}
break
- (void)requestorGotData:(id)sender {
NSLog(#"got data");
gotData = YES;
NSLog(#"Press 'Q' key to continue...");
stdIn = [[NSFileHandle fileHandleWithStandardInput] retain];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(fileHandleReadCompletion:)
name:NSFileHandleReadCompletionNotification
object:stdIn];
[stdIn readInBackgroundAndNotify];
}
- (void)fileHandleReadCompletion:(NSNotification *)notification {
NSLog(#"fileHandleReadCompletion:");
NSData *data = [[notification userInfo]
objectForKey:NSFileHandleNotificationDataItem];
NSLog(#"data == %#", data);
NSString *string = [[[NSString alloc]
initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
if (string) {
string = [string stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([[string lowercaseString] isEqualToString:#"q"]) {
[stdIn closeFile];
[stdIn release];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[NSApp terminate:nil];
} else {
[stdIn readInBackgroundAndNotify];
}
}
}
#end
break
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[NSApplication sharedApplication];
Requestor *requestor = [[Requestor alloc] init];
[requestor getData];
[NSApp run];
[requestor release];
[pool drain];
return 0;
}
I'm trying to learn how to use the NSInputStream class on the iPhone using a unit test. I can get the NSStream to read data from a file using the polling method but for some reason the delegate/event method is not working.
I've posted the relevant code below. Please ignore memory leak errors and such since I'm just trying to ensure I know how to use the NSStream class in a sandboxed environment before rolling it into my larger project.
I'm wondering if maybe I'm missing something with regards to how the run loops work?
This is the logic test that creates a streamer class to read from a file.
#import "StreamingTests.h"
#import "Streamer.h"
#implementation StreamingTests
- (void) testStream {
NSLog(#"Starting stream test.");
Streamer * streamer = [[Streamer alloc] init];
streamer.usePolling = NO;
streamer.readingStream = YES;
NSThread * readThread = [[NSThread alloc] initWithTarget:streamer selector:#selector(startStreamRead:) object:nil];
[readThread start];
while(streamer.readingStream) {
[NSThread sleepForTimeInterval:0.5];
}
[readThread cancel];
}
#end
This is a simple test helper object that reads from an NSStream. When usePolling == YES it read data and outputs the appropriate NSLog messages. However, if usePolling == NO the delegate stream event function is never called.
#implementation Streamer
#synthesize readingStream, usePolling;
- (void) startStreamRead:(NSObject*) context {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSLog(#"starting stream read.");
readingStream = YES;
/*
NSURL * url = [NSURL URLWithString:#"http://www.google.com"];
NSLog(#"Loading: %#",[url description]);
NSInputStream * inStream = [[NSInputStream alloc] initWithURL:url];
*/
NSInputStream * inStream = [[NSInputStream alloc] initWithFileAtPath:#"sample.ttc"];
if(!usePolling) {
[inStream setDelegate: self];
[inStream scheduleInRunLoop: [NSRunLoop currentRunLoop]
forMode: NSDefaultRunLoopMode];
}
[inStream open];
if(usePolling) {
while(1) {
if([inStream hasBytesAvailable]) {
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)inStream read:buf maxLength:1024];
NSLog(#"Read: %d",len);
}
NSStreamStatus status = [inStream streamStatus];
if(status != NSStreamStatusOpen && status != NSStreamStatusOpening) {
NSLog(#"Stream not open.");
break;
}
}
readingStream = NO;
NSStreamStatus status = [inStream streamStatus];
NSError * error = [inStream streamError];
NSLog(#"Status: %d Error Desc: %# Reason: %#",(int)status,[error localizedDescription], [error localizedFailureReason]);
[pool release];
}
}
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
NSMutableData * _data = nil;
NSNumber * bytesRead = nil;
NSLog(#"Event fired.");
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if(!_data) {
_data = [[NSMutableData data] retain];
}
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)stream read:buf maxLength:1024];
if(len) {
[_data appendBytes:(const void *)buf length:len];
// bytesRead is an instance variable of type NSNumber.
//[bytesRead setIntValue:[bytesRead intValue]+len];
NSLog(#"Read %d bytes",len);
} else {
NSLog(#"no buffer!");
}
break;
}
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[stream release];
stream = nil; // stream is ivar, so reinit it
readingStream = NO;
break;
}
default:
{
NSLog(#"Another event occurred.");
break;
}
// continued ...
}
}
#end
Thanks in advance,
b
The reason for it should be that the run loop is blocked since the unit test is executing. You could refer to the NSRunLoop documentation where the method
runUntilDate:
might help you to run the main run loop in the thread of execution of the unit test like this:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
This lets the run loop run for 1 second giving it time to process part of your file. It should be noted that this does not provide a reliable way for unit testing (since the time interval might differ depending on run loop size) and may then be unsuitable. By giving your unit an interface that could be used to check the status of the input stream read operation (with a reading finished state) such as
-(BOOL)hasFinishedReadingFile
the unit test could repeatedly execute the run loop until the above method returns TRUE and the file is read completely.
Addition: This question on stackoverflow also deals with the problem in a different way.