简体   繁体   中英

NSTimer affecting behavior of UIPickerView

Hi and thanks in advance. I have a problem where when I start an NSTimer in one view and then switch to another view with a UIPickerView , the UIPickerView 's behavior is affected. The more I switch back and forth between any view and the view containing the NSTimer , the more profoundly the UIPickerView 's behavior is affected - they lag and move sluggishly - eventually getting to a point where the UIPickerView doesn't call the DidSelectRow method. It's not just one UIPickerView that is getting affected but all the UIPickerView in my app.

If I decide not to activate the NSTimer in the first place, my UIPickerViews work without a problem. However, when I call the NSTimer methods, after switching back and fourth between the view containing the NSTimer and any other view maybe six times, my apps UIPickerView all stop working completely. In order to restore their proper behavior I need to shut the app down and restart it.

I'm using ARC so I'm not releasing the NSTimer manually - but I think this has something to do with my problems. I'm guessing that NSTimer or its methods are getting duplicated (without getting released or deallocated) every time I switch back to the view containing the NSTimer . Anyways, this is my second effort coding anything so I'm not sure how to fix this problem, although I've read that the NSTimer and UIPickerView could be allocated through the same NSRunLoop or thread, but I'm not really sure what that means.

Anyways, here is my code - its pretty generic boilerplate code.

-(void)showActivity:(NSTimer *)tim {

    NSDate *currentDate = [NSDate date];
    NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
    NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];

    NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"HH:mm:ss.S"];
    [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
    NSString* timeString = [dateFormatter stringFromDate:timerDate];
    stopWatchLabel.text = timeString;

}


- (IBAction)onStartPressed:(UIButton *)sender; {

    stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1/10
                                                  target:self
                                                selector:@selector(showActivity:)
                                                userInfo:nil
                                                 repeats:YES];
    // Save the new start date every time
    startDate = [[NSDate alloc] init]; // equivalent to [[NSDate date] retain];
    NSDate *savedMentionDate = [[NSUserDefaults standardUserDefaults] objectForKey:@"mostRecentMentionDate"];

    if (savedMentionDate == nil) {
        //There is no existing mention, so save the most recent one
        [[NSUserDefaults standardUserDefaults]setObject:startDate forKey:@"mostRecentMentionDate"];
        [[NSUserDefaults standardUserDefaults] synchronize];
    } else {
        startDate = savedMentionDate;
    }

    [stopWatchTimer fire];

    timerSetting = 0;

    NSNumber* timerSettingNS = [[NSNumber alloc] initWithInt:timerSetting];
    [[NSUserDefaults standardUserDefaults] setObject:timerSettingNS forKey:@"timerSetting"];
    [[NSUserDefaults standardUserDefaults] synchronize];

}


- (IBAction)onStopPressed:(UIButton *)sender {

    [stopWatchTimer invalidate];

}


- (IBAction)resetTimer:(UIButton *)sender; {

    stopWatchLabel.text = @"00:00:00.0";

    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
    [defaults removeObjectForKey:@"mostRecentMentionDate"];

    timerSetting = 1;

    NSNumber* timerSettingNS = [[NSNumber alloc] initWithInt:timerSetting];
    [[NSUserDefaults standardUserDefaults] setObject:timerSettingNS forKey:@"timerSetting"];
    [[NSUserDefaults standardUserDefaults] synchronize];

}

-(void)ViewDidLoad {
    ...
    ...
    NSNumber* timerSettings = [defaults objectForKey:@"timerSetting"];

    if (timerSettings == nil || timerSettings.intValue == 1) {

        [self resetTimer:resetTime];

    } else if (timerSettings.intValue == 0) {

        [self onStartPressed:start];

    }

}

I figured out a solution. I've inserted the code [stopWatchTimer invalidate] in all the methods that enable the app to switch views. I'm surprised this works because when I included [stopWatchTimer invalidate] in the viewDidLoad and viewDidUnload methods it didn't have any effect...

I figured out a solution. I've inserted the code [stopWatchTimer invalidate] in all the methods that enable the app to switch views.

This more or less what anyone would have told you to because:

I'm surprised this works because when I included [stopWatchTimer invalidate] in the viewDidLoad and viewDidUnload methods it didn't have any effect...

Those methods are only called on very specific occasions:

viewDidLoad is called when a view controller's view has been loaded. (Shouldn't be much of a surprise…)

The important part is to understand what load means:

When a view controller is instantiated — be it from a NIB, or in code — it doesn't have a view. What it does have, instead, is the information how to serve one, when being asked for it.

So when you send the view message to a UIViewController, it will figure if it already has its _view instance variable populated. If not it will invoke its own loadView method which will do whatever is necessary to fill that instance variable with something meaningful. It will then send itself the viewDidLoad message, to finalize the setup of the view hierarchy it manages, and only then will it return the value of the _view .

This implies that viewDidLoad is only called once throughout the lifetime of the view controller's view. But what does that mean?

On iOS 5, and earlier, the view's lifetime was tied to it being part of a visible view hierarchy: When it wasn't contained in one, and its owning view controller received a memory warning, the base implementation would basically look whether the view had a superview, and (if not) it would invoke viewWillUnload , release, and nil out _view , and eventually invoke viewDidUnload to finalize this process. (That's why in some crappy legacy code bases, you may find overrides of didReceiveMemoryWarning that don't call through to super …)

Since iOS 6, UIViewController no longer does this!

That's why viewWillUnload, and viewDidUnload have been deprecated: the view lives until its owning view controller is being disposed of, so those methods are no longer called.

With that out of the way, what are the relevant situations to clear your timer? 1. viewWillDisappear: your view is about to “leave the stage”, so tear down whatever is only relevant when it's front, and center. 2. Whenever you are to obscure the parts being influenced by the timer. This may be because you are presenting another view controller, or showing other things like a UIPickerView, that hide a larger part of the screen beneath them. 3. Whenever you need to reconfigure/reset the timer. (Failing to do this is the reason why your app came to a screeching halt in the first place.)

There's a ton of excellent documentation on these matters available from right within Xcode, and online:

  • The View Contoller Programming Guide is an absolute must-read for all things iOS.
  • The NSTimer Class Reference , and linked companion guides are extremely good. Make extra sure to read the conceptual part — before the “Tasks” section. Well beyond 90% of any questions that get asked under the NSTimer tag (and are actually related to NSTimer) would not be asked if people actually read this…

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM