解決NSTimer循環引用致使內存泄漏的六種方法

demo放在了GitHubgit

參考另外一篇文章可加深印象NSTimer的八種建立方式github

另外一篇文章對第三種方法作了重點解釋讓你們拿過來就能用且沒有循環引用的定時器TFQWeakTimerbash

內存泄漏的緣由:

self強引用timertimer添加在runloop上,只要timer不銷燬self就銷燬不了。固然了你能夠選擇在viewWillDisappear中銷燬timer。可是定時器頁面不必定都是pop到上一個頁面,也有可能push新頁面,也有多是進入後臺,這樣咱們但願從新回到定時器頁面的時候,定時任務還依舊是執行狀態。因此invalidate放到viewWillDisappear是不合理的,惟一合理的地方就是定時器頁面銷燬的時候銷燬timer。這就引出瞭如下三種解決方法。app

第1、二種方法是在合適的時機銷燬timer,幹掉強引用。異步

第三種方法是自定義timer,弱引用timer,從源頭上就不造成循環引用,更不會致使內存泄漏。async

1、離開當前控制器銷燬NSTimer

didMoveToParentViewController方法瞭解一下ide

@interface WLZSecondController ()

@property (nonatomic, strong)UILabel *label;
@property (nonatomic, assign)int repeatTime;
//第1、二種的屬性
//@property (nonatomic, strong)NSTimer *timer;
//第三種方法的屬性
@property (nonatomic, strong)WLZTimer *timer;

@end

@implementation WLZSecondController


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor yellowColor];
    self.repeatTime = 60;
    self.label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 50)];
    self.label.text = @"60";
    self.label.textColor = [UIColor blackColor];
    [self.view addSubview:self.label];
    [self createTimer];
}

#pragma mark - 第一種方法
- (void)createTimer{
    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(change) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if(parent == nil){
        [self.timer invalidate];
    }
}

- (void)change{
    self.repeatTime --;
    self.label.text = [NSString stringWithFormat:@"%d",self.repeatTime];
    if(self.repeatTime == 0){
        [self.timer invalidate];
    }
}

- (void)dealloc{
    NSLog(@"dealloc");
}
複製代碼

2、自定義返回按鈕銷燬NSTimer

這種方法就須要禁止掉側滑返回手勢。oop

#pragma mark - 第二種方法 這裏只是隨意建立了一個button,具體的圖片、文案能夠本身調試。
- (void)createTimer{
    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(change) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}

- (void)viewWillAppear:(BOOL)animated{
    [self changeBackBarButtonItem];
}

- (void)changeBackBarButtonItem{
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(invalidateTimer)];
}

- (void)invalidateTimer{
    [self.timer invalidate];
    [self.navigationController popViewControllerAnimated:YES];
}

- (void)change{
    self.repeatTime --;
    self.label.text = [NSString stringWithFormat:@"%d",self.repeatTime];
    if(self.repeatTime == 0){
        [self.timer invalidate];
    }
}

- (void)dealloc{
    NSLog(@"dealloc");
}
複製代碼

3、自定義timer,壓根不造成循環引用

自定義timer類post

.h文件
@interface WLZTimer : NSObject

@property (nonatomic, weak)id target;
@property (nonatomic, assign)SEL selector;

///建立timer
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval Target:(id)target andSelector:(SEL)selector;
///銷燬timer
- (void)closeTimer;

@end

.m文件
@interface WLZTimer ()

@property (nonatomic, strong)NSTimer *timer;

@end

@implementation WLZTimer

- (instancetype)initWithTimeInterval:(NSTimeInterval)interval Target:(id)target andSelector:(SEL)selector{
    if(self == [super init]){
        self.target = target;
        self.selector = selector;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(dosomething) userInfo:nil repeats:YES];
    }
    return self;
}

- (void)dosomething{
    //這裏是爲了避免阻塞主線程
    dispatch_async(dispatch_get_main_queue(), ^{
        id target = self.target;
        SEL selector = self.selector;
        if([target respondsToSelector:selector]){
            [target performSelector:selector withObject:nil];
        }
    });
}

