There are 3 ways: CADisplayLink, NSTimer, and GCD

CADisplayLink

Features: Triggered once when the screen is refreshed, the specified method is repeatedly called. CADisplayLink is a timer class that allows us to draw specific content onto the screen at a rate that synchronizes with the refresh rate of the screen. Typically, the screen refresh rate on iOS devices is 60 times per second.

After CADisplayLink registers with Runloop in a specific mode, the Runloop invokes the method by sending the specified selector message to the target specified by CADisplayLink each time the screen refreshes.

  • create
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)]; [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; // When a CADisplayLink object is added to a runloop, the selector can be called periodicallyCopy the code
  • attribute
FrameInterval: Sets the number of frames (NSInteger) at which the 'selector' method is called. The default is 1, which is called once per frame. 'duration' : indicates the interval between screen refreshes (CFTimeInterval). Read-only property. Note: This attribute is not assigned until target's 'selector' is first called. The interval between calls to 'selector' is calculated as: 't = duration × frameInterval'.Copy the code
  • Stop and release
[self.displayLink invalidate]; self.displayLink = nil; // The CADisplayLink object is removed from the runloop, and the selector call stopsCopy the code
  • The advantages and disadvantages

IOS devices refresh their screens at a fixed rate, and CADisplayLink is normally called at the end of each refresh with a high degree of accuracy. 1. However, if the timing method invoked is time-consuming and exceeds the screen refresh cycle, it will cause several callback opportunities to be skipped. 2. If the CPU is too busy to ensure a screen refresh rate of 60 times/SEC, it will cause several call callback opportunities to be skipped. The number of skips depends on how busy the CPU is.

  • Application scenarios

In principle, CADisplayLink is ideal for constantly redrawing interfaces. For example, while a video is playing, you need to constantly fetch the next frame for rendering.

NSTimer

  • create

Timers must be added to the RunLoop and mode selected to run.

NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @ the selector (dosomething:) the userInfo: nil repeats:NO]; // TimeInterval: wait time before execution // repeats: whether loop is requiredCopy the code

The above creation method creates a timer that is automatically added to the NSDefaultRunLoopMode of the MainRunloop and can be used directly.

Methods scheduledTimerWithTimeInterval in which thread creation, will be added to the RunLoop which thread running on which thread. A self-created Timer that is added to the RunLoop of any thread will run on that thread.

After the target object is created, the counter of the target object is incremented by 1. After the target object is created, the counter of the target object is automatically decreased by 1.

  • Disadvantages:

1. Delay exists. Whether it is one-off or periodic, the actual triggering time of timer is related to the added RunLoop and RunLoop Mode.

If the added RunLoop is performing a continuous task, the timer is delayed. For a repetitive timer, if the delay exceeds one cycle, it will be executed immediately after the end of the delay and continue with the previously specified cycle.

2. When the page slides, the timer does not work

The main thread RunLoop has two preset modes:

Both kCFRunLoopDefaultMode and UITrackingRunLoopMode are “Common” attributes. The former is the normal state of the App, and the latter is the state of tracking the slide.

When a Timer is created and added to DefaultMode, the Timer gets repeated callbacks. But if the page slides, RunLoop switches mode to TrackingRunLoopMode (so as not to affect the slide) but the Timer is not called back.

Solution: Slide and Timer call do not affect each other, add this Timer mode to NSRunLoopCommonModes

// Create NSTimer *timer = [NSTimer timerWithTimeInterval:1 Target :self selector:@selector(timerAction) userInfo:nil repeats:YES]; // Add to runloop [[NSRunLoop mainRunLoop] addTimer:timer forMode: NSRunLoopCommonModes];Copy the code

Method to get runloop

[NSRunLoop mainRunLoop] This applies to the main thread. [NSRunLoop currentRunLoop] This applies to both main and child threads

  • The release of

If the timer task is executed in a loop, you must manually close the release!

[timer invalidate];
timer = nil;
Copy the code
Release the Timer’s note:

Cannot release the timer in dealloc. Because the timer strongly holds self when repeats is YES, dealloc is never called and the class can never be released.

Solution: it can be released in viewDidDisappear so that when classes need to be recycled, they can go into dealloc as normal.

Stop the Timer

Need: Pause and then start again under certain circumstances. Mainly to prevent it from running in the background, temporarily using CPU. For example, close the page when it disappears, then wait for the page to open again, and turn on the timer again.

// Disable timer [myTimer setFireDate:[NSDate distantFuture]]; // Start timer [myTimer setFireDate:[NSDate distantPast]];Copy the code

GCD

Perform a

Dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC); Dispatch_after (popTime, dispatch_get_main_queue(), ^(void){// execute events});Copy the code

repeat

NSTimeInterval period = 1.0; // Interval dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); Dispatch_source_set_event_handler (_timer, ^{// execute event here}); dispatch_resume(_timer); / / startCopy the code

Cancel timer: dispatch_cancel(_timer);

Timer call, in the main thread optimal! Execution in GCD dispatch_async may be invalid!

Circular reference problem

1. CADisplayLink and NSTimer are strong references to target, and if target is strong references to them, they are circular references. Runloop creates strong references to CADisplayLink and NSTimer.

Solution: Use weakSelf, or NSProxy (proxy object)

  • weakSelf
// Internally use WeakSelf and turn off timer __weak __typeof(self) WeakSelf = self before the view disappears; NSTimer * timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"timer"); }]; self.timer = timer; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];Copy the code
  • NSProxy
@interface MyProxy: NSProxy - (instanceType)initWithObjc:(id)objc; + (instancetype)proxyWithObjc:(id)objc; .m @interface MyProxy() @property(nonatomic,weak) id objc; @end @implementation MyProxy - (instancetype)initWithObjc:(id)objc{ self.objc = objc; return self; } + (instancetype)proxyWithObjc:(id)objc{ return [[self alloc] initWithObjc:objc]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { return [self.objc methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)invocation { if ([self.objc respondsToSelector:invocation.selector]) { [invocation invokeWithTarget:self.objc]; }}Copy the code

Reference target with a proxy object.

NSTimer * timer = [NSTimer timerWithTimeInterval:1
                                          target:[TimerProxy proxyWithTarget:self]
                                        selector:@selector(test1)
                                        userInfo:nil
                                         repeats:YES];
self.timer = timer;
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code