Do you really know the correct usage of NSTimer?

Posted by mnetsys on Fri, 26 Nov 2021 18:46:47 +0100

NSTimer can you really use it? I believe everyone will confidently say: I know! It's simple, but are you sure you're using it right?

1. Use of nstimer

A: NSTimer can you really use it? Do you know the circular reference of NSTimer?
B: It's not easy, isn't it 👇 This kind of use, So easy!

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];

	[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

- (void)timerAction{

	 NSLog(@"Printed");
}

- (void)dealloc {

	[self.timer invalidate];
	self.timer = nil;

	 NSLog(@"** dealloc **");
}


A: Look at the circular reference! Page pop, dealloc didn't go at all! Your setting invalidate is invalid!

B: I was careless. I didn't think it over. I'll think about it again! Um.... Don't I just use the viewWillDisappear method before the page disappears?

2. NSTimer circular reference

-(void)viewWillDisappear:(BOOL)animated{
	[super viewWillDisappear:animated];

	[self.timer invalidate];
	self.timer = nil;
}


B: You see, isn't circular reference solved now? dealloc is gone, which means that the object is released and there is no strong reference.

A: It seems that the circular reference problem has been solved, but there are other problems. It is obviously inappropriate to put viewWillDisappear here. When the pop is half, the page rebounds back. At this time, the timer fails, but my page has not been destroyed! This is not user-friendly!

B: What should I do?

A: First, let's analyze. Generally, we create a timer holding relationship as follows:

B: Then I changed the target object's weak reference to NSTimer to solve the problem of circular holding? As follows:

A: No, No. The runloop of the main thread will not be destroyed during the running of the program. It is longer than the life cycle of self. That is, runloop references timer, timer will not be destroyed, timer references target, and target will not be destroyed. Runloop spacing holds target. As follows:

To break the circular reference, we can only break it from the relationship between timer and target.

3. Resolve circular reference of intermediate objects

You can use an intermediate object to weakly reference NSTimer and target, which is solved. When the VC is destroyed, the target will also be destroyed. You can judge whether the target is nil through the intermediate object. In this way, NSTimer can be set as invalid and nil, which breaks the purpose of circular reference.

Don't talk much. Look at the code.

#import "NSTimer+weakTimer.h"

@interface TimerWeakObject : NSObject
/** target */
@property (nonatomic, weak) id target;
/** timer Expired callback method selector */
@property (nonatomic, assign) SEL selector;
/** timer */
@property (nonatomic, weak) NSTimer *timer;

- (void)cancel:(NSTimer*)timer;

@end

@implementation TimerWeakObject

- (void)cancel:(NSTimer*)timer {
	
	//Determine whether the target exists
	if (self.target) {
		if ([self.target respondsToSelector:self.selector]) {
			[self.target performSelector:self.selector withObject:timer.userInfo];
		}
	}else{
		//If it does not exist, set the timer to invalid, that is, runLoop releases the strong reference to timer
		//At the same time, timer will also release TimerWeakObject
		[self.timer invalidate];
	}

}

@end

@implementation NSTimer (weakTimer)

+ (NSTimer*)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {

	TimerWeakObject *obj = [[TimerWeakObject alloc]init];
	obj.target = target;
	obj.selector = selector;
	// Create a system NSTimer to TimerWeakObject
	obj.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:obj selector:@selector(cancel:) userInfo:userInfo repeats:repeats];

	return  obj.timer;
}

@end

Here, an NSTimer classification is created. An intermediate object is created in the classification to process external NSTimer events through the intermediate object.

It's also very simple to use, one line of code.

self.timer = [NSTimer scheduledWeakTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];


The effect is very obvious. dealloc is gone, indicating that the object is released and there is no strong reference.

4. System api method to solve circular reference

After iOS 10, apple optimized NSTimer and used Block callback to solve the circular reference problem.

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
		 NSLog(@"Printed");
    }];
- (void)dealloc {

	[self.timer invalidate];
	self.timer = nil;

	 NSLog(@"** dealloc **");
}

Using the api method of this system, dealloc will be executed. Just cancel the timer in dealloc.

And below 👇 One way is that this page suitable for push is not suitable for present.

//When removing VC in the life cycle, this page suitable for push is not suitable for the Present
- (void)didMoveToParentViewController:(UIViewController *)parent {
    if (parent == nil) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

If you go out for an interview and the interviewer asks you how to use NSTimer correctly, you should know how to answer it! (the middle object is recommended to answer, Ps: why? You know, pretend to force! 😁)

There are many ways to do this. Here are not one of the ways to introduce it. What do you know about the old fellow railway? Welcome to leave a comment in the comments section.

5. Write it in the back

Follow me and more content will be output continuously

🌹 Just like it 👍🌹

🌹 If you think you have something to gain, you can have a wave of Collection + attention, so that you won't find me next time 😁🌹

🌹 Welcome to leave a message, criticize and correct, forward, please indicate the source, thank you for your support! 🌹

Topics: iOS Interview