Objective-C Perform method at given time daily - messy implementation - iphone

I have the following (terible) method that constantly checks the current time, and when a certain time is reached (in this case midnight) an NSLog statement is run once to signify something useful being done:
- (void) checkTime {
while (true){
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
NSDate *now = [[NSDate alloc] init];
NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init];
[outputFormatter setDateFormat:#"HH:mm"];
NSString *nowDateString = [outputFormatter stringFromDate:now];
if([nowDateString isEqualToString:#"00:00"]){
NSLog(#"Store previous days data..");
BOOL stillMidnight = YES;
while(stillMidnight == YES){
NSDate *latestNow = [[NSDate alloc] init];
NSDateFormatter *latestOutputFormatter = [[NSDateFormatter alloc] init];
[latestOutputFormatter setDateFormat:#"HH:mm"];
NSString *latestString = [latestOutputFormatter stringFromDate:latestNow];
//Check if it is still midnight
if([latestString isEqualToString:#"00:01"]){
//leave while
stillMidnight = NO;
}
}
NSLog(#"No longer midnight");
}
[loopPool drain];
}
}
The above method gets called as follows from the applicationDidFinishLaunchingWithOption method:
[self performSelectorInBackground:#selector(checkTime) withObject:nil];
This code runs the NSLog(#"Store previous days data..") once at midnight, which is what I need, but is there a more elegant solution to this problem?
Thanks,
Jack

You'd be better off:
getting the current date;
working out when midnight was today;
scheduling a one-shot timer to occur one day after that; and
repeating
It's tempting to just schedule a repeating timer that first fires with the date calculated at (3) and every 24 hours hence, but that would fail to allow for daylight savings. So, e.g. (coded directly in here, untested)
- (void)scheduleNextTimedAction
{
// get the date now and the calendar the user is using
// (which will include their time zone, helpfully)
NSDate *dateNow = [NSDate date];
NSCalendar *relevantCalendar = [NSCalendar currentCalendar];
// decompose the current date to components; we'll
// just ask for month, day and year here for brevity;
// check out the other calendar units to decide whether
// that's something you consider acceptable
NSDateComponents *componentsForNow =
[relevantCalendar components:
NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit
fromDate:dateNow];
// we could explicitly set the time to midnight now,
// but since that's 00:00 it'll already be the value
// in the date components per the standard Cocoa object
// creation components, so...
// get the midnight that last occurred
NSDate *lastMidnight = [relevantCalendar dateFromComponents:componentsForNow];
// can we just add 24 hours to that? No, because of DST. So...
// create components that specify '1 day', however long that may be
NSDateComponents *oneDay = [[NSDateComponents alloc] init];
oneDay.day = 1;
// hence ask the calendar what the next midnight will be
NSDate *nextMidnight = [relevantCalendar
dateByAddingComponents:oneDay
toDate:lastMidnight
options:0];
[oneDay release];
// now create a timer to fire at the next midnight, to call
// our periodic function. NB: there's no convenience factory
// method that takes an NSDate, so we'll have to alloc/init
NSTimer *timer = [[NSTimer alloc]
initWithFireDate:nextMidnight
interval:0.0 // we're not going to repeat, so...
target:self
selector:#selector(doTimedAction:)
userInfo:nil
repeats:NO];
// schedule the timer on the current run loop
[[NSRunLoop currentRunLoop]
addTimer:timer
forMode: NSDefaultRunLoopMode];
// timer is retained by the run loop, so we can forget about it
[timer release];
}
- (void)doTimedAction:(NSTimer *)timer
{
NSLog(#"do action");
[self scheduleNextTimedAction];
}

If you want to execute code at an arbitrary point in time, you best set up Local Notifications.

You can use either an UILocalNotification if the timer should also alert the user when your app is not running Push notification guide or NSTimer, which can be initialized with firing date or interval as well as selector to be called. Note that NSTimer will not fire if your app is in the background but in this case rather will fire the moment your app gets active again.

Related

Adding one millisecond to current time objectivec

Is there any way to add one millisecond in to current time in objective c . I will fetch the timestamp from server and want to show with millisecond repeatedly ( I dont want to use the system time) .Any help is appreciated .
Thanks in advance .
Read the timestamp into an NSDate. Than use
+ (id)dateWithTimeInterval:(NSTimeInterval)seconds sinceDate:(NSDate *)date
Should work.
You need to use an object of the NSTimer class.
Some sample code:
-(void)startTimer {
theTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/1000.0 target:self selector:#selector(updateTimer) userInfo:nil repeats:YES];
//the method updateTimer will be called once every millisecond
}
-(void)updateTimer {
//add one millisecond to the global time variable and display it in a label
}
-(void)stopTimer {
[theTimer invalidate]; //pauses the timer
}
Edit
For using a global Time variable, NSDate is probably the right class.
You can init the object using a time interval, for eg. 600 seconds before now:
NSDate *globalDateTime = [[NSDate alloc] initWithTimeIntervalSinceNow:-600];
To add 1 millisecond to the time:
globalDateTime = [globalDateTime dateByAddingTimeInterval:1.0/1000.0];
And to populate your global time variable in a string:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss"]; // use yyyy-MM-dd if you need to show the year, month or day
NSString *dateTimeString = [dateFormatter stringFromDate:globalDateTime];
The NSTimeInterval class "yields sub-millisecond precision over a range of 10,000 years."
Refer to apple's documentation on NSDate for more details:

Local time on the iPhone

I need to display the exact local time and the time should be running.
Please help, if possible with sample code or explanation.
Thanks.
This returns the current date with Time
[NSDate date];
Now you can use NStimer to fetch the time every one second and display it.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(displayTime) userInfo:nil repeats:NO];
//Time Interval is one second
-(void) displayTime
{
NSDate *currentDate = [NSDate date];
NSDateFormatter *timeFormatter = [[[NSDateFormatter alloc]init]autorelease];
timeFormatter.dateFormat = #"HH:mm:ss a";
NSString *dateString = [timeFormatter stringFromDate: currentDate];
yourlabel.text = dateString;
[timeFormatter release]
}

Consistent 1-Second-Long lag at 0:02 during NSTimer in "stopwatch-like" app

I have an app that uses a stopwatch-style count up from 0 in HH:mm:ss format. The code looks pretty straightforward to me, and I can't think of a more efficient way to run it.
For some reason, when I run it, there is a very noticeable and consistent (every time I run it, in the same place) lag when the timer gets to 00:00:02. It stays on 00:00:02 for a full second, and then counts on normally. Why would this happen?
-(IBAction)startAndStop;
{
if (!timer) {
NSLog(#"Pressing Start Button");
[startAndStopButton setTitle:#"Stop" forState:0];
startDate = [[NSDate date] retain];
timerLabel.text = #"00:00:00";
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(timerStart)
userInfo:nil
repeats:YES];
} else {
NSLog(#"Pressing Stop Button");
[startAndStopButton setTitle:#"Start" forState:0];
[startDate release];
[timer invalidate];
timer = nil;
[timer release];
}
}
-(void)timerStart
{
NSDate *currentDate = [NSDate date];
NSTimeInterval countInSeconds = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSinceReferenceDate:countInSeconds];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"HH:mm:ss"];
[df setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString = [df stringFromDate:timerDate];
[df release];
timerLabel.text = timeString;
}
NSTimer does not fire at exact times or time intervals (check the specification for the likely error). Thus it is possible for one late firing and one early firing to occur during the same clock second, when rounded to the nearest second, and you will see a stutter effect.
Instead, use a much faster timer (or CADisplaylink), say at 30 Hz, check the time, and update the label only if the time has changed enough to change the label (one second).
The interval you are passing is in seconds:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/Reference/NSTimer.html
My guess is that it is getting called immediately, and then every 1 second afterwards, since you are passing 1 second as the timer interval. Try passing something like 1.0/20.0 to update at a higher frame rate.

call a method with NSDate?

Im writing a simple clock app and am looking for a way to call a method when the hour changes (or minute of second etc etc). If someone could point me in the right direction for this i would much appreciate it.Perhaps the kind of instance that calls a method for an alarm, im not sure?
You could schedule an NSTimer with -initWithFireDate:..., with the fire date set to whole hours.
If you're writing a clock app with precision down to seconds for humans, it is easier to ignore sub-second differences and use +scheduledTimerWithTimeInterval:... directly, e.g.
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateClock)
userInfo:nil
repeats:YES];
...
-(void)updateClock {
NSDate* now = [NSDate date];
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* dateComps = [calendar components:NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit
fromDate:now];
NSInteger hourNow = [dateComps hour];
NSInteger minuteNow = [dateComps minute];
NSInteger secondNow = [dateComps second];
// update clock view using 'hourNow', 'minuteNow' and 'secondNow'
if (secondNow == 0) {
// whole minute
if (minuteNow == 0) {
// whole hour
}
// (note: not very reliable since a timer does not need to fire on schedule)
}

Registering a Notification in iPhone SDK 3.0

In iPhone SDK 3.0, I would like to register for a notification, which would alert my application when a certain time is reached. Is it possible?
Thanks
Set up an NSTimer that runs a selector every 30 seconds (or whatever granularity you need):
[NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES];
The -timerFired: selector (method) will run every thirty seconds and check the hour, minute and second components, firing a notification if elements match the desired time:
- (void) timerFired:(NSNotification *)notification {
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSCalendarUnit unitFlags = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDate *date = [NSDate date];
NSDateComponents *dateComponents = [calendar components:unitFlags fromDate:date];
NSInteger hour = [dateComponents hour];
NSInteger min = [dateComponents minute];
NSInteger sec = [dateComponents second];
if ((hour == kDesiredHour) && (min == kDesiredMinute) && (sec == kDesiredSecond)) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"kTimeComponentsWereMatched" object:nil userInfo:nil];
}
}
You register to listen for this notification in some other class somewhere:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSomething:) name:#"kTimeComponentsWereMatched" object:nil];
Accordingly, you have a method in that same class that does something interesting:
- (void) doSomething:(NSNotification *)notification {
// do something interesting here...
}
You can merge this code if it is all in one class. Or specify the target in the NSTimer to point to the class instance that you want to run the selector in.
Assuming you have a NSDate in the variable date, and want to fire the method dateIsHere: at that date, do this:
NSTimer* timer = [[NSTimer alloc] initWithFireDate:date
interval:0.0f
target:self
selector:#selector(dateIsHere:)
userInfo:nil
repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:timer
forMode:NSDefaultRunLoopMode];
[timer release];
I'm assuming you want this timer to fire, even if the app is closed. You can use a notification for that but you would have to have a server that issued the notification.
Furthermore, the iPhone would pull up an alert asking the user to open the app - but they could chose not to do so.
You would start by setting an NSTimer to fire at a certain date, and the selector that fires could be whatever you want. No need to use NSNotifications.
You are looking for "Local Notifications", which is being implemented in iOS since version 4.0:
http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008194-CH1-SW1
This should be the correct answer for >=4.0. For earlier versions, there is probably still only the NSNotifications (implementing push is too much of a hassle for most)