How to release my NSTimer object in ios? - ios

In My Application i need to release my NSTimer , when i am moving from one view controller to another view controller . How to release this type of objects in ARC ? i am using below code for creation and releasing NSTimer but Where i have to write this releasing code in view controller?
For Creation.
- (void)viewDidLoad{
[super viewDidLoad];
updateBCK = [NSTimer scheduledTimerWithTimeInterval:(5.0) target:self selector:#selector(changeImage) userInfo:nil repeats:YES];
[updateBCK fire];
}
-(void)changeImage{
static int i=0;
if (i == [myImages count]){
i=0;
}
[UIImageView beginAnimations:nil context:NULL];
[UIImageView setAnimationDuration:0.6];
mainBackgroundImageView.alpha=1;
mainBackgroundImageView.image =[myImages objectAtIndex:i];
NSLog(#"\n The main screen image is %#",[myImages objectAtIndex:i]);
[UIImageView commitAnimations];
i++;
}
For Release.
[updateBCK invalidate];//
updateBCK = nil;
Thanks in Advance.

You should call
[timer invalidate];
timer = nil;
where you push your view controller. If this is an issue, you can still call it in
- (void) viewWillDisappear:(BOOL)animated;
Also, you should initialize it in
- (void) viewDidAppear:(BOOL)animated;
That makes more sense.

When you want to stop time
use below code
[timer invalidate]; // timer is the name of timer object

- (IBAction)button:(id)sender {
SecondViewController *second = [[SecondViewController alloc]
initWithNibName:#"secondViewController"
bundle:nil];
[self presentViewController:second animated:NO completion:nil];
[self.timer invalidate]; // timer is the name of timer object
timer=nil;//it may work without this line too ;not sure
}

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Timers/Articles/usingTimers.html
A timer maintains a strong reference to its target. This means that as long as a timer remains valid, its target will not be deallocated. As a corollary, this means that it does not make sense for a timer’s target to try to invalidate the timer in its dealloc method—the dealloc method will not be invoked as long as the timer is valid.

when i am moving from one view controller to another view controller
Then you should do this in delloc, if you want to do some cleanup tasks when your view is dismissing or released. It's the best place, In such case you can implement it.
-(void)dealloc{
[updateBCK invalidate];//
updateBCK = nil;
}
Hope this helps

Related

Crash while popping view controller

I have one view controller (let's call it ViewController) with timer within its code, and have another view controller (let's call it ContentViewController) with webView within it which shows content depending on which button is clicked.
I have buttons on ViewController which, when clicked, pushes ContentViewController which loads a html file into its webView.
Timers are used to close ContentViewController if it is pushed.
Here's how I create timers:
-(void)createTimer :(NSNumber*)index
{
if (_show)
{
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:index, #"parameter1", nil];
switch ([index intValue])
{
case 1001:
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
case 1002:
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
case 1003:
[NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
case 1004:
[NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES];
break;
}
}
}
Here's the code in ViewController which closes ContentViewController when timer fired:
-(void)updateUi :(NSTimer *)timer
{
int index = [[timer.userInfo objectForKey:#"parameter1"] intValue];
if([self.navigationController.visibleViewController isKindOfClass:[ContentViewController class]])
{
if ([[[NSUserDefaults standardUserDefaults]valueForKey:#"CurrentString"] intValue]==index )
{
[self.navigationController popViewControllerAnimated:YES];
}
}
}
And on web page which is shown on ContentViewController there's a button,when user clicks it ContentView must go back to ViewController. Here's how I do this:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *URL = [request URL];
if ([[URL scheme] isEqualToString:#"coffee-drink"])
{
NSString *urlString = [[request URL] absoluteString];
NSArray *urlParts = [urlString componentsSeparatedByString:#":"];
if (urlParts.count > 1) ///------------поменял
{
[self.navigationController popViewControllerAnimated:YES];
}
}
return YES;
}
All of above works fine but sometimes it crashes with the following error:
[ContentViewController respondsToSelector:]: message sent to deallocated instance
And also, I have other errors like these:
nested push animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
and
delegate (webView:decidePolicyForNavigationAction:request:frame:decisionListener:) failed to return after waiting 10 seconds
The last one appears very rarely.
I don't know what am I doing wrong.Can someone help me)?If needed any more information I will provide it!Thanks in advance!
Issue
You are popping your view without leaving ARC to deallocate all objects and delegates, by keeping them alive.
My Solution
For the NSTimer
At #implementation create NSTimer *timer and use it when you want to initialize it. To dealloc it correctly when you pop back, at viewWillDisappear set [timer invalidate] and timer = nil.
For delegates
Set you specific delegates to nil. For example self.delegate = nil
#Yuandra Ismiraldi is right. Your NSTimer will call your updateUI method every 20/10 seconds repeatedly untill it receives [timer invalidate]; instruction. You are getting this error messages as you navigate through your view controller tree. Sometimes your NavigationViewController will need more memory and will release some of un-used objects. In your case some of the previously shown view controllers that still runs the updateUI method every 20/10 seconds. Once this view controller has been released and your selector is called you will receive this crash.
I hope this explains why sometimes you get this crash, and sometimes not.
Your timer code
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:YES]
the repeat parameter is set to YES, this make your timer always repeat, thus repeating the sending of the message updateUI after the timer has run for the first time and the view has popped, thus resulting in your error. To make sure your timer only run once, set the repeat to NO
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(updateUi:) userInfo:dictionary repeats:NO]

Dealloc not called on subview with ARC

I have a not usual architecture. With one UIViewController and many subview's. I use UIView like viewController and UIViewController for transition animation.
UIView with many subview, which i push on main window, i removeFromSuperview and all ok, but subview not call dealloc method. Also i try to clean objects
- (void) dealloc {
NSLog(#"ActivityShowVC dealloc");
[showView removeFromSuperview];
showView = nil;
}
This called.
- (void) dealloc {
NSLog(#"ActivityShowView dealloc");
[mainScroll removeFromSuperview];
[titleView setDelegate:nil];
[titleView removeFromSuperview];
titleView = nil;
[photoView setDelegate:nil];
[photoView removeFromSuperview];
photoView = nil;
[predictionView setDelegate:nil];
[predictionView removeFromSuperview];
predictionView = nil;
[calendarView setDelegate:nil];
[calendarView removeFromSuperview];
calendarView = nil;
[descriptionView setDelegate:nil];
[descriptionView removeFromSuperview];
descriptionView = nil;
}
But this have some problem.
If dealloc is implemented and not called then there is something retaining the instance. You need to rethink the strong references to it.
Dealloc is not a good place to do anything other than releasing resources since the instance is no longer fully functional nor in a complete state. Doing things like: [showView removeFromSuperview]; are best done explicitly prior to deallocation.
As #Greg stated, most or all of what you are doing in dealloc is not needed under ARC. Subviews are now generally weak so no explicit dealloc is necessary. In fact they may be already gone depending on the order of the teardown of the view hierarchy.
I have solved a similar problem. In my Viewcontroller, I have a NSTimer pointer created, when I put the code below in 'viewWillDisappear', 'dealloc' is called.
if (_timer) {
[_timer invalidate];
_timer = nil;
}

UILabel text is not updating NSTimer objective c?

Hi I have Navigation based application in which there is a timer in one view. (View like : A, B & C)
I have timer in C when I start timer it's working fine but when I push back to any view and again come to View C it's not showing updated values.
here is my code .
App Delegate
-(int)updateTimer
{
timer_value--;
return timer_value;
}
View "C" Code
- (IBAction)buttonClick:(id)sender {
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(update) userInfo:nil repeats:YES];
}
-(void)update
{
MFAppDelegate *appDelegate = (MFAppDelegate *) [[UIApplication sharedApplication]delegate];
int Time=[appDelegate updateTimer];
int minute=Time/60;
int second=Time-(minute*60);
NSString *strValue=[NSString stringWithFormat:#"%d:%d",minute,second];
NSLog(#"%#",self.lbl_timer.text);
[self.lbl_timer setText:strValue];
}
update function is calling every time and NSlog of label text is showing correct.
Anything I am doing wrong ? please help me out.
In your class
-(void)update
{
MFAppDelegate *appDelegate = (MFAppDelegate *) [[UIApplication sharedApplication]delegate];
int Time=[appDelegate updateTimer];
int minute=Time/60;
int second=Time-(minute*60);
NSString *strValue=[NSString stringWithFormat:#"%d:%d",minute,second];
NSLog(#"%#",self.lbl_timer.text);
[[NSNotificationCenter defaultCenter] postNotificationName:#"DoUpdateLabel" object:strValue userInfo:nil];
}
- (void) updateLabel:(NSString *)string
{
yourLabel.text = string;
}
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabel:) name:#"DoUpdateLabel" object:nil];
}
I think the views you are referring to are actually view controllers. When you go back from a view controller to previous view controller in the navigation controller stack, depending on your code, it will be released if you do not retain it, which I assume happened in your case.
Because of that, everytime you push a new view controller, it is actually a newly allocated instance of the view controller, and it is not the same instance as the previously viewed view controller.
Try to make the view controller, e.g. A, which has the timer label as a strong property of the view controller that push A.
#property (nonatomic, strong) UIViewController *viewControllerA;
And push the property instead of allocating a new instance everytime you push A.
[self.navigationController pushViewController:self.viewControllerA animated:YES];
you need to tell the UILabel to redraw itself after its text property has changed. I don't know about the method Ahmed Z. describes, but in my projects something like
[[self lbl_timer] setNeedsDisplay];
does the trick.
cheers

How can I tap into a UIView's changing values during an animateWithDuration block?

I am using animateWithDuration to change the value of a UISlider:
[UIView animateWithDuration:1.0f
delay:0.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^{ [myUISlider setValue:10]; }
completion:^(BOOL finished){ }];
The problem is, I need to be able to display the current value of the UISlider while it is changing. Is this even possible using animateWithDuration? I tried creating a UISlider subclass and overriding setValue in hopes of getting access to the slider's value as it is being changed:
-(void)setValue:(float)newValue
{
[super setValue:newValue];
NSLog(#"The new value is: %f",newValue);
}
But that code only gets called at the very end of the animation block. In a way that makes perfect sense, since I really only called setValue once. But I was hoping that the animation block would somehow call it over and over again within its internal mechanisms, but that appears not to be the case.
If UIView animateWithDuration is a dead-end here, I wonder if there is a better way to acheive this same functionality with something else? Perhaps another slick little block-driven part of the SDK I don't know about which allows animating more than just UIView parameters?
I think the best way is to handle it is to code a custom Slide you can creat it like progress bar which there are many demo on github.
You can also use NSTimer to do it , but I do not think it is a very better way.
When you tap , you can creat a timer :
_timer = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:#selector(setValueAnimation) userInfo:nil repeats:YES];
and set a ivar: _value = oldValue;
in the setValueAnimation method :
- (void)setValueAnimation
{
if (_value >= newValue) {
[_timer invalidate];
_value = newValue;
[self setVale:_value];
return;
}
_value += .05;
[self setVale:_value];
}
UPDATE
how to add block:
1st:you can define a block handler :
typedef void (^CompleteHandler)();
2nd: creat your block and added it into the userInfo :
CompleteHandler block = ^(){
NSLog(#"Complete");
};
NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:block,#"block", nil];
3rd: make the NSTimer:
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(setValueAnimation:) userInfo:userInfo repeats:YES];
4th:achieve your timer method:
- (void)setValueAnimation:(NSTimer *)timer
{
if (_value >= newValue) {
[_timer invalidate];
_value = newValue;
[self setVale:_value];
// also can use [_delegate complete];
CompleteHandler block = [timer.userInfo objectForKey:#"block"];
if (block) {
block();
}
return;
}
_value += .05;
[self setVale:_value];
}
I do not know if there is any sort of notification that you could "tap into" to get the UIView animation "steps", but you can do the animations "manually" using an NSTimer, and that will allow you to continuously update your value.
If there's an out of the box solution using UIView - I'm all ears. This animation framework (only 2 classes!) - PRTween https://github.com/chris838/PRTween will give you access to convenience timing functions and also the changed value.
Here's an updated project of mine https://github.com/jdp-global/KACircleProgressView/
PRTweenPeriod *period = [PRTweenPeriod periodWithStartValue:0 endValue:10.0 duration:1.0];
PRTweenOperation *operation = [PRTweenOperation new];
operation.period = period;
operation.target = self;
operation.timingFunction = &PRTweenTimingFunctionLinear;
operation.updateSelector = #selector(update:)
[[PRTween sharedInstance] addTweenOperation:operation]
- (void)update:(PRTweenPeriod*)period {
[myUISlider setValue:period.tweenedValue];
}

iPhone NSTimer running single method

I'm new to programming for the iPhone and i'm having a problem with this function
-(IBAction)changeX {
[self timere:field1];
[self timere:field2];
}
this is a button to move an uitextfield object across the screen. The problem is i want to run this method on the first field , complete it then go on to the second. The timere function uses NSTimer to continously move the object until it reaches a certain point at which it terminates. I have two other functions shown below. The actual program im making is much longer but the objective is the same and that code is too long. The problem is running the first function then the second. Thank you for the help.
-(void)timere:(UITextField*)f1 {
UITextField*fielder1=f1;
NSInvocation*timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(moveP:)]];
[timerInvocation setSelector:#selector(moveP:)];
[timerInvocation setTarget:self];
[timerInvocation setArgument:&fielder1 atIndex:2];
timer1 = [NSTimer scheduledTimerWithTimeInterval:0.03 invocation:timerInvocation repeats:YES];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
}
}
The standardized method of animating UIView objects is by using methods such as the following.
+ animateWithDuration:delay:options:animations:completion:
The syntax for these methods might be a bit daunting if you are unfamiliar with Objective-C Blocks. Here's a usage example which moves the first field, then the second field afterwards.
[UIView animateWithDuration:0.3 animations:^{
[field1 setCenter:CGPointMake(FINAL_CENTER_X1, FINAL_CENTER_Y1)];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
[field2 setCenter:CGPointMake(FINAL_CENTER_X2, FINAL_CENTER_Y2)];
}];
}];
EDIT: For a general fix on your specific problem, I would make the following modifications:
-(IBAction)changeX {
[self timere:field1];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
// I'm really not sure about the scope of field1 and field2, but
// you can figure out the encapsulation issues
if (f1 == field1) {
[self timere:field2];
}
}
}
A more generic and indeed low-level solution would be to have a state variable. Perhaps you would have some sort of NSArray *fields of UITextField * and int state = 0. Where I added the conditional in moveP above, you would state++ and call [self timere:[fields objectAtIndex:state]] if state < [fields count]. You timer invalidation code is correct, anyway.

Resources