当前位置: 首页 > news >正文

[iOS]-NSTimer与循环引用的理解

目录:

  • 参考的博客:
  • 问题引入
  • 循环引用
    • 简单的循环引用
    • Block中的循环引用强弱共舞
    • Delegate中的循环引用
  • NSTimer
    • 创建NSTimer
    • 销毁NSTimer
      • invalidate
      • fire
    • 面试题
    • 如何解决NSTimer强持有的问题?
      • 中间的代理对象
        • 使用NSObject类实现消息转发
        • 使用NSProxy类实现消息转发
      • 改变timer引用
      • 使用Category
      • 在合适的地方调用invalidate方法

参考的博客:

[iOS开发]NSTimer与循环引用的理解

NSTimer相关内容日常中时长会用到,且在runLoop中接触过,当时只是简单了解,NSTimer也是很重要的一部分,也是面试中经常会问到的,下面我们就详细总结一下这部分内容和循环引用部分的理解

问题引入

我们围绕这几个问题展开:

  • 什么是循环引用?
  • NSTimer里面为什么要强引用target
  • 有什么更好的方式来解决这种循环引用吗?

循环引用

简单的循环引用

简单的循环引用就是:

对象A持有对象B,同时对象B也持有对象A

我们举个简单例子体现一下循环引用:

@interface FirstPerson : NSObject 
 
@property (nonatomic, strong) SecondPerson *test; 
 
@end 
@interface SecondPerson : NSObject 
 
@property (nonatomic, strong) FirstPerson *test; 
 
@end 

如果不是循环引用就是单向持有,假设是A持有B,也就是BA的一个属性,而B不持有A

若对A发送release消息,发现持有对象B,则向对象B发送release消息,B对象执行dealloc方法,引用计数为0,释放内存,同时A对象引用计数也为0,内存得到正确释放

如果是循环引用

若对A发送release消息,发现持有B对象,则会向B对象发送release消息,等待b释放内存。B收到release消息后,发现持有A,于是也向A发送release消息,等待A释放内存。此时就会发生A等待B释放内存,B又等待A释放内存,造成了死锁,发生内存泄漏

如果我们使用weak就可以破解这个循环引用

若对A发送release消息,发现持有B对象,则会向B对象发送release消息,等待B释放内存。B收到release消息后,虽然持有Aweak),但不会等待A释放内存,此时引用计数为0,执行dealloc,释放内存,同时A的引用计数变成了0,执行dealloc,释放内存

Block中的循环引用强弱共舞

如果我们在某个页面的属性中声明了一个block,那么就相当于我们这个页面的self持有了这个block
如果我们在block再持有self,这个时候就会造成循环引用,例子如下:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.blk = ^{
        //5秒后执行
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakSelf);
        });
    };
    self.blk();
    //dismiss当前界面
	[self dismissViewControllerAnimated:YES completion:nil];
}

self对象持有blockblock里面又持有self,而且彼此之间都是默认的强引用,这就导致了一个循环引用,这个时候我们一执行后面的dismiss退出当前界面之后就会产生,界面已经销毁了,但是刚才产生的循环引用还在,就导致了内存泄漏

那这个问题要怎么解决呢?

我们可以对self进行weak处理,让block中持有的是weak修饰后的self,就可以避免循环引用,代码如下:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
	//用weak修饰self,然后让block持有这个weak修饰过后的self
    __weak typeof(self) weakSelf = self;
    self.blk = ^{
        //5秒后执行
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakSelf);
        });
    };
    self.blk();
    [self dismissViewControllerAnimated:YES completion:nil];
}

这样就没有什么问题了,由于是block弱引用self,所以已经打破了之前的循环引用,此时如果后面执行dismiss销毁当前视图的话,我们去打印刚才的self的值已经是null了。

