I'm trying to implement a timer but i'm getting bad access...I didn't use release neither in dealloc.
.h
NSTimer *_timerAnimacao;
#property (nonatomic, retain) NSTimer *timerAnimacao;
.m
#synthesize timerAnimacao = _timerAnimacao;
Here is where I call the timer.
if(acondition)
_timerAnimacao = [NSTimer scheduledTimerWithTimeInterval:0.06 target:self selector:#selector(changeTimeFilter:) userInfo:nil repeats:YES];
else
_timerAnimacao = [NSTimer scheduledTimerWithTimeInterval:0.06 target:self selector:#selector(changeTimeFilterHide:) userInfo:nil repeats:YES];
the changeTimeFilter works fine...but changeTimeFilterHide is getting BAD Access.
-(void)changeTimeFilter:(NSTimer *)theTimer{
if (tAnimacaoFilter<5) {
[_tbFilters setFrame:CGRectMake(_tbFilters.frame.origin.x, _tbFilters.frame.origin.y, _tbFilters.frame.size.width, 58*tAnimacaoFilter)];
}
if(tAnimacaoFilter < 10) {
tAnimacaoFilter++;
[_tbFeeds setFrame:CGRectMake(_tbFeeds.frame.origin.x, _tbFeeds.frame.origin.y + (4.4*tAnimacaoFilter), _tbFeeds.frame.size.width, _tbFeeds.frame.size.height - (4.4*tAnimacaoFilter))];
[imgBordaTbDireita setFrame:CGRectMake(imgBordaTbDireita.frame.origin.x, _tbFeeds.frame.origin.y - 4, imgBordaTbDireita.frame.size.width, imgBordaTbDireita.frame.size.height)];
[imgBordaTbEsquerda setFrame:CGRectMake(imgBordaTbEsquerda.frame.origin.x, _tbFeeds.frame.origin.y, imgBordaTbEsquerda.frame.size.width, imgBordaTbEsquerda.frame.size.height)];
}
else {
if(_timerAnimacao)
[_timerAnimacao invalidate];
[btnFilter setEnabled:YES];
}
}
-(void)changeTimeFilterHide:(NSTimer *)theTimer{
if (tAnimacaoFilter<5) {
[_tbFilters setFrame:CGRectMake(_tbFilters.frame.origin.x, _tbFilters.frame.origin.y, _tbFilters.frame.size.width, (58*5) - (58*tAnimacaoFilter))];
}
if(tAnimacaoFilter < 10) {
tAnimacaoFilter++;
[_tbFeeds setFrame:CGRectMake(_tbFeeds.frame.origin.x, _tbFeeds.frame.origin.y - (4.4*tAnimacaoFilter), _tbFeeds.frame.size.width, _tbFeeds.frame.size.height + (4.4*tAnimacaoFilter))];
[imgBordaTbDireita setFrame:CGRectMake(imgBordaTbDireita.frame.origin.x, _tbFeeds.frame.origin.y - 4, imgBordaTbDireita.frame.size.width, imgBordaTbDireita.frame.size.height)];
[imgBordaTbEsquerda setFrame:CGRectMake(imgBordaTbEsquerda.frame.origin.x, _tbFeeds.frame.origin.y, imgBordaTbEsquerda.frame.size.width, imgBordaTbEsquerda.frame.size.height)];
}
else {
if(_timerAnimacao)
[_timerAnimacao invalidate];
[btnFilter setEnabled:YES];
}
}
What you need to do on invalidate is to set NSTimer to nil like this:
[_timerAnimacao invalidate];
_timerAnimacao = nil;
If you don't do it your next commands will not work properly, e.g.:
if(_timerAnimacao)
[_timerAnimacao invalidate];
Please note: if(_timerAnimacao) is the same like if(_timerAnimacao == nil) !
Take a look at this one as well: How do I use NSTimer?
The instance of NSTimer needs to be in memory while you start and invalidate.
You can do that in two ways:
Set the NSTimer as a property of Appdelegate class, and use it wherever you need to.
NSTimer *timer=[(MyAppDelegate *)[[UIApplication sharedApplication] delegate].timer;
Create a new class and set NStimer as a property. Create a class method to return a singleton instance of the class.
static MyClass *obj = NULL;
(MyClass *)instance
{
#synchronized(self)
{
if (obj == NULL)
obj = [[self alloc] init];
}
return(obj);
}
I would prefer the second method.
Related
Thanks for the help. Experimenting with basic progressIndicator animation. This crashes, and noted: "TO8 Generator was compiled with optimization - stepping may behave oddly; variables may not be available." TO8 Generator is obviously the application.
Message:"No known class method for selector 'scheduledTimerWithTimeInterval:selector:userInfo:repeats:'
Any insight? Thank you again.
NSTimer *timer;
int count;
if(!timer)
{
timer = [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(checkThem:)
userInfo:nil
repeats:YES];
[progressBar startAnimation: self];
}
-(void)checkThem:(NSTimer *)aTimer
{
count++;
if(count > 100)
{
count = 0;
[timer invalidate];
timer = NULL;
[progressBar setDoubleValue:0.0];
[progressBar stopAnimation: self];
}
else
{
[progressBar setDoubleValue:(100.0 * count) / 100];
}
}
Andy's comment prompted me to check my Scheme config. "DeBug Executable" was enabled. As such, the function wouldn't work after compiling. I then disabled the option. All good after doing so. Sorry for the trouble.
Very similar issue is already discussed here. The problem at hand and what I am trying to achieve is to call a function on a given object in the thread it is created at. Here is the complete case:
an instance of class A is created in a given NSThread (Thread A) (not the main one). The instance keeps its creating NSThread as a member variable.
an instance of class B has one of its member functions executing in another NSThread - Thread B, and wants to call a function of A in A's creation thread. Thus B's currently executing function issues the following call:
[_a performSelector: #(fun)
onThread: _a.creationThread
withObject: nil
waitUntilDone: NO];
If the creation thread of A's instance is not the main one, fun never gets called. If the creation thread is the main one it is always called. First I was thinking whether the thread that created A's instance has been destroyed and the pointer points to an invalid thread but actually calling any functions on the thread object (Thread A) produces valid results and no crashes. Also checking the object is valid according to this check. Any suggestions?
Update:
What I'm doing is creating a timer on a background thread:
_timer = [NSTimer timerWithTimeInterval:60.0 target:self selector:#selector(fun:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
This code is the one starting the timer. The timer is not started in any specific background thread. It just happens that the function that creates the timer could be called in any thread. Thus the timer should be invalidated in the exactly same one as the NSTimer documentation states: "you should always call the invalidate method from the same thread on which the timer was installed."
To run timer on background thread, you have two options.
Use dispatch timer source:
#property (nonatomic, strong) dispatch_source_t timer;
and you can then configure this timer to fire every two seconds:
- (void)startTimer {
dispatch_queue_t queue = dispatch_queue_create("com.domain.app.timer", 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.timer, ^{
// call whatever you want here
});
dispatch_resume(self.timer);
}
- (void)stopTimer {
dispatch_cancel(self.timer);
self.timer = nil;
}
Run NSTimer on background thread. To do this, you can do something like:
#property (atomic) BOOL shouldKeepRunning;
#property (nonatomic, strong) NSThread *timerThread;
#property (nonatomic, strong) NSTimer *timer;
And
- (void)startTimerThread {
self.timerThread = [[NSThread alloc] initWithTarget:self selector:#selector(startTimer:) object:nil];
[self.timerThread start];
}
- (void)stopTimerThread {
[self performSelector:#selector(stopTimer:) onThread:self.timerThread withObject:nil waitUntilDone:false];
}
- (void)startTimer:(id)__unused object {
#autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(handleTimer:) userInfo:nil repeats:YES];
[runLoop addTimer:self.timer forMode:NSDefaultRunLoopMode];
self.shouldKeepRunning = YES;
while (self.shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
;
self.timerThread = nil;
}
}
- (void)handleTimer:(NSTimer *)timer {
NSLog(#"tick");
}
- (void)stopTimer:(id)__unused object {
[self.timer invalidate];
self.timer = nil;
self.shouldKeepRunning = FALSE;
}
I'm not crazy about the shouldKeepRunning state variable, but if you look at the Apple documentation for the run method, they discourage the reliance upon adding sources/timers to run loops:
If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
Personally, I'd recommend the dispatch timer approach.
Running the class below gives a thread break at the end of the first run of the #selector method timerEvent called by the NSTimer object [ timer ]. The error given is __NSFCTimer, '< error: unknown class>'. It seems to crash after the first iteration of currentNumber to 1.
Any ideas?
//
// NumberLooper.m
// delegation excersise
//
// Created by Edwin on 7/31/13.
// Copyright (c) 2013 Offbeat Software. All rights reserved.
//
#import "NumberLooper.h"
#implementation NumberLooper
-(void) timerEvent: (NSTimer *)timer {
if (currentNumber < 256)
currentNumber ++;
else
currentNumber = 0;
NSLog (#"%d", currentNumber);
[self.delegate numberHasChangedTo:currentNumber];
}
-(void) startTimerLoop {
if (!timer) {
timer = [NSTimer scheduledTimerWithTimeInterval: 0.020 target:self selector:#selector(timerEvent:) userInfo:nil repeats:YES];
NSLog(#"The timer has started");
}
else
{
NSLog(#"The timer is already running");
}
}
#end
theTimer = [NSTimer scheduledTimerWithTimeInterval:1.00 target:self selector:#selector(sendMessageHandler:) userInfo:nil repeats:YES];
//////////////////////////////////////////////////////
- (void)sendMessageHandler (NSTimer *) timer {
}
Ok so sendMessageHandler is triggering every second. But now, I want it to check the value of "theString" and if it changed value from the previous run, do something.
Can someone help me?
Thanks!
Make another string called prevString.
Make them equal each other initially. Then in the timer:
if ([theString isEqualToString:prevString]) {
//No change.
}
else {
//Change happened.
prevString = theString;
}
And for memory management in dealloc:
[theString release];
[prevString release];
Here's the problem: I have some code that goes like this
otherWinController = [[NotificationWindowController alloc] init];
for (int i = 0; i < 10; i++) {
[otherWinController showMessage:[NSString stringWithFormat:#"%d", i]];
NSLog(#"%d", i);
sleep(1);
}
where otherWinController is a subclass of NSWindowController that I am using to update my window as changes happen in code, and the alloc init method simply opens up the nib and shows the window. showMessage is a method that changes an NSTextView to display whatever text is in the parameter.
in the NSLog, the text changes every second and just counts to ten. However for the showMessage method, the text is blank for a full ten seconds and then just displays the number 10. any thoughts??
FTR, the showMessage method is simply
- (void)showMessage:(NSString *)text {
[[self message] setStringValue:text];
}
not that it should matter, that's pretty basic.
You can probably achieve the desired effect right inside your loop, if you explicitly give the run loop some time to run:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 0.1]];
The problem is that you’re blocking the main thread in your for loop and user interface updates happen on the main thread. The main thread run loop will only spin (and consequently user interface updates will take place) after the method containing that for loop finishes executing.
If you want to update that text field every second, you should use a timer. For instance, considering otherWinController is an instance variable, declare a counter property in your class and:
otherWinController = [[NotificationWindowController alloc] init];
self.counter = 0;
[otherWinController showMessage:[NSString stringWithFormat:#"%d", self.counter]];
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
In the same class, implement the method that’s called whenever the time has been fired:
- (void)updateCounter:(NSTimer *)timer {
self.counter = self.counter + 1;
[otherWinController showMessage:[NSString stringWithFormat:#"%d", self.counter]];
if (self.counter == 9) {
[timer invalidate];
// if you want to reset the counter,
// self.counter = 0;
}
}
Views don't get updated until the end of the run loop; your for loop doesn't let the run loop continue, so all the view updates you make are just done after your for loop exits.
You should either use an NSTimer or performSelector:withObject:afterDelay: to change the display in a loop-like fashion.
[NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(changeTextFieldsString:)
userInfo:nil
repeats:YES];
Then your timer's action will change the view's image:
- (void)changeTextFieldsString:(NSTimer *)tim {
// currStringIdx is an ivar keeping track of our position
if( currStringIdx >= maxStringIdx ){
[tim invalidate];
return;
}
[otherWinController showMessage:[NSString stringWithFormat:#"%d", currStringIdx]]
currStringIdx++;
}
You also generally don't want to use sleep unless you're on a background thread, because it will lock up the rest of the UI; your user won't be able to do anything, and if you sleep long enough, you'll get the spinning beach ball.
I think that by calling sleep(1) you block the main thread, which must draw your changes. So the display is not updated. The task manager will not interrupt your function. You shouldn't use sleep in this case. Please take a look at NSTimer class. It has a static method scheduledTimerWithTimeInterval, which you should use.
static UIViewController *controller = nil;
.....
{
.....
otherWinController = [[NotificationWindowController alloc] init];
controller = otherWinController;
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(timerTick:) userInfo:nil repeats:YES]; //set timer with one second interval
.....
}
- (void) timerTick:(NSTimer*)theTimer {
static int i = 0;
[controller showMessage:[NSString stringWithFormat:#"%d", i]];
NSLog(#"%d", i);
if (++i == 10) {
[theTimer invalidate];
i = 0;
}
}