Interesting place in NSTask's source code - ios

In source code of the NSTask I've found interesting place in method waitUntilExit:
- (void) waitUntilExit
{
NSTimer *timer = nil;
while ([self isRunning])
{
NSDate *limit = [[NSDate alloc] initWithTimeIntervalSinceNow: 0.1];
if (timer == nil)
{
timer = [NSTimer scheduledTimerWithTimeInterval: 0.1
target: nil
selector: #selector(class)
userInfo: nil
repeats: YES];
}
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
beforeDate: limit];
RELEASE(limit);
}
[timer invalidate];
}
I can't understand purpose of NSTimer here. And whose method class will be called?

The timer target is nil, so the selector is actually irrelevant: You can send any message to nil which is then simply discarded.
The compiler only verifies that the selector refers to some known method, in this case the class method of the NSObject protocol.
This dummy timer is necessary for the following runMode statement
which otherwise could terminate immediately, as the NSRunLoop documentation states:
If no input sources or timers are attached to the run loop, this method exits immediately and returns NO; otherwise.

Related

NSTimer's isValid always returns YES

So I've got a timer that is not repetitive. Each time it fires, the method that being executed decide if to reschedule it or not according to some inner logic of my app.
This method is available from other parts of the app, so the first thing that I'm doing in the method is to check if the timer is still valid (to know if the initiator was the timer or a different entity) so in case it wasn't initiated by the timer I want to invalidate it:
if (self.pollingTimer.isValid) {
[self.pollingTimer invalidate];
self.pollingTimer = nil;
}
I've noticed that if the method is being called due to the timer being fired - I always receive a true value from the isValid property, even though when looking at the NSTimer documentations under the scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats method:
repeats
If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
Discussion
After seconds seconds have elapsed, the timer fires,
sending the message aSelector to target.
I'm having hard time to understand when the timer is being automatically invalidated which bring me to my questions:
Any idea why I always get YES from isValid?
What is the exact definition of the timer fires? Is it just sending the message aSelector to target as stated in the documentation? or is it finishing the execution of the method? (which might explain what I'm experiencing)
Thanks in advance.
A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. Therefore, the timer does not immediately invalidate itself, but at the end of the run loop.
As a simple test, you can see:
- (void)viewDidLoad {
[super viewDidLoad];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(timerFired) userInfo:nil repeats:NO];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.timer.isValid){
NSLog(#"--> TIMER VALID");
} else {
NSLog(#"TIMER INVALID!");
}
});
}
- (void) timerFired {
if (self.timer.isValid){
NSLog(#"--> TIMER VALID");
} else {
NSLog(#"TIMER INVALID!");
}
}
This will log --> TIMER VALID from the timerFired method and when the block from dispatch_after is called, you will see TIMER INVALID!. So, when you schedule a timer with repeats:NO, it is guaranteed to not reschedule itself but it will not invalidate immediately.
So, to answer your question:
repeats
If YES, the timer will repeatedly reschedule itself until
invalidated. If NO, the timer will be invalidated after it fires (but not immediately)
I made a test like this:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(xx) userInfo:nil repeats:NO];
- (void)xx
{
if ([self.timer isValid]) {
NSLog(#"isvalid");
}
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.timer isValid]) {
NSLog(#"isvalid");
}else{
NSLog(#"isInvalid");
}
});
static int i = 0;
NSLog(#"%d",i++);
}
and the result is:
isvalid
0
isInvalid
thus, I guess when timer is fired,the function is execute like this:
void __NSFireTimer(){
id yourObj;
[yourObj performSelector:#selector(yourSelector)];
timer.isvalid = NO;
}
what you believe is:
void __NSFireTimer(){
id yourObj;
timer.isvalid = NO;
[yourObj performSelector:#selector(yourSelector)];
}
So, just accept it.You can put your check valid code in dispatch_asyn() ,like the test code.
This is how I used my timers. First initialise it on the top as
#property (strong, nonatomic) NSTimer *refreshTimer;
then this two methods, to create and invalidate the timer. "Its very important to invalidate the current timer if you want to create another timer with same name" otherwise their will be two timers.
- (void)startTimer {
if (_refreshTimer) {
[self invalidateTimer];
}
_refreshTimer = [NSTimer scheduledTimerWithTimeInterval:15.0
target:self
selector:#selector(determineIfPartOfgroup)
userInfo:nil
repeats:YES];
}
- (void)invalidateTimer {
if (_refreshTimer) {
[_refreshTimer invalidate];
_refreshTimer = nil;
}
}
I hope this will help you.

How to make multiple NSTimers for many class instances?

I am making an iOS application that uses wireless communication. One of its features is checking if the external devices that it is connected with are responding. So what I tried to do, is to make a "Device" class for every connected device, and then for each of them create a NSTimer that would handle the timeouts. And I made it like this:
The "Device" class init:
NSTimer* communicationChecker;
- (id)initWithAddress: (NSString*) address;
{
self = [super init];
if (self)
{
_address = address;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateStatus:) name:NOTIFICATION_STATUS object:nil];
communicationChecker = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(iAmDead:) userInfo:nil repeats:YES];
self.readyToRoll = true;
}
return self;
}
The timer selector:
- (IBAction)iAmDead:(NSTimer*)sender
{
self.readyToRoll = false;
NSLog(#"%# is dead :(", self.address);
}
And the notification selector:
-(void)updateStatus:(NSNotification *) notification
{
NSDictionary* userInfo = notification.userInfo;
NSString* deviceAddress = (NSString*)userInfo[PARAM_DEVICE_ADDRESS];
if ([_address isEqualToString:deviceAddress]) {
self.readyToRoll = true;
[communicationChecker invalidate];
communicationChecker = nil;
communicationChecker = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(iAmDead:) userInfo:nil repeats:YES];
}
}
So how I thought this would work, is that every time that a notification comes for that given device, it would change its "readyToRoll" variable and reset the timer. The problem is that only one device declares that it's dead (when none of them report status), and it's the one that sent the last status report message. I really have no clue how to go about this. What causes this behaviour?
I have solved the problem by moving the NSTimer declaration from the .m file, to .h. By adding the NSTimer as a property (#property NSTimer* communicationChecker;) it is initiated for each device. Everything works as expected now.
I think that the NSTimer was initiating only once earlier, and was only restarted with different parameters. Now each device has its own timer.
Now this might be total wrong, but did you try to initialize your timers like this :
NSTimer *timer = [NSTimer timerWithTimeInterval:kUpdateTimeInterval
target:self
selector:#selector(doStuff)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
That way you ensure that they are added to the mainRunLoop, and not the "currentLoop" as done in the scheduledTimerWithTimeInterval function.
Try and let me know.

NSTimer doesn't stop with invalidate

I add timer like this
tim=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(repeatTim) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:tim forMode:NSDefaultRunLoopMode];
tim it is NSTimer property of my class.
Then i stop it on button click like
[[fbt tim] invalidate];
[fbt setTim:nil];
fbt it is instance of my class.
if i call only invalidate then it doesn't stop, but if i set it to nil then i got EXC_BREAKPOINT
here code of repeatTim method in selector
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
[appDelegate.wbv stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"intal()"]];
I tried to call init and invalidate in
dispatch_async(dispatch_get_main_queue(), ^{})
it also doesn't stop timer.
You have more than one timer running . Try this:
-(void)startTimer{
[self.myTimer invalidate]; // kill old timer
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(doSomething) userInfo:nil repeats:YES];
}
-(void)stopTimer{
[self.myTimer invalidate];
self.myTimer=nil; //set pointer to nil
}
Read documentation for NSTimer:
There are three ways to create a timer:
Use the scheduledTimerWithTimeInterval:invocation:repeats: or scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer and schedule it on the current run loop in the default mode.
Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
Allocate the timer and initialize it using the initWithFireDate:interval:target:selector:userInfo:repeats: method. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
You are using method which already adds it to mainLoop from 1. - you need to remove this line or create a timer with 2. approach and leave manual adding.
Also remember that you must send invalidate message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.
I have tried every possible solution found but not able to resolve that at the end I have set repeat "false" while initialising timer like below
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(viewcontroller.methodname), userInfo: nil, repeats: false)
And need to add above line in my selector method for whatever the condition for which I wanted to repeat the time.
For example:- My requirement is I want to repeatedly call some method until one condition satisfied. So instead of adding repeats true I set it false as repeat true does not invalidate timer in my case.
I have added below in my viewdidload method
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(viewcontroller.method), userInfo: nil, repeats: false)
in selector function I added below code:-
func method{
if condition matched{
// here your timer will be invalidated automatically
}
else{
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self,selector: #selector(viewcontroller.method), userInfo: nil,repeats: false)
}
}
Hope this will solve your problem.
Happy Coding :)

