iOS - 關于NSTimer的循環引用

ChaMuriel 7年前發布 | 10K 次閱讀 NSTimer iOS開發 移動開發

現象

在當前控制器(ViewController)的view上添加了一個自定義的view(LXFTimerView),

LXFTimerView在成功創建出來后添加了定時器NSTimer并加入RunLoop開始工作,

當在當前控制器里將LXFTimerView移除掉后,定時器還在工作,而且LXFTimerView里的dealloc并沒有調用

現象

代碼

LXFTimerView.m

#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>

    ViewController.m

    #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>

    引用關系

    引用關系

    問題就出在LXFTimerView與NSTimer之間,在創建定時器時執行

    [NSTimer scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:];

    會將LXFTimerView進行強引用,什么?我怎么知道?看下圖

    NSTimer

    翻譯:定時器保持著對target的強引用,直到定時器作廢

    那為什么LXFTimerView中的timer屬性要用weak?? 不用著急,下面即將揭曉~

    解決方案

    讓定時器指著另一個對象,讓那個對象來執行LXFTimerView中需要執行的方法。

    引用關系如下圖所示

    LXFWeakTarget

    創建一個繼承于NSObject的類 LXFWeakTarget,并提供一個創建定時器的方法(蘋果官方的方法,對scheduledTimerWithTimeInterval進行轉到定義操作【就是command+左鍵】就可以得到)

    LXFWeakTarget.h

    #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>
    #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>

    在LXFTimerView.m中導入LXFWeakTarget的頭文件

    #import "LXFWeakTarget.h"

    將創建定時器的類改為 LXFWeakTarget

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

    現在再來執行一下程序

    執行dealloc

    最后縷下思路

    • 我們用一個LXFWeakTarget來替LXFTimerView執行一些操作。
    • 當沒有被定時器強引用的LXFTimerView從父控件上被移除時,就會執行dealloc方法,LXFTimerView被銷毀。
    • 將定時器作廢并設為nil,這樣定時器對LXFWeakTarget的引用也沒有了,LXFWeakTarget也會被銷毀。

    好,那“為什么LXFTimerView中的timer屬性要用weak”這個問題就不用多加解析了吧。

     

    來自:http://www.jianshu.com/p/0c5056b16c6b

     

 本文由用戶 ChaMuriel 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!