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

​ CADisplayLink、NSTimer会对Target产生强引用,如果target又对他们产生强引用,那么就会引发循环引用。

@interface ViewController ()

@property (nonatomic, strong) CADisplayLink *link;
@property (nonatomic, strong) NSTimer *time; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; // 保证调用频率和刷帧频率 60fps
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode] ; self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeTest) userInfo:nil repeats:YES];
} - (void)timeTest {
NSLog(@"%s", __func__);
} - (void)linkTest {
NSLog(@"%s", __func__);
} - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
[self.time invalidate];
}

__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 解决循序引用问题

- (void)viewDidLoad {
[super viewDidLoad]; __weak typeof(self) weakSelf = self;
self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timeTest];
} ];
} - (void)timeTest {
NSLog(@"%s", __func__);
} - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
[self.time invalidate];
}

方案二、使用 NSObject 消息转发

​ 通过中间变量,是中间的一个引用变成弱引用。

​ 第一步:创建继承 NSObject 的 RHMiddleTarget 中间文件,创建 target 属性,使用 weak 弱引用修饰

#import <Foundation/Foundation.h>

@interface RHMiddleTarget : NSObject

@property (nonatomic, weak) id target;

+ (instancetype)middleTargetWithTarget:(id)target;

@end

#import "RHMiddleTarget.h"

@implementation RHMiddleTarget

+ (instancetype)middleTargetWithTarget:(id)target {
RHMiddleTarget *middleTarget = [[RHMiddleTarget alloc] init];
middleTarget.target = target;
return middleTarget;
} - (id)forwardingTargetForSelector:(SEL)aSelector {
// 返回可以处理的对象, 给对象发送aSelector消息
// objc_msgSend(self.target, aSelector);
return self.target;
} @end

第二步:在使用 CADisplayLink 和 NSTimer 中的 target 传到中间变量里:

    // 保证调用频率和刷帧频率 60fps
self.link = [CADisplayLink displayLinkWithTarget:[RHMiddleTarget middleTargetWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode] ; self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[RHMiddleTarget middleTargetWithTarget:self] selector:@selector(timeTest) userInfo:nil repeats:YES];

第三步:测试验证:

2022-07-05 16:01:47.895716+0800 Interview03-定时器[10828:260303] -[ViewController timeTest]
2022-07-05 16:01:48.895649+0800 Interview03-定时器[10828:260303] -[ViewController timeTest]
2022-07-05 16:01:49.895571+0800 Interview03-定时器[10828:260303] -[ViewController timeTest]
2022-07-05 16:01:50.666056+0800 Interview03-定时器[10828:260303] -[ViewController touchesBegan:withEvent:]

方案三、使用 NSProxy 消息转发

第一步:创建继承 NSObject 的 RHMiddleTarget 中间文件,创建 target 属性,使用 weak 弱引用修饰

#import <Foundation/Foundation.h>

@interface RHMiddleTarget : NSProxy

@property (nonatomic, weak) id target;

+ (instancetype)middleTargetWithTarget:(id)target;

@end

#import "RHMiddleTarget.h"

@implementation RHMiddleTarget

+ (instancetype)middleTargetWithTarget:(id)target {
RHMiddleTarget *middleTarget = [RHMiddleTarget alloc];
middleTarget.target = target;
return middleTarget;
} // 返回方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
} // 进行相应的调用
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}

第二步:在使用 CADisplayLink 和 NSTimer 中的 target 传到中间变量里

    // 保证调用频率和刷帧频率 60fps
self.link = [CADisplayLink displayLinkWithTarget:[RHMiddleTarget middleTargetWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode] ; self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[RHMiddleTarget middleTargetWithTarget:self] selector:@selector(timeTest) userInfo:nil repeats:YES];

第三步:测试验证

2022-07-05 16:27:25.427454+0800 Interview03-定时器[11550:281823] -[ViewController linkTest]
2022-07-05 16:27:25.444101+0800 Interview03-定时器[11550:281823] -[ViewController linkTest]
2022-07-05 16:27:25.456983+0800 Interview03-定时器[11550:281823] -[ViewController touchesBegan:withEvent:]

方案四、使用 viewWillDisappear

​ 在控制器销毁或者定时器停止的时候,调用如下的方法

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