NSTimer not calling Method [duplicate]

This question already has answers here:
NSTimer timerWithTimeInterval: not working
(4 answers)
Closed 9 years ago.
I have an NSTimer that should call a method every second, but it doesn't seem to be calling it at all.
This is how I declare the timer:
[NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(fadeManager:) userInfo:nil repeats:YES];
This is the method it calls:
-(void) fadeManager: (NSTimer*) timer{
NSLog(#"IT'S WORKING!");
self.playHead += .1; // keeps track of where they are in full track
if (self.playHead >= self.wait && ![player isPlaying]) { // checks if wait is over
[player play];
}
if (self.playHead >= self.duration + self.startTime && [player isPlaying]) { // checks if full duration is over
[player pause];
[self reset];
}
int fadeOutArea = self.startTime + self.duration - self.fadeOut;
int fadeInArea = self.startTime + self.fadeIn;
if (self.playHead <= fadeInArea && [player volume] < relativeVolume) { // checks if fadingIn.
[self fadeInIncriment];
}
if (self.playHead >= fadeOutArea && [player volume] > 0) {
[self fadeOutIncriment];
}
}
The code was not working so I put the NSLog in as well as a break point. It seems that it is never being called. Why is this? Does it matter that I declared the method in the .m file like this:
#import <AVFoundation/AVFoundation.h>
#interface CueMusic ()
- (void) delayFadeOut: (AVAudioPlayer*) dFade;
- (void) fadeInIncriment;
- (void) fadeOutIncriment;
- (void) fadeManager: (NSTimer*) timer; // <--------
- (void) start;
#end
#implementation CueMusic
.......
Either use
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(fadeManager:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
or
//schedules the timer
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(fadeManager:) userInfo:nil repeats:YES];
From the docs Scheduling Timers in Run Loops
Use the scheduledTimerWithTimeInterval:invocation:repeats: or scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer and schedule it on the current run loop in the default mode.
Use the timerWithTimeInterval:invocation:repeats: or timerWithTimeInterval:target:selector:userInfo:repeats: class method to create the timer object without scheduling it on a run loop. (After creating it, you must add the timer to a run loop manually by calling the addTimer:forMode: method of the corresponding NSRunLoop object.)
Swift Code
Either
let timer: NSTimer = NSTimer(timeInterval: 1.0, target: self, selector: "fadeManager:", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
Or
//schedules the timer
NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "fadeManager:", userInfo: nil, repeats: true)
Your problem is that when using timerWithTimeInterval:target:selector:userInfo:repeats:, the resulting timer does not automatically get added to the run loop. I would recommend using scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: instead, which performs this step for you.
If you prefer to use timerWithTimeInterval:target:selector:userInfo:repeats: then you need to manually add the timer to the current run loop. To do this, call NSRunLoop's addTimer:forMode: method. Documentation
[[NSRunLoop currentRunLoop] addTimer:tTimer forMode: NSDefaultRunLoopMode];
You need to fire the timer.
You can do it by adding it to a thread:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(fadeManager:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
or calling
[timer fire];
You can use:
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

How to run NSTimer in background and sleep in iOS?

I found a lot of post in stackoverflow about NSTimer run in background.
However I didn't find any solution.
In my app , I play sound in background and I set timer to stop music when it reached that time.
So I need to run my NSTimer background (mean when home button click and sleep iPhone).
How can I do that?
// NSTimer run when app in background <br>
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
loop = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(Update) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:loop forMode:NSRunLoopCommonModes];
This is what do you want?.
You can’t
Timers only exist within your application. So (except for a very small window) when your app is sent to the background, timers cannot fire anymore.
(The audio keeps running, because it’s played by the system, and not by your app.)
So you cannot use timers for this purpose.
What you can do — as suggested by iPatel — is use a local notification, instead. This will briefly awake your app, allowing you to stop playing the music.
Get Form [This Question] (http://iphonedevsdk.com/forum/iphone-sdk-development/58643-keep-nstimer-running-when-app-is-in-background-multitasking.html)
- (void)btnSetupNotificationClicked:(id)sender
{
UILocalNotification* pOrderCompletedNotification=[[UILocalNotification alloc] init];
if(pOrderCompletedNotification!=nil)
{
[pOrderCompletedNotification setFireDate:[[NSDate alloc] initWithTimeIntervalSinceNow:5.00]];
// [pOrderCompletedNotification setApplicationIconBadgeNumber:1];
[pOrderCompletedNotification setTimeZone:[NSTimeZone systemTimeZone]];
[pOrderCompletedNotification setSoundName:#\"OrderCompleted.m4a\"];
[pOrderCompletedNotification setAlertBody:#\"Order Completed\"];
[pOrderCompletedNotification setAlertAction:nil];
[pOrderCompletedNotification setHasAction:NO];
UIApplication* pApplication=[UIApplication sharedApplication];
if(pApplication!=nil)
{
[pApplication scheduleLocalNotification:pOrderCompletedNotification];
}
else
{
NSLog(#\"Application singleton allocation error.\");
}
[pOrderCompletedNotification release];
[pApplication release];
}
else
{
NSLog(#\"Local notification creation error.\");
} // if
}
With Swift
let app = UIApplication.sharedApplication()
app.beginBackgroundTaskWithExpirationHandler(nil)
let timer = NSTimer.scheduledTimerWithTimeInterval(1,
target: self,
selector:"DoSomethingFunctions",
userInfo: nil,
repeats: true)
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
I know this post is relatively old but I recently ran into this problem and was having some trouble figuring it out. This is the solution I came up with as of Swift 5. It uses a combination of the RunLoop and Timer classes, with information about them in the provided links below.
Using timers
https://developer.apple.com/documentation/foundation/timer
Using run loops
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1
https://developer.apple.com/documentation/foundation/runloop
Sample code:
class AMSleepTimerUtil: NSObject {
static let shared = AMSleepTimerUtil()
fileprivate var sleepTimer: Timer?
/// Initialize the timer to trigger a function to execute after a duration of time
/// - Parameter seconds: the time delay until the selector function executes
/// - Returns: true if sleep timer were successfully initialized
func createTimerToStopMusic(at seconds: TimeInterval) -> Bool {
let fireAtDate = Date(timeIntervalSinceNow: seconds)
stopSleepTimer()
self.sleepTimer = Timer(fireAt: fireAtDate,
interval: 0,
target: self,
selector: #selector(pauseMusic),
userInfo: nil,
repeats: false)
guard let sleepTimer = sleepTimer else { return false }
RunLoop.main.add(sleepTimer, forMode: .common)
return true
}
func pauseMusic() {
guard let audioPlayer = AMNowPlayingViewController.sharedInstance()?.audioPlayer else { return }
audioPlayer.pause()
}
/// Used to reset the sleep timer before initializing a new timer if the user clicks the "Set Timer" multiple times
func stopSleepTimer() {
if sleepTimer != nil {
sleepTimer?.invalidate()
sleepTimer = nil
}
}
func sleepTimerIsActive() -> Bool {
return self.sleepTimer != nil
}
}
When you run NSTimer, the #selector method itself will determine wether you want to run in background or main thread.
Initial set up:
self.scanTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(manageMethod) userInfo:nil repeats:YES]; //#property (nonatomic, strong) NSTimer *scanTimer
Case you want to run in Background:
-(void)manageMethod
{
dispatch_queue_t queue = dispatch_queue_create("com.mysite.thread1",NULL);
dispatch_async(queue,^{
//run in background
});
}

Resources