但是单独添加一个weak修饰的self是有一些漏洞的,如果在block执行到关于self的那部分之前,我们的self在外部被提前释放掉了的话,那么当执行到block有关self的那块部分时程序就会崩溃。为了解决这个问题,我们就可以在weak修饰完self之后,让block持有再用strong修饰的已经被weak修饰过的self,在block内部让self的引用计数先加1保证self不会意外释放,然后当block中的内容执行完之后出block中代码的作用域的时候刚才加1的引用计数会自动release1,这样一来,既避免了循环引用,也避免了self意外释放,代码示例如下:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 
 	//用weak修饰self
	__weak typeof(self) weakSelf = self;
    self.blk = ^{
    	//用strong修饰被weak修饰过的self
    	__strong typeof(self) strongSelf = weakSelf;
        //5秒后执行
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", strongSelf);
        });
    };
    self.blk();
    [self dismissViewControllerAnimated:YES completion:nil];
}

Delegate中的循环引用

在这里插入图片描述
举个简单例子:我们当前界面的self持有一个tableView,然后tableViewdelegate又设置为我们的self,假如这之间都是强持有的话就会造成一个循环引用的效果,所以我们的delegate一般都需要用weak来修饰,用来避免循环引用。

NSTimer

之前学习的循环引用部分我们就结束了,下面步入正题NSTimer

创建NSTimer

创建NSTimer有三种方法:
常用的一种是:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

不常用的方法是:

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

这几种方法除了创建方式(参数)不同,方法类型不同(类方法,对象方法),还有什么不同?

我们常用的scheduledTimerWithTimeInterval相比与其他两种方法,其不仅仅是创建了NSTimer对象,还把该对象加入到了当前的runloop

NSTimer只有被加入到runloop中,才会生效,NSTimer才会真正执行

也就是说,如果我们想使用timerWithTimeIntervalinitWithFireDate的话,需要使用NSRunloop的以下方法将NSTimer加入到runloop

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode

销毁NSTimer

有两个方式:invalidatefire

invalidate

ver from ever firing again and requests its removal from its run loop
This method is the only way to remove a timer from an NSRunLoop object

阻止接收器再次触发,并请求将其从runloop中移除
此方法是从NSRunLoop对象中删除计时器的唯一方法

fire

Causes the receiver’s message to be sent to its target
If the timer is non-repeating, it is automatically invalidated after firing

使接收方的消息发送到其目标
如果计时器不重复,则会在触发后自动失效

总之,如果想要销毁NSTimer,那么一定要使用invalidate方法

那现在就有一个问题了,我们对NSTimernil可以让iOS系统帮我买销毁NSTimer吗?

答案是否定的

为什么呢?

我们通过看ARC下的引用计数来探究一下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));

    _timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timerSelector) userInfo:nil repeats:NO];

    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));

    [_timer invalidate];

    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));

}

- (void) timerSelector {
    NSLog(@"调用了NSTimer!");
}

打印结果如下:
请添加图片描述
加入了NSTimer之后,引用计数从26变为了27

这个其实是timerViewController对象进行了强引用
因为如果要让timer运行的时候执行ViewController下面的timerSelector:timer就需要知道target,并且保存这个target。以便于在以后执行[target performSelector:];,这里的target就是指ViewControllerself

所以,NSTimerViewController是相互之间强引用的,这样子就形成了循环引用的问题。

为了解除循环引用,在 invalidate 这个方法下,timer之前保存的target被设置成了nil,强制断开了循环引用。这点和直接设置timer = nil是差不多的。但是invalidate还做了另外一个动作,就是解除了runLooptimer的强引用,使得timer成功停止

从官方文档中我们可以看到这一点:
请添加图片描述
翻译版如下:
请添加图片描述

  1. 解除RunLoop对象对于该定时器的强引用和定时器对其设置的target等的强引用
  2. RunLoop中移除该定时器
    重要的点: 必须在创建定时器的那个线程中调用invalidate方法,否则可能不会从RunLoop中删除与该计时器相关的输入源,从而阻止线程正确退出

所以在创建NSTimer对象的类的dealloc里面去invalidate timer的这个做法是不行的,因为都是相互强引用的,在定时器invalidate前,我们创建NSTimer对象的类的dealloc不可能被执行到。

面试题

  • NSTimer一定会持有self吗?
    从官方文档看三种创建方法:
  1. + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
    请添加图片描述
    其中关键部分target的翻译如下:

