I am running into a situation where I'm not able to properly handle NSTimer.
In my app, I've an option of user chats (I'm not using XMPP because of a low budget project, but the chat is working through API calls ONLY). I've scheduled a timer at a time interval of 15 seconds. If any new chats available I'll get it and will update chat view.
Here's the working scenario:
As this is a UITabbar based app, a user will come to "Chat" tab.
A User will have a list of persons with whom he can chat.
A User will select any of a user – will push to Chat Screen.
Where all locally saved chats will be visible and an API call will be made for new chats, on success (or error) of API call, a timer will be scheduled to sync chats at a time interval of 15 seconds.
If a user goes back (pops), in viewDidDisappear: method, I'm invalidating the (running) timer.
In my Unit testing, if I'll continuously push & pop to/from Chat screen, there'll be multiple instances of that timer will get scheduled. I suspect, this is WRONG.
I'm not sure what I'm doing is correct or not though I need your help to understand the right and the correct way to get my functionality done. I guess here there's no need of the code for above explanation.
First of all, why are you not exploring option of push notification? Polling server every 15 second is a bad design :-(.
Second, when it comes to NSTimer it is important to start and stop them from the same thread. I would advise you encapsulate your timer start/stop code in below block always ensuring you deal on main thread with your timer.
dispatch_async(dispatch_get_main_queue(), ^{
});
This is the way o usually work with NSTimer. Hope it helps.
#implementation MainViewController{
NSTimer *myTimer
}
- (void)startTimer{
//Prevents multiple timers at the same time.
[myTimer invalidate];
myTimer = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:#selector(update) userInfo:nil repeats:YES];
}
- (void)update
{
//Stops the timer if the view in not on the screen
if (!(self.isViewLoaded && self.view.window)) {
[myTimer invalidate];
}
}
#end
I'm trying to do something quite simple : Stopping a parse.com query after a few seconds, with an NSTimer. I've read after some reasearch it's a good "trick" to use.
Here is how I create my timer :
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:6.0
target:self
selector:#selector(stopRetries:)
userInfo:#{#"query":query}
repeats:NO];
Because i'm running this on a background thread (and outside a viewcontroller class), the timer is inside a dispatch_sync(dispatch_get_main_queue());
But whatever I do, I cannot stop the query, because [query cancel] doesn't do anything. I can't pass it in the userInfo of the timer. Breakpoints show it has an address and is "there" but it looks like junk inside.
What can I be doing wrong and what should I be doing instead?
My main goal is to make the parse.com query stop faster than 30 seconds, and warn the user with an alert.
You can Try this [self performSelector:#selector(abc) withObject:nil afterDelay:6.0];
or
You can Invalidate the timer
I have a method which displays a clock with seconds and the current time. This works fine except that this code will get called either half way through the current second or three quarters of the way through the current second depending on what time I open the app or run it. The method is called through the viewDidLoad method. When this happens my clock will be off up to almost 1 second. Is there any way to start my method when the next second start exactly? i.e. start it when the devices time is HH:MM:SS.000? Note: sorry if this is confusing with the excessive use of second and clock. I just mean I need to start my method at HH:MM:SS.000 (devices internal clock)
Using:
- (id)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds
target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo
repeats:(BOOL)repeats
With an object of NSTimer is probably the way to go.
Add the logic found in this StackOverflow question/answers and you should be able to get it right on an exact second. (Use the logic there to create an NSDate object with resolution to 1 second, then use that date in the method I mentioned above).
NSTimer *yourTimer = [[NSTimer alloc] initWithFireDate:nowToTheSecond
interval:1.0 target:self selector:#selector(updateClock) userInfo:nil
repeats:YES];
[[NSRunLoop mainLoop] addTimer:yourTimer forMode:NSRunLoopCommonModes];
NSTimer objects are not exact. They depend on the app visiting the event loop frequently, and can vary by 50 MS or more (according to what I've read in the docs). If I remember correctly they try to "snap back" to the desired time interval rather than drifting, but any given firing will not be exact.
That said, I guess what I would do is to take the current NSDate, convert it to an NSTimeInterval, take the ceiling value (the next higher whole number) and start a one-time timer that will fire at that moment. Then in the handler for that timer, start a once-a-second timer. Something like this:
//Get the current date in seconds since there reference date.
NSTimeInterval nowInterval =[NSDate timeInervalSinceReferenceDate];
//Figure out the next even second time interval.
NSTimeInterval nextWholeSecond = ceil(nowInterval);
//Figure out the fractional time between now and the next even second
NSTimeInterval fractionUntilNextSecond = nextWholeSecond - nowInterval;
//Start a one-time timer that will go off at the next whole second.
NSTimer oneTimeTimer = [NSTimer timerWithTimeInterval: fractionUntilNextSecond
target: self
#selector: (startSecondTimer:)
userInfo: nil
repeats: NO];
And the startSecondTimer method:
- (void) startSecondTimer: (NSTimer *)timer;
{
//Start a new, repeating timer that fires once per second, on the second.
self.secondsTimer = [NSTimer timerWithTimeInterval: 1.0
target: self
#selector: (handleSecondTimer:)
userInfo: nil
repeats: YES];
}
You should still calculate the new time in each call to your handleSecondTimer: method rather than relying on the number of times you are called, because if the system gets really busy at the moment when it's supposed to call your timer and can't get to you, it might skip a call completely.
Disclaimer: I haven't tried this, but it should work. My only concern is edge cases. For example, when the next whole second is too close to now and the one-time timer can't fire fast enough. It might be safer to add a second to the fractionUntilNextSecond value, so the second hand doesn't start running for greeter than 1 second but less than 2 seconds.
I am pretty new to bonjour/networking with ObjC (although well versed in other areas!) I am asking for a bit of advice - I have an iOS app that will run on multiple iPads, in a store. The apps occasionally have to share some data, and the internet isn't always available so a webservice is not an option, hence I decided on using bonjour.
I have setup the Bonjour/NSNetservices and everything is functioning correctly, the ipads basically form an 'ad-hoc network' and connect automatically at app launch, however I am looking for advice for the following situation:
The app normally shares data in the background, without any user intervention - however there is one function where when a button is pressed on one app, data should be returned from another app remotely. The UI then updates when the data has been received from the other device - however if the connection should be lost to the other device, the data will never reach the users device, and the data will not be displayed. I am wanting to implement some form of timout, but unsure how to do this - any suggestions would be much appreciated!
The process flow for this is something like this:
button press on 'dev 1' > 'dev 1' broadcasts 'dev 2 please send data message' > 'dev 2' responds with requested data [timeout required here] > UI is updated if data is received /[if timeout fires, error message is displayed]
So I really just need a timeout for the last section - and I really cannot think of a way to implement it.
I can post code for this if required.
Thanks!
This solution may work if you have the possibility to manually cancel the request.
I believe you can use a simple NSTimer to cancel the request after a wait. I'm sure it's not the best solution, but it will probably work.
Here's how I would do it.
Create your timer and an NSInteger (to store the timer value) in your class :
NSTimer *timer;
NSInteger timerValue;
Call this method with timer = [self startTimer]; when you fire your request :
- (NSTimer*)startTimer {
timerValue = 30;
return [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTicked:) userInfo:nil repeats:YES];
}
Implement the timerTicked: method :
- (void)timerTicked:(NSTimer*)timer {
timerValue --;
if (timerValue <= 0) {
// Cancel the request
// Show an alert
}
}
You can cancel the timer with [timer invalidate]; but remember this will "destroy" your timer, so it won't fire events ever again.
As you've not indicated how you are currently requesting data from your other device, I have had to make some assumptions.
You can use NSURLRequest with a timeout using requestWithURL:cachePolicy:timeoutInterval:
See NSURLRequest documentation
Note: further down in the edits there's simple code that generates the problem without the full complexity of my original program.
I'm trying to code an alarm-clock app for jailbroken iOS. I have a UI set up as a standalone application for scheduling the alarms, which then saves the alarm information to disk. The save file is read by a launch daemon that's always running, which deals with actually scheduling the alarms.
I'm scheduling the alarms as so (EDIT: in the daemon) (NSDate *fireDate is calculated earlier):
NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:fireDate
interval:0
target:self
selector:#selector(soundAlarm:)
userInfo:alarm
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:singleTimer
forMode:NSRunLoopCommonModes];
[self.timers addObject:singleTimer];
[singleTimer release];
EDIT: the above code runs in a method called createTimers, which gets called by reloadData. reloadData reads information about the timers from the shared save file, and it gets called in AMMQRDaemonManager's init function, as well as whenever the manager gets a notification (with notify_post) that the UI app has updated the save file.
The soundAlarm: method (EDIT: also in the daemon) is:
- (void)soundAlarm:(NSTimer *)theTimer {
NSLog(#"qralarmdaemon: sounding alarm");
extern CFStringRef kCFUserNotificationAlertTopMostKey;
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(dict, kCFUserNotificationAlertTopMostKey, kCFBooleanTrue);
CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, CFSTR("Title"));
CFDictionaryAddValue(dict,kCFUserNotificationDefaultButtonTitleKey, CFSTR("OK"));
SInt32 err = 0;
CFUserNotificationRef notif = CFUserNotificationCreate(NULL,
0, kCFUserNotificationPlainAlertLevel, &err, dict);
CFOptionFlags response;
if((err) || (CFUserNotificationReceiveResponse(notif, 0, &response))) {
// do stuff
} else if((response & 0x3) == kCFUserNotificationDefaultResponse) {
// do stuff
}
CFRelease(dict);
CFRelease(notif);
// Do some other stuff
}
This works great, and shows the alert whether the phone is unlocked or locked. But if the phone is locked for a sufficient period of time to enter deep sleep then the timer just fails to fire.
I don't need it to necessarily turn the screen on (though that would be nice) since I'll also be playing sound in addition to displaying the alert, but I do need the timer to fire so that I know when to start the sound.
Any ideas?
EDIT: Here is the main function for the daemon.
int main(int argc, char **argv, char **envp) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"qralarmdaemon: launched");
AMMQRDaemonManager *manager = [[AMMQRDaemonManager alloc] init];
NSTimer *keepRunningTimer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture]
interval:1000
target:manager
selector:#selector(keepRunning:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:keepRunningTimer
forMode:NSRunLoopCommonModes];
// Execute run loop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
[manager release];
NSLog(#"qralarmdaemon: exiting");
[pool release];
return 0;
}
(Not included is the code that registers for notifications from the main app to know when to read in the save file, etc, but I don't think that's relevant).
EDIT (again): I've added a timer to the run loop that fires at [NSDate distantFuture]. This seems to preserve the timers longer (a timer scheduled 1 min 45 secs after the phone was locked went off, and woke up the phone) but not indefinitely (a timer scheduled 7 min, 30 seconds after the phone was locked did not go off).
EDIT: I've constructed the following toy example that illustrates the problem, without having to worry about interactions with other parts of my code.
I compiled this code, SSH'd in, and ran it, then locked my phone. If I change the dateByAddingTimeInterval:480 to dateByAddingTimeInterval:30, I get the following output:
2013-03-31 12:21:25.555 daemontimertest[6160:707] daemon-timer-test: launched
2013-03-31 12:21:56.265 daemontimertest[6160:707] daemon-timer-test: timer fired
But when it's set to 480, I wait more than 8 minutes and only see the first line:
2013-03-31 12:08:09.331 daemontimertest[6049:707] daemon-timer-test: launched
main.m:
#import "MyClass.h"
int main(int argc, char **argv, char **envp) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"daemon-timer-test: launched");
MyClass *obj = [[MyClass alloc] init];
NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:[[NSDate date] dateByAddingTimeInterval:480]
interval:0
target:obj
selector:#selector(fireTimer:)
userInfo:nil
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:singleTimer
forMode:NSRunLoopCommonModes];
// Execute run loop
[[NSRunLoop currentRunLoop] run];
[pool release];
return 0;
}
MyClass.m:
#import "MyClass.h"
#implementation MyClass
- (void)fireTimer:(NSTimer *)theTimer {
NSLog(#"daemon-timer-test: timer fired");
}
#end
EDIT (3/31/13 5:50 EDT): I've added the following code the toy app code to incorporate Nate's suggestion of using GCD's dispatch_after functionality, but it appears subject to the same time constraints. As an additional note, the main UI app is installed in /Applications and the daemon is installed in /usr/bin.
double delayInSeconds = 10.0;
NSLog(#"daemon-timer-test: delay is %f",delayInSeconds);
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"daemon-timer-test: time has passed.");
});
EDIT (3/31 5:54 PM): Another quick note. The following lines show up (not consecutively) in syslog right before it appears to go into deep sleep and there are no more messages before I wake the phone up. I've selected the ones that look like they may be relevant; the last message is the very last one sent to syslog before deep sleep.
Mar 31 17:34:23 Andrew-MacKie-Masons-iPhone lockdownd[50]: 002c1000 -[hostWatcher handleSleepNotification:service:messageArgument:]: <hostWatcher: 0x1cd59890> [CC535EDB-0413-4E5E-A844-4DA035E7217C 169.254.2.141:54757] [fd=13]: kIOMessageCanSystemSleep
Mar 31 17:34:23 Andrew-MacKie-Masons-iPhone lockdownd[50]: 002c1000 -[hostWatcher handleSleepNotification:service:messageArgument:]: <hostWatcher: 0x1cd59890> [CC535EDB-0413-4E5E-A844-4DA035E7217C 169.254.2.141:54757] [fd=13]: kIOMessageSystemWillSleep
...
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone lockdownd[50]: 00343000 __63-[hostWatcher handleSleepNotification:service:messageArgument:]_block_invoke_0: Allowing Sleep
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone powerd[42]: PM scheduled RTC wake event: WakeImmediate inDelta=645.40
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone powerd[42]: Idle Sleep Sleep: Using BATT (Charge:76%)
...
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone kernel[0]: en0::stopOutputQueues
...
Mar 31 17:34:29 Andrew-MacKie-Masons-iPhone kernel[0]: pmu wake events: menu
Short Answer
Yes, it's possible (I've done it).
I tried a few different ways, and I was not able to get my daemon/NSTimer to fail in the way you're describing. However, I haven't seen all the files/code that defines your app, so there's at least one more thing I'm concerned about.
Keeping the Daemon Alive
If you look in the Apple docs for NSRunLoop run:
If no input sources or timers are attached to the run loop, this
method exits immediately; otherwise, it runs the receiver in the
NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In
other words, this method effectively begins an infinite loop that
processes data from the run loop’s input sources and timers.
Manually removing all known input sources and timers from the run loop
is not a guarantee that the run loop will exit. OS X 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.
In the code you show for you daemon's main program, you don't (directly) create any timers. Of course, I don't know what you do in [[AMMQRDaemonManager alloc] init], so maybe I'm wrong. You then use:
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
to start the run loop. The problem is, if there are no timers at this point, I'm not sure your daemon is going to stay alive. If you look at the second paragraph above, it also indicates that it might stay alive, so maybe that's why I don't see my daemon die when I attempt to use your code.
Your comment says that you see the daemon process alive, when the alarm should go off. However, I'm wondering if maybe your daemon process did die, and then was restarted. Maybe you could also show us the .plist file you use for your Launch Daemon (that goes in /System/Library/LaunchDaemons).
One quick experiment, might be to not start your daemon automatically. Just uninstall the plist file from the LaunchDaemons folder, and make sure you kill the process. Then, start it manually from the command line, ssh'd into the phone:
$ /Applications/MyApp.app/MyDaemon
then, watch the command line. You'll see if it dies or not, and since it's not actually being run by launchd, it won't get restarted if it does die.
Solution?
If it turns out that you do have problems with it dying, then I would try adding a timer that always starts when you daemon does. If you look at my other example, or Chris Alvares' daemon tutorial, it shows this. In the daemon main(), you set one NSTimer to fire a run: method. In that run: method, you could use a while loop and a sleep() call. Or just schedule the timer to repeat at some slow interval.
I'm also not sure how your entire app works. Is it only a tool for scheduling (NSTimer) alarms? If so, it's possible that at any time, there might be no alarms set. Maybe another solution, instead of using the UIApplication to notify_post() to communicate a new timer to the daemon, you could configure the daemon to simply watch a data file. The UIApplication would write out the data file, whenever there is a new timer. Then, iOS could wake your daemon to schedule the NSTimer.
Anyway, this may be a separate issue from your original problem, but it also might be a more efficient way to build an alarm clock daemon, since it doesn't really need to run if there's no alarms active.
Post more if these ideas don't help you fix it (the body of [AMMQRDaemonManager init] might help).
Update
Two more suggestions:
make sure your app (daemon and UI) are installed in /Applications. This is the normal location for jailbreak apps, but I just wanted to make sure you weren't installing it in the sandbox area.
try replacing your NSTimer implementation (for the alarms, you can leave the main() daemon keepalive timer as is) with GCD blocks:
// you have used notify_post() to tell the daemon to schedule a new alarm:
double delayInSeconds = 1000.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// put timer expiration code here
});
Update II
I also noticed that in your original alarm: callback, you use CFUserNotificationReceiveResponse() with an infinite timeout. That means that if the user doesn't dismiss the popup, the timer callback won't complete, and I believe that means that no subsequently scheduled timer callbacks can fire. Probably, you should put all the CFUserNotification code into its own method (e.g. showPopup), and then have your timer callback like so:
- (void)soundAlarm:(NSTimer *)theTimer {
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self showPopup];
});
}
Then, there's the main program (in the code you put on Dropbox). I would recommend changing your main timer (that you call directly from main()) to be a repeating timer, with a relatively small interval, instead of using a fire date with distantFuture. If you want, you can do nothing in it. It's just a heartbeat.
main.m:
NSTimer *singleTimer = [[NSTimer alloc] initWithFireDate:[NSDate date]
interval:5*60 // 5 minutes
target:obj
selector:#selector(heartbeat:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:singleTimer
forMode:NSRunLoopCommonModes];
MyClass.m:
- (void)heartbeat:(NSTimer *)theTimer {
NSLog(#"daemon-timer-test: heartbeat timer fired");
}
My last comment is that I don't use syslogd. I'm wondering if any of your tests are failing, not because timers aren't running, but because NSLog statements aren't showing up in your log file. I've done all tests where I actually run the daemon executable at the command line, ssh'd into the phone, and I just watch the console for NSLog output. Take logging out of the list of possible failure points ...
I've worked out a method that works for me. As per my long exchange with Nate (and I definitely wouldn't have been able to work out what was going on without his help), this seems to happen automatically on some systems, but not on others. The problem on my phone seemed to be that powerd was putting the phone into some sort of deep sleep that paused the NSTimers and didn't allow them to fire properly.
Rather than disabling deep sleep (which I suspect has negative power implications) I scheduled a power event:
NSDate *wakeTime = [[NSDate date] dateByAddingTimeInterval:(delayInSeconds - 10)];
int reply = IOPMSchedulePowerEvent((CFDateRef)wakeTime, CFSTR("com.amm.daemontimertest"), CFSTR(kIOPMAutoWake));
This successfully wakes the phone 10 seconds before the alarm is supposed to go off. (The interval isn't precise. I wanted it to be short enough that the phone wouldn't go back to sleep, but long enough that if the phone takes a moment to wake up the timer can still go at the right time. I'll probably shorten it to just 3 or 4 seconds.
The remaining problem is that the NSTimer for the alarm itself won't update automatically, and so it'll be late by whatever period the phone was asleep for. To fix this you can cancel and reschedule the NSTimer whenever the phone wakes up. I did this by registering for a notification that the power management system posts whenever the power state changes:
int status, notifyToken;
status = notify_register_dispatch("com.apple.powermanagement.systempowerstate",
¬ifyToken,
dispatch_get_main_queue(), ^(int t) {
// do stuff to cancel currently running timer and schedule a new one here
});
The inefficiency here is that the notification is posted both on sleeps and wakes, but I haven't been able to find an alternative yet.
I hope this is helpful to anyone else who was struggling with this issue.