iOS - 关于NSTimer的循环引用

ChaMuriel 8年前
   <h2>现象</h2>    <p>在当前控制器(ViewController)的view上添加了一个自定义的view(LXFTimerView),</p>    <p>LXFTimerView在成功创建出来后添加了定时器NSTimer并加入RunLoop开始工作,</p>    <p>当在当前控制器里将LXFTimerView移除掉后,定时器还在工作,而且LXFTimerView里的dealloc并没有调用</p>    <p><img src="https://simg.open-open.com/show/c71207dd9f172d082eb2b800184f5098.gif"></p>    <p>现象</p>    <h2>代码</h2>    <p>LXFTimerView.m</p>    <pre>  <code class="language-objectivec">#import "LXFTimerView.h"    @interface LXFTimerView()  /** 定时器 */  @property(nonatomic, weak) NSTimer *timer;  @end    @implementation LXFTimerView    - (instancetype)initWithFrame:(CGRect)frame {      if (self = [super initWithFrame:frame]) {          [self addTimer];      }      return self;  }    - (void)dealloc {      NSLog(@"LXFTimerView - dealloc");      [self removeTimer];  }    #pragma mark - 定时器方法  /** 添加定时器方法 */  - (void)addTimer {      // 创建定时器      if (self.timer) { return; }      self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(log) userInfo:nil repeats:YES];      [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];  }  /** 移除定时器 */  - (void)removeTimer {      [self.timer invalidate];      self.timer = nil;  }  - (void)log {      NSLog(@"定时器 -- %s", __func__);  }  @end</code></pre>    <p>ViewController.m</p>    <pre>  <code class="language-objectivec">#import "ViewController.h"  #import "LXFTimerView.h"    @interface ViewController ()  /** timerView */  @property(nonatomic, weak) LXFTimerView *timerView;  @end    @implementation ViewController  - (void)viewDidLoad {      [super viewDidLoad];        LXFTimerView *timerView = [[LXFTimerView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 200)];      timerView.backgroundColor = [UIColor orangeColor];      self.timerView = timerView;      [self.view addSubview:timerView];     }  - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {      [self.timerView removeFromSuperview];  }  @end</code></pre>    <h2>引用关系</h2>    <p><img src="https://simg.open-open.com/show/46e60c86e4aeb0722879f18be5b5f342.png"></p>    <p>引用关系</p>    <p>问题就出在LXFTimerView与NSTimer之间,在创建定时器时执行</p>    <pre>  <code class="language-objectivec">[NSTimer scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:];</code></pre>    <p>会将LXFTimerView进行强引用,什么?我怎么知道?看下图</p>    <p><img src="https://simg.open-open.com/show/e414e65084caab79f1f6239b6c338866.png"></p>    <p>NSTimer</p>    <p>翻译:定时器保持着对target的强引用,直到定时器作废</p>    <p>那为什么LXFTimerView中的timer属性要用weak?? 不用着急,下面即将揭晓~</p>    <h2>解决方案</h2>    <p>让定时器指着另一个对象,让那个对象来执行LXFTimerView中需要执行的方法。</p>    <p>引用关系如下图所示</p>    <p><img src="https://simg.open-open.com/show/05eafd3308d5c766998016ba23b02bfe.png"></p>    <p>LXFWeakTarget</p>    <p>创建一个继承于NSObject的类 LXFWeakTarget,并提供一个创建定时器的方法(苹果官方的方法,对scheduledTimerWithTimeInterval进行转到定义操作【就是command+左键】就可以得到)</p>    <p>LXFWeakTarget.h</p>    <pre>  <code class="language-objectivec">#import <Foundation/Foundation.h>  @interface LXFWeakTarget : NSObject  + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;  @end</code></pre>    <pre>  <code class="language-objectivec">#import "LXFWeakTarget.h"    @interface LXFWeakTarget()  @property(nonatomic, weak) id target;  @property(nonatomic, assign) SEL selector;  @end    @implementation LXFWeakTarget  + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {      // 创建当前类的对象      LXFWeakTarget *object = [[LXFWeakTarget alloc] init];      object.target = aTarget;      object.selector = aSelector;        return [NSTimer scheduledTimerWithTimeInterval:ti target:object selector:@selector(execute:) userInfo:userInfo repeats:yesOrNo];  }  - (void)execute:(id)obj {      [self.target performSelector:self.selector withObject:obj];   }  @end</code></pre>    <p>在LXFTimerView.m中导入LXFWeakTarget的头文件</p>    <pre>  <code class="language-objectivec">#import "LXFWeakTarget.h"</code></pre>    <p>将创建定时器的类改为 LXFWeakTarget</p>    <pre>  <code class="language-objectivec">self.timer = [LXFWeakTarget scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(log) userInfo:nil repeats:YES];</code></pre>    <p>现在再来执行一下程序</p>    <p><img src="https://simg.open-open.com/show/06dce838884c04bb28fdd9c85d3b8932.gif"></p>    <p>执行dealloc</p>    <h2>最后缕下思路</h2>    <ul>     <li>我们用一个LXFWeakTarget来替LXFTimerView执行一些操作。</li>     <li>当没有被定时器强引用的LXFTimerView从父控件上被移除时,就会执行dealloc方法,LXFTimerView被销毁。</li>     <li>将定时器作废并设为nil,这样定时器对LXFWeakTarget的引用也没有了,LXFWeakTarget也会被销毁。</li>    </ul>    <p>好,那“为什么LXFTimerView中的timer属性要用weak”这个问题就不用多加解析了吧。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/0c5056b16c6b</p>    <p> </p>