计时器触发时将消息发送到对象,该对象由选择器指定。计时器保持对target的强引用,直到它(计时器)失效。

  1. + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
    请添加图片描述
    其中关键部分target的翻译如下:

计时器触发时将消息发送到对象,该对象由选择器指定。计时器保持对该对象的强引用,直到它(计时器)失效。

  1. - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
    请添加图片描述
    其中关键部分target的翻译如下:

计时器触发时将消息发送到对象,该对象由选择器指定。计时器保持对该对象的强引用,直到它(计时器)失效。

这三种方法关于target的注释无一例外,都是说明定时器会对我们设置的target对象进行强引用。

而且苹果应该也是发现了timer容易造成内存泄漏的问题,所以iOS10以后,这三个方法出了对应新的API,如下:
请添加图片描述
请添加图片描述
请添加图片描述
由于这三个API将定时器中我们要执行的任务加到了block中去,且不用设置target,所以就很容易避免循环引用,只需要注意这个block中要用到self的话采用强弱共舞即可。

  • 如何解决NSTimer强持有target的问题?
    我们在合适时机手动调用invalidate销毁定时器,或者使用一个代理对象,让timer引用代理对象,代理对象弱引用self,就可以避免循环引用,最合适的就是使用代理对象转发,系统提供的NSProxy(消息转发机制)。我们下面再说
  • 那这里self不持有NSTimer也会出现循环引用吗?
    这个有点儿像设套的,说是也许,说不也许,只能说是彼此之间相互引用,导致无法释放,是否可以释放不一定非得取决于循环引用。由于使用NSTimer时是需要添加到RunLoop中的,如果是self不持有,但是NSTimer需要引用target所以必须持有self,这样又会造成一个 runloop –> timer –> self 这么一个强引用链,导致大家都又释放不了。

两种释放不了的情况如下图所示:
请添加图片描述
请添加图片描述

  • runloop会持有NSTimer
    NSTimer只有被加入到runloop中,才会生效,NSTimer才会真正执行,真正执行了RunLoop就会持有NSTimer

到这里肯定有人疑惑,如果将NSTimertarget弱持有self可以解决这个循环引用问题吗?代码如下:

	// 让 timer 对self 产生弱引用
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(test) userInfo:nil repeats:YES];

答案是不行!

原因就是当我们使用__weakself转为弱指针的时候,这个只有在Block变脸捕获的时候才生效。所以这里我们应该使用NSTimerblock方法,代码如下:

	// 让 timer 对self 产生弱引用
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf test];
    }];

这样就成功了,类似的block版的API我们上方已经介绍过了,一共有三种API

那么还是疑惑究竟是什么导致的NSTimer弱引用self都无法解决呢?

答案在这里:
其实scheduledTimerWithTimeInterval这个方法会调用initWithFireDate方法,我们去GNUStep源码里面找到NSTimer.m,看看initWithFireDate的实现(此处附上:GNUStep源码下载地址):

- (id) initWithFireDate: (NSDate*)fd
	       interval: (NSTimeInterval)ti
		 target: (id)object
	       selector: (SEL)selector
	       userInfo: (id)info
		repeats: (BOOL)f
{
  if (ti <= 0.0)
    {
      ti = 0.0001;
    }
  if (fd == nil)
    {
      _date = [[NSDate_class allocWithZone: NSDefaultMallocZone()]
        initWithTimeIntervalSinceNow: ti];
    }
  else
    {
      _date = [fd copyWithZone: NSDefaultMallocZone()];
    }
  //下面这行代码是关键原因,不管传入的target是strong修饰还是weak修饰,timer持有的都是target的强引用
  _target = RETAIN(object);
  _selector = selector;
  _info = RETAIN(info);
  if (f == YES)
    {
      _repeats = YES;
      _interval = ti;
    }
  else
    {
      _repeats = NO;
      _interval = 0.0;
    }
  return self;
}

我们可以看到其中关键的:_target = RETAIN(object);,由此可见,不管你传入的targetstrong修饰还是weak修饰,timer持有的都是target的强引用,所以传入weakSelf是行不通的。

如何解决NSTimer强持有的问题?

这是上面的第二个面试题

