前言
NSTimer的官方文档对于target的解释,The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.意思就是会对target强引用,直到timer被invalidated掉。
场景:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.f target:self selector:@selector(update:) userInfo:nil repeats:YES];控制器强引用timer,timer强引用控制器,造成循环引用,导致控制器无法释放造成内存泄露。
weakSelf:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.f target:weakSelf selector:@selector(update:) userInfo:nil repeats:YES];
这种简单的方式,并不能打破timer和控制器之间的循环引用,也就是说weakSelf和strongSelf在这里的唯一区别:就是self被释放了,target会变成nil。
NSProxy构建中间target:
DYLWeakProxy对target进行弱引用,然后通过消息转发,最后的执行者转发至targetObject,因为target是弱引用,因此不会造成循环引用。
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.f target:[DYLWeakProxy weakProxyForObject:self] selector:@selector(update:) userInfo:nil repeats:YES];
#import <Foundation/Foundation.h>
@interface DYLWeakProxy : NSProxy
+ (instancetype)weakProxyForObject:(id)targetObject;
@end
#import "DYLWeakProxy.h"
@interface DYLWeakProxy ()
@property (weak, nonatomic) id targetObject;
@end
@implementation DYLWeakProxy
+ (instancetype)weakProxyForObject:(id)targetObject
{
DYLWeakProxy *weakObject = [DYLWeakProxy alloc];
weakObject.targetObject = targetObject;
return weakObject;
}
#pragma mark - Forwarding Messages
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.targetObject;
}
#pragma mark - NSWeakProxy Method Overrides
#pragma mark - Handling Unimplemented Methods
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
void *nullPointer = NULL;
[invocation setReturnValue:&nullPointer];
}
@end
CADisplayLink:
同样的,CADisplayLink也会因为强引用target,从而造成循环引用,解决这个问题与NSTimer类似,依然可以DYLWeakProxy解决;其实加载gif的第三方FLAnimatedImage也使用该方法解决循环引用的问题。
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:[DYLWeakProxy weakProxyForObject:self] selector:@selector(update:)];
最后
除了NSTimer定时器和CADisplayLink外,GCD也可以创建定时器(我封装的DYLDispatchTimerButton倒计时用的就是GCD定时器),而且不存在循环应用的问题以及RunLoop的问题,NSTimer默认情况下运行在NSDefaultRunLoopMode模式下,因此为了防止滚动视图在滚动过程中NSTimer也能正常运行,会通过设置NSRunLoopCommonModes标记,自动的将NSTimer加入到带有CommonMode标记的模式下,即NSDefaultRunLoopMode和NSEventTrackingRunLoopMode。