解决NSTimer的强引用的问题
2021-08-18 本文已影响0人
guoguojianshu
下面的代码会造成循环引用的问题
@interface ViewController ()
@property (nonatomic,strong) NSTimer * timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 这个方式scheduled创建的timer都默认会被加入runloop中了
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}
-(void)timerTest{
NSLog(@"%s",__func__);
}
-(void)dealloc{
[self.timer invalidate];
}
@end
解决方法:
- 把self修改为弱引用,不能起作用,因为这个target传入的是一个地址,nstimer对这个地址进行强引用了,使用弱引用,是block的内部,使用弱引用的变量,对这个变量就是 弱引用的,是block的特性
//这样使用弱引用没有效果的
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(timerTest) userInfo:nil repeats:YES];
解决方法1:
- 可以使用block的方式,创建timer,在block中使用self的弱引用
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
2.使用代理方法,来解决强引用的问题
代理对象的结构
.h
@interface JGProxy : NSObject
@property (nonatomic,weak) id target;
+(instancetype)proxyWithTarget:(id)target;
@end
.m
@implementation JGProxy
+(instancetype)proxyWithTarget:(id)target{
JGProxy * proxy = [[JGProxy alloc]init];
proxy.target = target;
return proxy;
}
//可以在这个代理对象里面,写上没有实现的timerTest方法,然后再把这个方法发送给self.target,这样也能解决问题,就是如果这个方法变了,还得从新写个,麻烦
-(void)timerTest{
[self.target timerTest];
}
// 解决方法2:使用中间变量
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[JGProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
3.使用 代理方法来解决问题,使用消息转发来处理,解决方法2的问题
@implementation JGProxy
+(instancetype)proxyWithTarget:(id)target{
JGProxy * proxy = [[JGProxy alloc]init];
proxy.target = target;
return proxy;
}
//可以在这个代理对象里面,写上没有实现的timerTest方法,然后再把这个方法发送给self.target,这样也能解决问题,就是如果这个方法变了,还得从新写个,麻烦
//-(void)timerTest{
// [self.target timerTest];
//}
//使用消息转发机制来处理,集成子nsobjet对象,会先进入这个方法
-(id)forwardingTargetForSelector:(SEL)aSelector{
return self.target;
}
//这个两个方法是消息转发,forwardingTargetForSelector后面的两个方法
//-(IMP)methodForSelector:(SEL)aSelector{
// return [self.target methodForSelector:aSelector];
//}
//-(void)forwardInvocation:(NSInvocation *)anInvocation{
// [anInvocation invokeWithTarget:self.target];
//}
@end
4.使用继承自NSProxy的对象,这个对象专做为消息转发的
.h
@interface JGProxy1 : NSProxy
@property (nonatomic,weak) id target;
+(instancetype)proxyWithTarget:(id)target;
@end
.m
@implementation JGProxy1
+(instancetype)proxyWithTarget:(id)target{
JGProxy1 * proxy = [JGProxy1 alloc];
proxy.target = target;
return proxy;
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];
}
-(void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];
}
使用
// 解决方法3:使用中间变量,继承自nsproxy对象,推荐使用这个nsproxy对象,做消息转发,这个NSProxy是专业做消息转发的,省得继承自NSObject对象,再去走一遍消息机制,先去自己类对象里面找,没有找到再去父类里面找的过程了,然后再走消息转发机制
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[JGProxy1 proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
NSProxy是代理对象,里面的方法不是走消息机制,而是消息转发的机制,
ViewController * vc = [[ViewController alloc]init];
//这句是消息机制的
JGProxy * proxy = [JGProxy proxyWithTarget:vc];
//这句是走的消息转发的机制
JGProxy1 * proxy1 = [JGProxy1 proxyWithTarget:vc];
NSLog(@"%d %d",[proxy isKindOfClass:[ViewController class]],[proxy1 isKindOfClass:[ViewController class]]);
打印结果为
2021-08-18 21:12:18.648990+0800 nstimer强引用[9554:621191] 0 1
NSProxy的源码,直接是消息转发的机制
- (BOOL) isKindOfClass: (Class)aClass
{
NSMethodSignature *sig;
NSInvocation *inv;
BOOL ret;
sig = [self methodSignatureForSelector: _cmd];
inv = [NSInvocation invocationWithMethodSignature: sig];
[inv setSelector: _cmd];
[inv setArgument: &aClass atIndex: 2];
[self forwardInvocation: inv];
[inv getReturnValue: &ret];
return ret;
}
调用isKindOfClass直接走的是消息转发的,调用这个代码
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];
}
-(void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];
}
所以proxy1打印结果为1,是ViewController,走的消息转发,是target的,而target是ViewController
CADisplayLink的使用
使用CADisplayLink,使用方法和timer的一样,只是这个是和屏幕的刷新频率一样的,不用设计时间间隔,一秒刷新60次,60fps
self.link = [CADisplayLink displayLinkWithTarget:[JGProxy1 proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
-(void)linkTest{
NSLog(@"%s",__func__);
}