美文网首页
解决NSTimer的强引用的问题

解决NSTimer的强引用的问题

作者: guoguojianshu | 来源:发表于2021-08-18 21:37 被阅读0次

下面的代码会造成循环引用的问题

@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:

  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__);

}

相关文章

网友评论

      本文标题:解决NSTimer的强引用的问题

      本文链接:https://www.haomeiwen.com/subject/trfnbltx.html