I have problem with NSTimer in Objective-C. This is my source code:
Main.m
#import <Foundation/Foundation.h>
#import "TimerTest.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
TimerTest *timerTest = [[[TimerTest alloc] init] autorelease];
}
return 0;
}
TimerTest.h
#import <Foundation/Foundation.h>
#interface TimerTest : NSObject {
NSTimer *_timer;
}
#property (nonatomic, retain) NSTimer *timer;
- (id) init;
#end
TimerTest.m
#import "TimerTest.h"
#implementation TimerTest
#synthesize timer = _timer;
- (id) init {
if (self = [super init]) {
[NSTimer timerWithTimeInterval:0.5f
target:self
selector:#selector(tick:)
userInfo:nil
repeats:YES];
}
return self;
}
- (void) tick: (NSDate *) dt {
NSLog(#"Tick! \n");
}
- (void) dealloc {
self.timer = nil;
[super dealloc];
}
#end
My program should log "Tick! \n" every 0.5 second. But then my program is finished, xcode console is clear, that's meaning that NSLog in
-(void)tick:(NSDate *)dt method didn't work . Where is my mistake?
My program should log "Tick! \n" every 0.5 second.
No it shouldn't (at least not according to the code you posted). You need a run loop. Timers only fire as events on run loops. So, in your main, you need to set one up and run it.
Not only do you need an event loop, but you've created a timer, and you haven't scheduled it in said run loop. Instead of:
[NSTimer timerWithTimeInterval:0.5f
target:self
selector:#selector(tick:)
userInfo:nil
repeats:YES];
do this:
[NSTimer scheduledTimerWithTimeInterval:0.5f
target:self
selector:#selector(tick:)
userInfo:nil
repeats:YES];
I set your code up in the context of a Cocoa application (because it comes with a run loop), executing the TimerTest allocation in the applicationDidFinishLaunching of the delegate, and your code otherwise works.
A couple of other things: The method whose selector is passed to scheduledTimerWithTimerInterval:... should be of the form
- (void)timerMethod:(NSTimer *)aTimer
and when you're done with your timer, just invalidate it:
[timer invalidate];
though you have to keep a reference to the timer to do this, which it appears you hadn't.
Related
there is a nib file and i am creating different window instances with different context, all controls works normally except timer and variables triggered by timers looks shared with all windows. this is how i am create window instances.
#import <Cocoa/Cocoa.h>
#import "MYWindow.h"
#interface AppDelegate : NSObject <NSApplicationDelegate>
{
}
#property (strong) MYWindow *pickerWindow;
--
#import "AppDelegate.h"
#implementation AppDelegate
-(IBAction)newWindow:(id)sender
{
myWindow = [[MYWindow alloc] initWithWindowNibName:#"MYWindowNIB"];
[myWindow showWindow:self];
}
Also i am having problem with ARC, when i open new instance of a window previous one releases immediately even i declare it is strong in property that is why compiling AppDelegate with a flag -fno-objc-arc. otherwise as i said what ever i did windows releases immediately.
XCode 4.6
edit
int i = 0;
-(void)windowDidLoad
{
timerMoveOutNavBar = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countUP) userInfo:nil repeats:YES];
}
-(void)countUP
{
[text setStringValue:[NSString stringWithFormat:#"%d", i]];
i++;
}
i found the solution, you have to declare variables and all other objects as private.
#private
int i;
What do you mean by "share same variables"? You can have a superclass that all the window controllers inherit from, and they will all have the properties and methods you create in the superclass, if that's what you're talking about.
As far as the windows being released, do you have the "Release When Closed" box checked in IB? If so, uncheck that box.
After Edit
The problem has to do with the way you initialize the int variable "i". By putting it outside a method, you're declaring it as a global variable that all instance will see. You should create an ivar and set its value to zero where you create the timer:
#implementation MyWindowController {
IBOutlet NSTextField *text;
int i;
}
- (void)windowDidLoad {
[super windowDidLoad];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countUP:) userInfo:nil repeats:YES];
i = 0;
}
-(void)countUP:(NSTimer *) timer {
[text setStringValue:[NSString stringWithFormat:#"%d", i]];
i++;
if (i== 50) [timer invalidate];
}
Notice that I added a colon to the selector name -- with a timer, the timer passes itself as the argument to its selector, so you can reference it like I do to invalidate it. But it's ok to do it the way you did by assigning the timer to an ivar or property (timerMoveOutNavBar in your case).
The following is my Objective-C category on NSTimer to do block-based firing of NSTimers. I can't see anything wrong with it, but what I am getting is that the block I pass into the schedule... method is being deallocated despite me calling copy on it.
What am I missing?
typedef void(^NSTimerFiredBlock)(NSTimer *timer);
#implementation NSTimer (MyExtension)
+ (void)timerFired:(NSTimer *)timer
{
NSTimerFiredBlock blk = timer.userInfo;
if (blk != nil) {
blk(timer);
}
}
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
repeats:(BOOL)repeats
callback:(NSTimerFiredBlock)blk
{
return [NSTimer scheduledTimerWithTimeInterval:seconds
target:self
selector:#selector(timerFired:)
userInfo:[blk copy]
repeats:repeats];
}
#end
I found this code over at http://orion98mc.blogspot.ca/2012/08/objective-c-blocks-for-fun.html
Great work
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.7
target:[NSBlockOperation blockOperationWithBlock:^{ /* do this! */ }]
selector:#selector(main)
userInfo:nil
repeats:NO
];
You have a project on github that does the job !
Cocoapod BlocksKit, allow you to Blockify a bunch of Classes...
#import "NSTimer+BlocksKit.h"
[NSTimer bk_scheduledTimerWithTimeInterval:1.0 block:^(NSTimer *time) {
// your code
} repeats:YES];
Here is the Swift version of Mc.Stever's answer:
NSTimer.scheduledTimerWithTimeInterval(0.7, target: NSBlockOperation(block: {
/* do work */
}), selector: "main", userInfo: nil, repeats: false)
What you're missing is that if the block you're passing in is on the stack then copy will do exactly what the name says — it'll create a copy of the block over on the heap. You'd therefore expect no change in the behaviour of the one you passed in; nobody new is retaining it. The copy will stay alive while the original is deallocated.
(aside: if you're not using ARC you'll also want to autorelease the copy; you're meant to pass a non-owning reference as userInfo:. Otherwise the copy will never be deallocated)
try this
typedef void(^NSTimerFiredBlock)(NSTimer *timer);
#interface NSObject (BlocksAdditions)
- (void)my_callBlock:(NSTimer *)timer;
#end
#implementation NSObject (BlocksAdditions)
- (void)my_callBlock:(NSTimer *)timer {
NSTimerFiredBlock block = (id)self;
block(timer);
}
#implementation NSTimer (MyExtension)
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
repeats:(BOOL)repeats
callback:(NSTimerFiredBlock)blk
{
blk = [[blk copy] autorelease];
return [NSTimer scheduledTimerWithTimeInterval:seconds
target:blk
selector:#selector(my_callBlock:)
userInfo:nil
repeats:repeats];
}
#end
Im learning about NSTimer and after reading Docs I made this simple test. My understanding is that when I call viewDidLoad in main, viewDidLoad should then call runTest after 3.0 secs - but it's not working. It compiles fine with no errors but will not load runTest (no NSLog) please help ... thank you.
#import "MyClass.h"
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
MyClass *trial = [[MyClass alloc]init];
[trial viewDidLoad]; ///// this should call viewDidLoad /////
[pool drain];
return 0;
}
#import <Foundation/Foundation.h>
#interface MyClass : NSObject {
NSTimer *aTimer;}
#property (nonatomic, retain) NSTimer *aTimer;
- (void)viewDidLoad;
- (void)runTest;
#end
#import "MyClass.h"
#implementation MyClass
#synthesize aTimer;
- (void)viewDidLoad {
aTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(runTest) userInfo:nil repeats:NO];}
- (void) runTest {
NSLog(#"Working - runTest has loaded !");
aTimer = nil;
}
#end
It will not work this way. NSTimer requires a runloop, which will be created automatically when you make a normal Cocoa Application not a Foundation command line tool.
Create the timer in the applicationDidFinishLaunching method of the application delegate or in the init of your object if you instantiate it in the main nib.
Need help with a problem.
Goal
I'm putting together an iOS book app that uses NSTimers to fire off several staggered animation events after loading a view. I've created a MethodCallerWithTimer class to help me do this (code at bottom).
My Solution So Far
When I use the MethodCallerWithTimer class, I assign the objectOwningMethod as my UIViewController subclass object (it's a book page), and then the method as an instance method in that class. Here is an example of a method I assign - quite simply turning on some artwork on the screen:
- (void) playEmory {
[emoryRedArt setHidden:NO];
}
My Issue
When I create multiple MethodCallerWithTimer instances then load the view and start them all, I only ever get the FIRST event to happen. None of the other timers call their target methods. I suspect I don't understand what I'm asking NSRunLoop to do or something similar.
Any thoughts?
Here is my MethodCallerWithTimer class:
#interface MethodCallerWithTimer : NSObject {
NSTimer * timer;
NSInvocation * methodInvocationObject;
NSNumber * timeLengthInMS;
}
- (id) initWithObject: (id) objectOwningMethod AndMethodToCall: (SEL) method;
- (void) setTime: (int) milliseconds;
- (void) startTimer;
- (void) cancelTimer;
#end
And implementation:
#import "MethodCallerWithTimer.h"
#implementation MethodCallerWithTimer
- (id) initWithObject: (id) objectOwningMethod AndMethodToCall: (SEL) method {
NSMethodSignature * methSig = [[objectOwningMethod class] instanceMethodSignatureForSelector:method];
methodInvocationObject = [NSInvocation invocationWithMethodSignature:methSig];
[methodInvocationObject setTarget:objectOwningMethod];
[methodInvocationObject setSelector:method];
[methSig release];
return [super init];
}
- (void) setTime: (int) milliseconds {
timeLengthInMS = [[NSNumber alloc] initWithInt:milliseconds];
}
- (void) startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:([timeLengthInMS longValue]*0.001) invocation:methodInvocationObject repeats:NO];
}
- (void) cancelTimer {
[timer invalidate];
}
-(void) dealloc {
[timer release];
[methodInvocationObject release];
[timeLengthInMS release];
[super dealloc];
}
#end
These look like one-time firings after a delay; have you considered using something like:
[myObject performSelector:#selector(playEmory) withObject:nil afterDelay:myDelay];
where myObject is the instance with the playEmory routine and myDelay is a float of the seconds you want the OS to wait before making the call?
You can find out more information about this flavor of performSelector here.
So I am trying to set up a basic timer but I am failing miserably. Basically all I want is to start a 60 second timer when the user clicks a button, and to update a label with the time remaining(like a countdown). I created my label and button and connected them in IB. Next I created a IBAction for the button. Now when I tried to update the label based on the timer, my app screws up. Here's my code:
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 1
target: self
selector:#selector(updateLabelDisplay)
userInfo: nil repeats:YES];
I also have an updateLabelDisplay function that determines how many times the timer has ran and then subtracted that number from 60 and displays that number in the countdown label. Can anyone tell me what I am doing wrong?
Ok, well for starters, check this out if you haven't already: Official Apple Docs about Using Timers
Based on your description, you probably want code that looks something like this. I've made some assumptions regarding behavior, but you can suit to taste.
This example assumes that you want to hold on to a reference to the timer so that you could pause it or something. If this is not the case, you could modify the handleTimerTick method so that it takes an NSTimer* as an argument and use this for invalidating the timer once it has expired.
#interface MyController : UIViewController
{
UILabel * theLabel;
#private
NSTimer * countdownTimer;
NSUInteger remainingTicks;
}
#property (nonatomic, retain) IBOutlet UILabel * theLabel;
-(IBAction)doCountdown: (id)sender;
-(void)handleTimerTick;
-(void)updateLabel;
#end
#implementation MyController
#synthesize theLabel;
// { your own lifecycle code here.... }
-(IBAction)doCountdown: (id)sender
{
if (countdownTimer)
return;
remainingTicks = 60;
[self updateLabel];
countdownTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: #selector(handleTimerTick) userInfo: nil repeats: YES];
}
-(void)handleTimerTick
{
remainingTicks--;
[self updateLabel];
if (remainingTicks <= 0) {
[countdownTimer invalidate];
countdownTimer = nil;
}
}
-(void)updateLabel
{
theLabel.text = [[NSNumber numberWithUnsignedInt: remainingTicks] stringValue];
}
#end
It may be a little late to post a second answer to this question but I've been looking for a good place to post my own solution to this problem. In case it is of use to anyone here it is. It fires 8 times but of course this can be customised as you please. The timer deallocates itself when time is up.
I like this approach because it keeps the counter integrated with the timer.
To create an instance call something like:
SpecialKTimer *timer = [[SpecialKTimer alloc] initWithTimeInterval:0.1
andTarget:myObject
andSelector:#selector(methodInMyObjectForTimer)];
Anyway, here are the header and method files.
//Header
#import <Foundation/Foundation.h>
#interface SpecialKTimer : NSObject {
#private
NSTimer *timer;
id target;
SEL selector;
unsigned int counter;
}
- (id)initWithTimeInterval:(NSTimeInterval)seconds
andTarget:(id)t
andSelector:(SEL)s;
- (void)dealloc;
#end
//Implementation
#import "SpecialKTimer.h"
#interface SpecialKTimer()
- (void)resetCounter;
- (void)incrementCounter;
- (void)targetMethod;
#end
#implementation SpecialKTimer
- (id)initWithTimeInterval:(NSTimeInterval)seconds
andTarget:(id)t
andSelector:(SEL)s {
if ( self == [super init] ) {
[self resetCounter];
target = t;
selector = s;
timer = [NSTimer scheduledTimerWithTimeInterval:seconds
target:self
selector:#selector(targetMethod)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)resetCounter {
counter = 0;
}
- (void)incrementCounter {
counter++;
}
- (void)targetMethod {
if ( counter < 8 ) {
IMP methodPointer = [target methodForSelector:selector];
methodPointer(target, selector);
[self incrementCounter];
}
else {
[timer invalidate];
[self release];
}
}
- (void)dealloc {
[super dealloc];
}
#end