中间的代理对象

使用NSObject类实现消息转发

请添加图片描述
就是图中所示的这样一个效果

正常使用target-action即将target设置为self,当页面销毁时,不走dealloc方法,造成循环引用:
代码例子如下:

ViewController.h中:
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end


ViewController.m中:
#import "ViewController.h"
#import "SecondController.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

	UIButton *buttonTest = [UIButton buttonWithType:UIButtonTypeCustom];
    [buttonTest setTitle:@"跳转" forState:UIControlStateNormal];
    [buttonTest setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [buttonTest addTarget:self action:@selector(pressButton) forControlEvents:UIControlEventTouchUpInside];
    buttonTest.frame = CGRectMake(100, 200, 60, 30);
    [self.view addSubview:buttonTest];
}

//当点击跳转按钮时自动跳转进secondController界面
- (void) pressButton {
    SecondController *secondController = [[SecondController alloc] init];
    secondController.modalPresentationStyle = UIModalPresentationFullScreen;
    [self presentViewController:secondController animated:NO completion:nil];
}

@end


//SecondController.h中:
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface SecondController : UIViewController

@end

NS_ASSUME_NONNULL_END


//SecondController.m中:
#import "SecondController.h"

@interface SecondController ()
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation SecondController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

	//打印创建timer前后的self的引用计数
    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));
     
     //创建timer,这个方法会自定将timer添加到RunLoop中去
    _timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timerSelector) userInfo:nil repeats:YES];

    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));

    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));

    
    self.view.backgroundColor = [UIColor whiteColor];
    
    //设置一个按钮,点击后dismiss销毁当前视图
    UIButton *buttonTest = [UIButton buttonWithType:UIButtonTypeCustom];
    [buttonTest setTitle:@"返回" forState:UIControlStateNormal];
    [buttonTest setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [buttonTest addTarget:self action:@selector(pressButton) forControlEvents:UIControlEventTouchUpInside];
    buttonTest.frame = CGRectMake(100, 200, 60, 30);
    [self.view addSubview:buttonTest];
}

//按钮的事件函数
- (void) pressButton {
    //销毁当前视图返回到原来的视图
    [self dismissViewControllerAnimated:NO completion:nil];
}

//定时器要执行的函数
- (void)timerSelector {
    NSLog(@"----");
}

//重写dealloc方法做一个标记
- (void) dealloc {
 	//打印标记
    NSLog(@"SecondController dealloc!");
    [_timer invalidate];
}

@end

我们执行代码,过一段时间后点击返回按钮后发现并没有调用dealloc方法,视图已经销毁了,但是视图本身selftimer形成了循环引用,导致定时器依然在运作,打印结果如下:
请添加图片描述
接下来我们创建一个中间类:

TestProxy.h中:
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface TestProxy : NSObject

@property (nonatomic, weak) id target;

+ (instancetype) proxyWithTarget:(id)target;

@end

NS_ASSUME_NONNULL_END


TestProxy.m中:
#import "TestProxy.h"

@implementation TestProxy

//实现我们的类方法,创建一个TestProxy类型的对象,然后将传入的参数作为target便于后续的消息转发
+ (instancetype) proxyWithTarget:(id)target {
    TestProxy *proxy = [[TestProxy alloc] init];
    proxy.target = target;
    return proxy;
}

//因为我们的target是新类,而不是self,所以SEL应该也是新类中的SEL,所以我们调用sel方法的时候可以通过第二次拯救,更改timerSelector这条消息的接受者为self.target,(当然我们再实现一次也可以)
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.target;
}

@end

有这么一个中间类之后,我们添加timer的target时就可以这样子了:

//只需要调用TestProxy类的类方法时,将参数传成当前界面的self即可
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TestProxy proxyWithTarget:self] selector:@selector(timerSelector) userInfo:nil repeats:YES];

这样子就是利用了消息转发的第二次拯救,实现了将timertarget设置为中间对象,从而避免了timerself的循环引用。

现在的打印结果如下:
请添加图片描述
可以看到成功打印dealloc,并且成功销毁了定时器。

使用NSProxy类实现消息转发