CADisplayLink、NSTimer循环引用解决方案的更多相关文章

  1. iOS 循环引用解决方案

    一.BLOCK 循环引用 一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身.构成循环引用. // 定义 block 的时候,会对外部变量做一次 cop ...

  2. Atitit.json xml 序列化循环引用解决方案json

    Atitit.json xml 序列化循环引用解决方案json 1. 循环引用1 2. 序列化循环引用解决方法1 2.1. 自定义序列化器1 2.2. 排除策略1 2.3. 设置序列化层次,一般3级别 ...

  3. 解决NSTimer循环引用Retain Cycle问题

    解决NSTimer循环引用Retain Cycle问题 iOS开发中以下的情况会产生循环引用 block delegate NSTimer 循环引用导致一些对象无法销毁,一定的情况下会对我们横须造成影 ...

  4. 解决NSTimer循环引用

    NSTimer常见用法 @interface XXClass : NSObject - (void)start; - (void)stop; @end @implementation XXClass ...

  5. NSTimer循环引用的几种解决方案

    前言 在iOS中,NSTimer的使用是非常频繁的,但是NSTimer在使用中需要注意,避免循环引用的问题.之前经常这样写: - (void)setupTimer { self.timer = [NS ...

  6. 用block解决nstimer循环引用

    大多数开发者可能都会这样来实现定时器.创建定时器的时候,由于目标对象是self,所以要保留此实例.然而,因为定时器是用实例变量存放的,所以实例也保留了定时器,这就造成了循环引用.除非调用stop方法, ...

  7. NSTimer循环引用的问题

    前言: 记得之前看过一个面试题问:ARC环境下的dealloc方法有什么用?问题解答是:代理指针置空,停止定时器timer,注销通知,释放掉实例变量.看着没什么问题,而且网上一收也是大概这样的答案.今 ...

  8. OC MRC之循环引用问题(代码分析)

    // // main.m // 07-循环引用 // // Created by apple on 13-8-9. // Copyright (c) 2013年 itcast. All rights ...

  9. iOS 里面 NSTimer 防止 循环引用

    使用NSTimer的类 #import "TBTimerTestObject.h" #import "TBWeakTimerTarget.h" @interfa ...

随机推荐

  1. 从OC角度思考OKR的底层逻辑

    原创不易,求分享.求一键三连 扩展阅读:什么是OKR OC:Organization Cultrue即组织文化,标题用OC纯粹为了装逼... ​自从接受公司文化建设工作后,思维发生了很大的变化,文化, ...

  2. Glide源码解析二---into方法

    转载请标明出处,维权必究: https://www.cnblogs.com/tangZH/p/12543154.html Glide作为一个强大的图片加载框架,已经被android官方使用,所以,明白 ...

  3. 用crash tool观察ARM64 Linux地址转换

    初学者学习Linux系统地址转换时,如果只是学习理论,又或者研读代码,那可能感觉比较枯燥.此时如果可以利用某些工具实际观察一下地址转换的过程,那可能会给枯燥的内核学习带来些微的乐趣.crash too ...

  4. k8s的api资源

    NAME SHORTNAMES APIGROUP NAMESPACED KIND 资源用途说明 bindings     TRUE Binding 已弃用.用于记录一个object和另一个object ...

  5. 初识 Redis 以及其基本使用方法

     1.什么是Redis  redis 是一个高性能的key-value数据库,它支持的类型更多 包括 string(字符串).list(链表).set(集合).zset(sorted set --有序 ...

  6. 大白话讲Java的锁

    偏向锁 对一个对象的锁偏向于某个线程,在markword中记录线程id 下次相同的线程来,直接就可以获取锁 轻量级锁 对象的Markword记录锁地址 跟线程栈里面的锁记录Lock Record的锁地 ...

  7. ARC126F

    [ARC126F] Affine Sort 给定一个长为 \(N\) 的序列 \(x\) ,定义 \(f(K)\) 表示满足下述条件的 \((a,b,c)\) 个数: \(1\le c\le K,0\ ...

  8. dotnet-cnblog-tool 图片上传失败问题

    dotnet-cnblog-tools 这个工具是将本地的 Markdown 文件转换为 可以上传到 cnblog 的格式,并且会将图片自动上传到 cnblog 的图床. 具体可以参考这篇文章: cn ...

  9. 深入C++05:运算符重载

    运算符重载 1.复数类 运算符重载目的:使对象运算表现得和编译器内置类型一样: 复数类例子 #include<iostream> using namespace std; class CC ...

  10. 从零开始实现lmax-Disruptor队列(二)多消费者、消费者组间消费依赖原理解析

    MyDisruptor V2版本介绍 在v1版本的MyDisruptor实现单生产者.单消费者功能后.按照计划,v2版本的MyDisruptor需要支持多消费者和允许设置消费者组间的依赖关系. 由于该 ...