CADisplayLink、NSTimer循环引用解决方案

打印 上一主题 下一主题

主题 835|帖子 835|积分 2505

前言:CADisplayLink、NSTimer 循环引用问题

​        CADisplayLink、NSTimer会对Target产生强引用,如果target又对他们产生强引用,那么就会引发循环引用。
  1. @interface ViewController ()
  2. @property (nonatomic, strong) CADisplayLink *link;
  3. @property (nonatomic, strong) NSTimer *time;
  4. @end
  5. @implementation ViewController
  6. - (void)viewDidLoad {
  7.     [super viewDidLoad];
  8.     // 保证调用频率和刷帧频率 60fps
  9.     self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
  10.     [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode] ;
  11.    
  12.     self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeTest) userInfo:nil repeats:YES];
  13. }
  14. - (void)timeTest {
  15.     NSLog(@"%s", __func__);
  16. }
  17. - (void)linkTest {
  18.     NSLog(@"%s", __func__);
  19. }
  20. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  21.     NSLog(@"%s", __func__);
  22.     [self.time invalidate];
  23. }
复制代码
__weak typeof(self) weakSelf = self;能否解决NSTimer的循环引用问题?
答:我们并不能够通过__weak typeof(self) weakSelf = self;代码来实现解决循环引用。
__weak typeof(self) weakSelf = self;是用在block内可以解决循环引用的问题。

方案一、使用 NSTimer 使用 block(CADisplayLink 没有提供block api 不可以用)

​        NSTime 使用提供 block 的 API ,通过 __weak 解决循序引用问题
  1. - (void)viewDidLoad {
  2.     [super viewDidLoad];
  3.    
  4.     __weak typeof(self) weakSelf = self;
  5.     self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
  6.         [weakSelf timeTest];
  7.     } ];
  8. }
  9. - (void)timeTest {
  10.     NSLog(@"%s", __func__);
  11. }
  12. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  13.     NSLog(@"%s", __func__);
  14.     [self.time invalidate];
  15. }
复制代码
方案二、使用 NSObject 消息转发

​        通过中间变量,是中间的一个引用变成弱引用。
​        第一步:创建继承 NSObject 的 RHMiddleTarget 中间文件,创建 target 属性,使用 weak 弱引用修饰
  1. #import <Foundation/Foundation.h>
  2. @interface RHMiddleTarget : NSObject
  3. @property (nonatomic, weak) id target;
  4. + (instancetype)middleTargetWithTarget:(id)target;
  5. @end
  6. #import "RHMiddleTarget.h"
  7. @implementation RHMiddleTarget
  8. + (instancetype)middleTargetWithTarget:(id)target {
  9.     RHMiddleTarget *middleTarget = [[RHMiddleTarget alloc] init];
  10.     middleTarget.target = target;
  11.     return middleTarget;
  12. }
  13. - (id)forwardingTargetForSelector:(SEL)aSelector {
  14.     // 返回可以处理的对象, 给对象发送aSelector消息
  15.     // objc_msgSend(self.target, aSelector);
  16.     return self.target;
  17. }
  18. @end
复制代码
第二步:在使用 CADisplayLink 和 NSTimer 中的 target 传到中间变量里:
  1.     // 保证调用频率和刷帧频率 60fps
  2.     self.link = [CADisplayLink displayLinkWithTarget:[RHMiddleTarget middleTargetWithTarget:self] selector:@selector(linkTest)];
  3.     [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode] ;
  4.    
  5.     self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[RHMiddleTarget middleTargetWithTarget:self] selector:@selector(timeTest) userInfo:nil repeats:YES];
复制代码
第三步:测试验证:
  1. 2022-07-05 16:01:47.895716+0800 Interview03-定时器[10828:260303] -[ViewController timeTest]
  2. 2022-07-05 16:01:48.895649+0800 Interview03-定时器[10828:260303] -[ViewController timeTest]
  3. 2022-07-05 16:01:49.895571+0800 Interview03-定时器[10828:260303] -[ViewController timeTest]
  4. 2022-07-05 16:01:50.666056+0800 Interview03-定时器[10828:260303] -[ViewController touchesBegan:withEvent:]
复制代码
方案三、使用 NSProxy 消息转发

第一步:创建继承 NSObject 的 RHMiddleTarget 中间文件,创建 target 属性,使用 weak 弱引用修饰
  1. #import <Foundation/Foundation.h>
  2. @interface RHMiddleTarget : NSProxy
  3. @property (nonatomic, weak) id target;
  4. + (instancetype)middleTargetWithTarget:(id)target;
  5. @end
  6. #import "RHMiddleTarget.h"
  7. @implementation RHMiddleTarget
  8. + (instancetype)middleTargetWithTarget:(id)target {
  9.     RHMiddleTarget *middleTarget = [RHMiddleTarget alloc];
  10.     middleTarget.target = target;
  11.     return middleTarget;
  12. }
  13. // 返回方法签名
  14. - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
  15.     return [self.target methodSignatureForSelector:sel];
  16. }
  17. // 进行相应的调用
  18. - (void)forwardInvocation:(NSInvocation *)invocation {
  19.     [invocation invokeWithTarget:self.target];
  20. }
复制代码
第二步:在使用 CADisplayLink 和 NSTimer 中的 target 传到中间变量里
  1.     // 保证调用频率和刷帧频率 60fps
  2.     self.link = [CADisplayLink displayLinkWithTarget:[RHMiddleTarget middleTargetWithTarget:self] selector:@selector(linkTest)];
  3.     [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode] ;
  4.    
  5.     self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[RHMiddleTarget middleTargetWithTarget:self] selector:@selector(timeTest) userInfo:nil repeats:YES];
复制代码
第三步:测试验证
  1. 2022-07-05 16:27:25.427454+0800 Interview03-定时器[11550:281823] -[ViewController linkTest]
  2. 2022-07-05 16:27:25.444101+0800 Interview03-定时器[11550:281823] -[ViewController linkTest]
  3. 2022-07-05 16:27:25.456983+0800 Interview03-定时器[11550:281823] -[ViewController touchesBegan:withEvent:]
复制代码
方案四、使用 viewWillDisappear

​        在控制器销毁或者定时器停止的时候,调用如下的方法
  1. [self.timer invalidate];
  2. [self.link invalidate];
  3. self.timer = nil;
  4. self.link = nil;
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

郭卫东

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表