这是一个专门用于做消息转发的类,我们需要通过子类的方式来使用它(与第一种方法基本上一样,不过调用时机不同罢了,具体如下:
在这里插入图片描述
所以用NSObject也可以,但是用NSproxy会大幅提升性能。

NSProxy的子类需要实现两个方法,即消息转发的第三次拯救的那一对方法:methodSignatureForSelector:forwardInvocation:,具体应用如下:

TestNSproxy.h中:
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface TestNSproxy : NSProxy

@property (nonatomic, weak) id target;

+ (instancetype)proxyWithTarget:(id)target;

@end

NS_ASSUME_NONNULL_END


TestNSproxy.m中:
#import "TestNSproxy.h"

@implementation TestNSproxy

//实现我们的类方法,创建一个TestNSproxy类型的对象,然后将传入的参数作为target便于后续的消息转发
+ (instancetype)proxyWithTarget:(id)target {
    TestNSproxy *proxy = [TestNSproxy alloc];
    proxy.target = target;
    return proxy;
}

//下面实现第三次消息拯救中的那两个方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}

@end


作为中间类作为timer的target的时候:
//只需要调用TestProxy类的类方法时,将参数传成当前界面的self即可
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TestNSproxy proxyWithTarget:self] selector:@selector(timerSelector) userInfo:nil repeats:YES];

我们的打印结果如下:
请添加图片描述
可以看到是成功打印dealloc,并且成功销毁了定时器。

NSProxy类的优点在于直接执行了消息转发机制三次拯救的第三步的调用methodSignatureForSelector:返回方法签名,如果方法签名不为nil,调用forwardInvocation:来执行该方法,会跳过前面的步骤,提高性能

改变timer引用

上面说过我们可以采用block类型的定时器API,结合self强弱共舞的方法来解决循环引用问题,使得定时器可以及时释放。

而且对于CADisplayLinkNSTimer来说,无论外面传递的target弱指针还是强指针,都会传入一个内存地址定时器内部都是对这个内存地址产生强引用,所以传递弱指针没有用

所以我们采用不带target参数的block类型的定时器API是非常好的选择,用法如下(上方已经讲过一次了):

	// 让 timer 对self 产生弱引用
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf test];
    }];

使用Category

通过 categoryNSTimertarget 设置为 NSTimer 类,让 NSTimer 自身做为target, 把 selector 通过 block 传入给 NSTimer,在 NSTimercategory 里面触发 selector 。这样也可以达到 NSTimer 不直接持有 TimerViewController 的目的,实现更优雅 ( 如果是直接支持 iOS 10 以上的系统版本,那可以使用 iOS 10新增的系统级 block 方案 ),代码示例如下:

首先新建一个NSTimer类的分类:BlocksSupportSecond
BlocksSupportSecond.h中:
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSTimer (BlocksSupportSecond)

//我们重写封装的一个创建定时器的接口
+ (NSTimer *)tay_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block: (void(^)(void))block;

@end

NS_ASSUME_NONNULL_END


BlocksSupportSecond.m中:
#import "NSTimer+BlocksSupportSecond.h"

@implementation NSTimer (BlocksSupportSecond)

//我们重写封装的一个创建定时器的接口
+ (NSTimer *)tay_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void(^)(void))block {
    return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(tay_blockInvoke:) userInfo:[block copy] repeats:repeats];
}

//用新接口创建定时器时最后在block中添加的操作会传到这里来执行
+ (void)tay_blockInvoke:(NSTimer *)timer {
    void (^block)(void) = timer.userInfo;
    if(block) {
        block();
    }
}

@end


SecondController.h中:
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface SecondController : UIViewController

@end

NS_ASSUME_NONNULL_END


SecondController.m中:
#import "SecondController.h"
#import "NSTimer+BlocksSupportSecond.h"