- (void)closeTimer{
    [self.timer invalidate];
    self.timer = nil;
}

- (void)dealloc{
    NSLog(@"WLZTimer dealloc");
}

@end
複製代碼

自定義timer在主線程異步執行任務不明白緣由的話,可參考文章iOS線程、同步異步、串行並行隊列ui

建立timer的時候就用自定義的類就能夠

#pragma mark - 第三種方法
/*
 *  與第前兩種不一樣的是:前兩種只是在合適的時機解決循環引用,
 *  第三種根本就不會形成循環引用,能夠封裝起來供多個地方使用,並且遵循單一職責原則
 *
 */
- (void)createTimer{
    self.timer = [[WLZTimer alloc] initWithTimeInterval:1 Target:self andSelector:@selector(change)];
}

- (void)change{
    self.repeatTime --;
    self.label.text = [NSString stringWithFormat:@"%d",self.repeatTime];
    if(self.repeatTime == 0){
        [self.timer closeTimer];
    }
}

- (void)dealloc{
    [self.timer closeTimer];
    NSLog(@"dealloc");
}
複製代碼

2019/1/17 更新

4、定義中介繼承NSObject進行消息轉發消除強引用NSTimer

+ (instancetype)proxyWithTarget:(id)aTarget{
    TFQProxy *proxy = [[TFQProxy alloc] init];
    proxy.target = aTarget;
    return proxy;
}

//本身不能處理這個消息,就會調用這個方法來消息轉發,return target,讓target來調用這個方法。
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if([self.target respondsToSelector:aSelector]){
        return self.target;
    }
    return nil;
}
複製代碼
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TFQProxy proxyWithTarget:self] selector:@selector(repeatAction:) userInfo:nil repeats:YES];
複製代碼

5、定義中介繼承NSProxy進行消息轉發消除強引用NSTimer

+ (instancetype)proxyWithTarget:(id)aTarget{
    TFQProxySubclass *proxy = [TFQProxySubclass alloc];
    proxy.target = aTarget;
    return proxy;
}

//爲另外一個類實現的消息建立一個有效的方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    return [self.target methodSignatureForSelector:sel];
}

//將選擇器轉發給一個真正實現了該消息的對象
- (void)forwardInvocation:(NSInvocation *)invocation{
    [invocation invokeWithTarget:self.target];
}
複製代碼
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TFQProxySubclass proxyWithTarget:self] selector:@selector(repeatAction:) userInfo:nil repeats:YES];
複製代碼

tips:五比四效率高,由於5是系統的類,直接進行消息轉發,4會走幾條彎路纔會到消息轉發那個方法

6、GCD建立定時器

/**
 GCD建立定時器

 @param task 定時器內容
 @param interval 執行間隔
 @param repeat 是否重複
 @param async 是否異步
 @param identifier 定時器惟一ID
 @return 返回定時器惟一ID,銷燬的時候用
 */
+ (NSString *)schedleTask:(void (^)(void))task interval:(NSTimeInterval)interval repeat:(BOOL)repeat async:(BOOL)async reuseIdentifier:(NSString *)identifier{
    dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
    //穿件定時器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //開始時間
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 0*NSEC_PER_SEC);
    //設置各類時間
    dispatch_source_set_timer(timer, start, interval*NSEC_PER_SEC, 0);
    //設置回調
    dispatch_source_set_event_handler(timer, ^{
        task();
        if(!repeat){
            [self cancelTimer:identifier];
        }
    });
    //啓動定時器
    dispatch_resume(timer);
    timerDict[identifier] = timer;
    return identifier;
}

+ (void)cancelTimer:(NSString *)identifier{
    dispatch_source_cancel(timerDict[identifier]);
    [timerDict removeObjectForKey:identifier];
}
複製代碼
__weak typeof(self) weakSelf = self;
self.timerIdentifier = [TFQGCDTimer schedleTask:^{
    [weakSelf repeatAction:nil];
} interval:1 repeat:YES async:NO reuseIdentifier:@"identifier"];
複製代碼
相關文章
相關標籤/搜索