“I. Basic Concepts”

The above two methods can be repeated timing events, but there is a problem with target-action. The memory leak is caused by a reference problem between objects, because the timer strongly references self and is itself strongly referenced by runloop. So neither timer nor self is released, so the timer is always there and triggers events, which can cause a memory leak.

To avoid memory leaks, you need to execute the timer.invalidate() method manually when the timer is not in use. The block method does not have circular references, but because it is strongly referenced by runloop, timer.invalidate() must be executed, otherwise the timer will remain.

The invalidate method does two things: it removes the timer from the runloop, and the timer itself frees the resources it holds

timer.invalidate()
timer = nil
Copy the code

Range of Timer Tolerance

After iOS7, iOS allowed us to specify Tolerance for timers, which would add some time Tolerance to your Timer to reduce its power consumption and increase responsiveness. A good example is: “I want it to run once a second, but I don’t mind 200 milliseconds later.”

When you specify time tolerance, it means that the system can trigger the timer at any time within the original time plus that tolerance. For example, if you want timerto run 1 second later with a time tolerance of 0.5 seconds, it might actually be 1 second, 1.5 seconds, 1.3 seconds, etc.

Work with Run Loop

When using the following methods to create a timer, you need to manually add the timer to the Run Loop and specify the Run model. The methods used above are automatically added to the current Run Loop and allowed in the default mode

Func addTimerToRunloop() {let timer = timer (timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true) RunLoop.current.add(timer, forMode: .common) }Copy the code

“Ii. Timer cyclic reference”

Scene: There are two controllers ViewControllerA and ViewControllerB, ViewControllerA jumps to ViewControllerB, ViewControllerB starts the timer, but when you go back to ViewControllerA, The timer is still running, and the controller is not deinit to destroy it

Why do circular references occur? The reason is that the timer strongly references the controller (self), the timer is referenced by the runloop, and the timer is not released, so the controller is not released

To solve this problem, there are two ways

Method 1:

In order to solve the object reference problem, apple has provided a new timer method that uses blocks to solve the reference loop with the view controller, but only for iOS10 and later:

Method 2:

Since Apple has provided us with blocks to solve the problem of circular references, we can also copy Apple’s blocks and extend Timer to add a new method to create a Timer

“Iii. Accuracy of timer”

In general, there is no problem with using Timer, but CADisplayLink (animation) and GCD can be used for high accuracy. For CADisplayLink, you can see the introduction of CADisplayLink. For comparison between timers, Can see more reliable and high precision iOS timer

The timer is not on time reasons:

  • The timer calculates the next triggering time according to the initial triggering time. The next triggering time is an integer multiple of the timer + tolerance
  • Timers are added to the runloop, and if the runloop blocks, calling or executing the method takes longer than the specified interval (the time calculated at point 1) and is deferred to the next runloop cycle.
  • Timers do not attempt to compensate for any missed fires that may occur when a specified method is called or executed
  • Mode impact of runloop

“Iv. Use GCD to achieve a good timer”

As we all know, NSTimer has a number of caveats.

  1. Circular reference problem NSTimer strongly references target, and RunLoop strongly references non-invalidate NSTimer instances. Memory leaks may occur. (Read the iOS Tamping down: Memory Management in the Age of ARC for details on memory leaks caused by NSTimer.)
  2. RunLoop Issues Because NSTimer relies on the RunLoop mechanism to work, you need to be aware of runloop-related issues. NSTimer runs in default mode of RunLoop by default. When ScrollView is swiped, the main thread RunLoop goes to UITrackingRunLoopMode. At this point, the Timer will not run and the method will not get fire. If you want the Timer not to expire while the ScrollView is rolling, you need to make sure that the Timer Settings are run on NSRunLoopCommonModes.
  3. Thread problem NSTimer cannot be used in child threads. If we want to execute a scheduled task in a child thread, we must enable RunLoop and manage the child thread ourselves. Otherwise NSTimer is invalid.
  4. NSTimer cannot dynamically change the interval if we want to increase or decrease the interval. You can only invalidate the previous NSTimer and then generate a new NSTimer to set the new interval.
  5. Closures are not supported. NSTimer only supports calls to selector and does not support the more modern closure syntax.

“V. Background timer continues to run”

Apps on Apple are generally not allowed to run in the background. For example, the timer is suspended when the user switches to the background and resumes only after the App is returned.

But any app can use UIApplication Background Tasks to run in the background for a short period of time, there is no other way.

“Vi. Pause/start of timer”

NSTimer suspended

 [timer setFireDate:[NSDate distantFuture]];

NSTimer continue

 [timer setFireDate:[NSDate date]];