错误用法
NSTimer 和 self会导致相互引用
@interface CircleRetainViewController ()
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation CircleRetainViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
if (!self.timer) {
// 这是一种会导致循环引用的错误用法
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.f
target:self
selector:@selector(printNum)
userInfo:nil
repeats:YES];
}
}
- (void)printNum {
NSLog(@"zhiyun>>>>");
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
正确解法
我们考虑新增一个代理,让timer引用代理proxy,proxy再弱引用 self,这样的话,self 和 timer 之间就不会产生循环引用,最后我们在 self被释放后执行 invalidate 终止 timer。
同时,我们需要利用消息转发机制把 selector的执行转接到 真正的target.
额外话: 如果要求timer的执行不受滚动影响,可以指定加入的runloop的 mode 为 NSRunLoopCommonModes。
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
NSTimer+Util.h
@interface NSTimer (Util)
+ (NSTimer *)yun_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(nullable id)userInfo
repeats:(BOOL)yesOrNo;
@end
NSTimer+Util.m
#import "NSTimer+Util.h"
#import "YUNTimerWeakTargetProxy.h"
@implementation NSTimer (Util)
+ (NSTimer *)yun_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
target:(id)target
selector:(SEL)selector
userInfo:(nullable id)userInfo
repeats:(BOOL)yesOrNo {
YUNTimerWeakTargetProxy *proxy = [[YUNTimerWeakTargetProxy alloc] initWithTarget:target selector:selector];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:ti
target:proxy
selector:selector
userInfo:userInfo
repeats:yesOrNo];
proxy.timer = timer;
return timer;
}
@end
YUNTimerWeakTargetProxy.h
@interface YUNTimerWeakTargetProxy : NSProxy
@property(nonatomic, weak) NSTimer *timer;
- (id)initWithTarget:(id)target selector:(SEL)sel;
@end
YUNTimerWeakTargetProxy.m
#import "YUNTimerWeakTargetProxy.h"
@interface YUNTimerWeakTargetProxy ()
@property(nonatomic, weak) id target;
@property(nonatomic, assign) SEL selector;
@property (nonatomic, strong) NSMethodSignature *sig;
@end
@implementation YUNTimerWeakTargetProxy
- (id)initWithTarget:(id)target selector:(SEL)sel {
if (self) {
self.target = target;
self.selector = sel;
self.sig = [self.target methodSignatureForSelector:self.selector];
}
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == self.selector)
return self.sig;
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
if (invocation.selector == self.selector) {
if (self.target) {
[invocation invokeWithTarget:self.target];
} else {
[self.timer invalidate];
self.timer = nil;
NSLog(@"%@ timer auto stop", NSStringFromSelector(self.selector));
}
} else {
[super forwardInvocation:invocation];
}
}
@end
这样修改过后,业务调用yun_scheduledTimerWithTimeInterval即可,不会出现循环引用
@interface CircleRetainViewController ()
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation CircleRetainViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
if (!self.timer) {
self.timer = [NSTimer yun_scheduledTimerWithTimeInterval:2.f
target:self
selector:@selector(printNum)
userInfo:nil
repeats:YES];
}
}
- (void)printNum {
NSLog(@"zhiyun>>>>");
}