@interface SecondController ()
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation SecondController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));
	
	//用咱们封装的新接口创建定时器,将定时器要执行的操作放到最后的block参数中
    _timer = [NSTimer tay_scheduledTimerWithTimeInterval:2 repeats:YES block:^{
        NSLog(@"----");
    }];
    
    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));

    //[_timer invalidate];

    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));

    
    self.view.backgroundColor = [UIColor whiteColor];
    
    UIButton *buttonTest = [UIButton buttonWithType:UIButtonTypeCustom];
    [buttonTest setTitle:@"返回" forState:UIControlStateNormal];
    [buttonTest setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [buttonTest addTarget:self action:@selector(pressButton) forControlEvents:UIControlEventTouchUpInside];
    buttonTest.frame = CGRectMake(100, 200, 60, 30);
    [self.view addSubview:buttonTest];
}

//按钮的事件函数
- (void) pressButton {
    [self dismissViewControllerAnimated:NO completion:nil];
}

//重写dealloc方法做一个标记
- (void) dealloc {
     //打印标记
    NSLog(@"SecondController dealloc!");
    [_timer invalidate];
}

@end

ViewController.h和ViewController.m文件中的代码同上方其他例子,此处不再赘述

打印结果如下:
请添加图片描述
可以看到是成功打印dealloc,并且成功销毁了定时器。

到这里,有人肯定会想到,用子类去新封装这个接口是不是也可以实现分类实现的效果呢? 答案是不能的,因为创建定时器实质上就是调用的- (id) initWithFireDate: (NSDate*)fd interval: (NSTimeInterval)ti target: (id)object selector: (SEL)selector userInfo: (id)info repeats: (BOOL)f;方法,这个方法我们上方有讲过,值得注意的是,这个实例方法的调用者类型必须是严格的NSTimer类型,即使是NSTimer的子类也是不行的,所以我们就没发去子类里面写新接口了,因为写的话需要子类的实例对象调用initWithFireDate:方法,而这个方法的调用者类型必须是严格的NSTimer类型,所以要想写新接口的话,还是得移步分类中。

在合适的地方调用invalidate方法

这个就是一个比较取巧的方法,麻烦之处就是得记得去在合适的地方及时调用invalidate方法

相关文章:

  • SpringCloudAlibaba2.2.6.RELEASE集成Gateway并实现JWT鉴权
  • Alevel经济知识点讲解:effects of deflation
  • 安装Docker后的一些配置
  • 智能驾驶功能软件平台设计规范第三部分:预测功能服务接口
  • 网课搜题公众号在线制作
  • Linux性能优化思路和方法
  • 什么是数据仓库?
  • 01|一条SQL查询语句是如何查询的?
  • NLP基础
  • 公众号查题系统平台
  • 129、LeetCode-392.判断子序列
  • Python面向对象编程
  • java计算机毕业设计霍山石斛网站源码+数据库+系统+lw文档+mybatis+运行部署
  • Python文件处理与垃圾回收机制
  • java计算机毕业设计基于MVC框架的在线书店设计源码+数据库+系统+lw文档+mybatis+运行部署
  • 002-读书笔记-JavaScript高级程序设计 在HTML中使用JavaScript
  • C++类中的特殊成员函数
  • CentOS7简单部署NFS
  • dva中组件的懒加载
  • JS 面试题总结
  • Js基础——数据类型之Null和Undefined
  • k8s如何管理Pod
  • laravel5.5 视图共享数据
  • mysql 数据库四种事务隔离级别
  • mysql常用命令汇总
  • Nacos系列:Nacos的Java SDK使用
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • react-core-image-upload 一款轻量级图片上传裁剪插件
  • Redis在Web项目中的应用与实践
  • TypeScript实现数据结构(一)栈,队列,链表
  • Web设计流程优化:网页效果图设计新思路
  • XForms - 更强大的Form
  • 技术:超级实用的电脑小技巧
  • 检测对象或数组
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 排序算法之--选择排序
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 深入浅出Node.js
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • 追踪解析 FutureTask 源码
  • 容器镜像
  • ​香农与信息论三大定律
  • #if 1...#endif
  • (12)Linux 常见的三种进程状态
  • (2)Java 简介
  • (c语言版)滑动窗口 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度
  • (附源码)springboot家庭装修管理系统 毕业设计 613205
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .net core 6 redis操作类
  • .net framework 4.0中如何 输出 form 的name属性。
  • .net mvc部分视图
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .net 获取url的方法