前言:CADisplayLink、NSTimer 迴圈引用問題 CADisplayLink、NSTimer會對Target產生強引用,如果target又對他們產生強引用,那麼就會引發迴圈引用。 @interface ViewController () @property (nonatomic, ...
前言: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;