iOS 面试汇总

NSTimer用法与循环引用

2017-09-23  本文已影响65人  不吃鸡爪

首先介绍NSTimer的几种创建方式

- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

常用方法

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

三种方法的区别是:

- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;

与UIScrollView使用时注意事项

在当前线程为主线程时,某些UI事件,比如UIScrollView的拖动操作,会将RunLoop切换为NSEventTrackingRunLoopModel模式,在这个过程中,默认的NSDefaultRunLoopModel模式中注册的事件是不会被执行的。
这时可以将Timer按照NSRunLoopCommonModes模式加入到RunLoop中。
通常情况下NSDefaultRunLoopMode和UITrackingRunLoopMode都已经被加入到了common modes集合中, 所以不论runloop运行在哪种mode下, NSTimer都会被及时触发

如何销毁NSTimer

invalidate方法的官方介绍:

Stops the timer from ever firing again and requests its removal from its run loop.
This method is the only way to remove a timer from an NSRunLoopobject. The NSRunLoop
object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.
If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.

意思是:

那为什么RunLoop会对NSTimer强引用呢?

Timers work in conjunction with run loops. Run loops maintain strong references to their timers
( 计时器与运行循环一起工作。运行循环维护对计时器的强引用)

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.
(当计时器触发后,在调用invalidated之前会一直保持对target的强引用)

以上也解释了下面要说的NSTimer造成循环引用的原因

循环引用造成内存泄漏

循环引用示例.png

由上可见:NSTimer强引用了self,self也强引用了NSTimer,由此造成了循环引用,同时Runloop也强引用NSTimer。

  1. 一般情况下直接在vc的viewWillDisappear中调用以下方法即可解决
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.timer invalidate];
    self.timer = nil;
}
  1. 在A--push--B,B返回A

这种情况显然是在dealloc中调用invalidate方法,
有些人可能会想将NSTimer弱引用

@property (nonatomic, weak)NSTimer *timer;

下面介绍几种成熟的解决方案

一. 使用自定义Category用Block解决
NSTimer+ZHWeakTimer.h

@interface NSTimer (ZHWeakTimer)
+ (NSTimer *)zh_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval block:(void (^)(void))eventBlock repeats:(BOOL)repeats;
@end
NSTimer+ZHWeakTimer.m

@implementation NSTimer (ZHWeakTimer)

+ (NSTimer *)zh_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval block:(void (^)(void))eventBlock repeats:(BOOL)repeats
{
    NSTimer *timer = [self scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(zh_executeTimer:) userInfo:[eventBlock copy] repeats:repeats];
    
    return timer;
}

+ (void)zh_executeTimer:(NSTimer *)timer
{
    void (^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}

@end

定时器对象指定的target是NSTimer类对象是个单例,因此计时器是否会保留它都无所谓。这么做,循环引用依然存在,但是因为类对象无需回收,所以能解决问题。

优点:代码简洁,逻辑清晰
缺点:
1.需要使用weakSelf避免block循环引用
2.不再使用原生API
3.同时要为NSTimer何CADisplayLink分别引进一个Category

二. GCD自己实现Timer

直接用GCD自己实现一个定时器,YYKit直接有一个现成的类YYTimer这里不再赘述。
缺点:代价有点大,需要自己重新造一个定时器。

三. 代理NSProxy

使用工具类YYWeakProxy解决NSTimer/CADisplayLink循环引用问题!

YYWeakProxy.h
@interface YYWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
-(instancetype)initWithTarget:(id)target;
+(instancetype)proxyWithTarget:(id)target;
@end
YYWeakProxy.m
-(instancetype)initWithTarget:(id)target {
 _target = target;
 return self;
}
+(instancetype)proxyWithTarget:(id)target {
 return [[YYWeakProxy alloc] initWithTarget:target];
}
-(id)forwardingTargetForSelector:(SEL)selector {
 return _target;
}
-(void)forwardInvocation:(NSInvocation *)invocation {
 void *null = NULL;
 [invocation setReturnValue:&null];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
 return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
-(BOOL)respondsToSelector:(SEL)aSelector {
 return [_target respondsToSelector:aSelector];
}
-(BOOL)isEqual:(id)object {
 return [_target isEqual:object];
}
-(NSUInteger)hash {
 return [_target hash];
}
-(Class)superclass {
 return [_target superclass];
}
-(Class)class {
 return [_target class];
}
-(BOOL)isKindOfClass:(Class)aClass {
 return [_target isKindOfClass:aClass];
}
-(BOOL)isMemberOfClass:(Class)aClass {
 return [_target isMemberOfClass:aClass];
}
-(BOOL)conformsToProtocol:(Protocol *)aProtocol {
 return [_target conformsToProtocol:aProtocol];
}
-(BOOL)isProxy {
 return YES;
}
-(NSString *)description {
 return [_target description];
}
-(NSString *)debugDescription {
 return [_target debugDescription];
}
@end

该方法引入一个YYWeakProxy对象,在这个对象中弱引用真正的目标对象。通过YYWeakProxy对象,将NSTimer/CADisplayLink对象弱引用目标对象。
使用方法:

 self.timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                  target:[YYWeakProxy proxyWithTarget:self]
                                                selector:@selector(timeEvent)
                                                userInfo:nil
                                                 repeats:YES];
- (void)timeEvent{
}
- (void)dealloc
{
    [self.timer invalidate];
    self.timer = nil;// 对象置nil是一种规范和习惯
}
为什么NSProxy的子类YYWeakProxy可以解决呢?

参考文档

1.iOS实录8:解决NSTimer/CADisplayLink的循环引用
2.NSTimer Class

上一篇 下一篇

猜你喜欢

热点阅读