浅谈NSTimer基本用法和循环引用问题

相信大家对NSTimer不陌生,定时器是我们项目中经常用到的组件,定时去完成某项任务。下面我们来简单介绍一下NSTimer。

一、NSTimer基本原理

官方文档对NSTimer的介绍:

A timer waits until a certain time interval has elapsed and then fires,

sending a specified message to a target object.

由此可见,NSTimer就是计时器等待特定的时间间隔,然后触发,向目标对象发送指定的消息。实际上NSTimer是依赖于runloop来实现定时器功能的。

 

二、scheduledTimerWithTimeInterval和timerWithTimeInterval的区别

scheduledTimerWithTimeInterval和timerWithTimeInterval这两个类方法都可以创建一个NSTimer对象,但是稍微有点不同,scheduledTimerWithTimeInterval创建的NSTimer对象默认被加入到NSRunloop里面了,不要写用户手动添加,timerWithTimeInterval创建的NSTimer对象是一个autorelease类型,并且需要用户将NSTimer对象添加到NSRunloop,不然定时器不会工作。

 

三、NSTimer循环引用

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeEvent) userInfo:nil repeats:YES];

分析一下这段代码,target持有timer强引用,timer持有target强引用,造成循环引用,如果你直接退出target,你会发现target的dealloc根本没有调用。想要避免这个问题,必须打破循环引用。有的同学就想了,如果我不传入self,我传入weakSelf,这样是不是就可以避免循环引用了呢?

__weak typeof(self) weakSelf = self;

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(timeEvent) userInfo:nil repeats:YES];

很遗憾,这种方法还是会循环引用,因为scheduledTimerWithTimeInterval这个方法会调用initWithFireDate方法,我们去GNUStep源码里面找到NSTimer.m,看看initWithFireDate的实现

_target = RETAIN(object);  由此可见,不管你传入的target是strong修饰还是weak修饰,timer持有的都是target的强引用,所以传入weakSelf是行不通的。那么怎么避免NSTimer循环引用问题呢?有两种方法,一是退出target之前,手动释放定时器([timer invalidate]; timer = nil;),二是利用抽象类NSProxy,原理是继承于NSProxy的子类持有target弱引用,timer持有NSProxy子类强引用,NSProxy子类没有selector实现,对timer的selector进行消息转发给target处理。target、timer和NSProxy三者之间并没有形成循环引用,故而退出target时会调用dealloc。

SCProxy.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SCProxy : NSProxy

- (instancetype)initWithTarget: (id)target;

+ (instancetype)proxyWithTarget: (id)target;

@end

NS_ASSUME_NONNULL_END

SCProxy.m

#import "SCProxy.h"

@interface SCProxy()

@property (nonatomic, weak, readonly) NSObject *target;

@end

@implementation SCProxy

- (instancetype)initWithTarget: (id)target {
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget: (id)target {
    return [[self alloc] initWithTarget:target];
}

// 消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    if (self.target && [self.target respondsToSelector:sel]) {
        return [self.target methodSignatureForSelector:sel];
    }
    
    return [super methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    SEL aSelector = [invocation selector];
    if (self.target && [self.target respondsToSelector:aSelector]) {
        [invocation invokeWithTarget:self.target];
    } else {
        [super forwardInvocation:invocation];
    }
}

@end

timer传入SCProxy对象:

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[SCProxy proxyWithTarget:self] selector:@selector(timeEvent) userInfo:nil repeats:YES];

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值