系统框架--52:NSTimer会保留其目标对象

2023-11-22 08:33:54 浏览数 (3)

NSTimer

计时器要和runloop相关联,runloop会触发任务。创建NSTimer时,可以将其“预先安排”在当前的runloop中,也可以创建好,然后再由开发人员自己调度。无论采用什么方式,只要把计时器放在循环里,它才能创建好触发的任务

代码语言:javascript复制
//将其预先安排在当前的runloop中
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:@selector(run) userInfo:nil repeats:YES];

//停止timer
[self.timer invalidate];

注:target和selector参数表示计时器将在哪个对象上调用哪个方法,同时,计算机会保留其目标对象,直到计时器失效才会放弃此对象。

NSTimer的循环引用

创建定时器时,Student引用了定时器,在给定时器添加任务时,定时器保留了目标对象self,这里就出现了循环引用

代码语言:javascript复制
#import <Foundation/Foundation.h>
@interface Student : NSObject
@property (nonatomic,strong) NSTimer *timer;
- (void)start;
- (void)end;
@end
--------------------
#import "Student.h"
@implementation Student
- (void)start{
    _timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(run) userInfo:nil repeats:YES];
}
- (void)end{
    [_timer invalidate];
    _timer = nil;
}
- (void)run{
    NSLog(@"--");
}
- (void)dealloc{
    NSLog(@"%s",__func__);
}
@end
--------------------
#import "ViewController.h"
#import "Student.h"
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    Student *stu = [[Student alloc]init];
    [stu start];
}
@end

解决这个循环引用的方法

  • Student不再引用定时器
  • 定时器不再保留目标对象self

Student不再引用定时器是不可行的,那么就只有第二种方法了。也就是合适的时候调用end方法。然而,合适的时机很难找到。假如这是一个验证码倒计时程序,你可以在倒计时结束时调用end方法。但是你不能确定用户一定会等倒计时结束才返回到上一级页面.或许你想在dealloc方法中使定时器失效,那你就太天真了。此时定时器还保留着当前控制器,此方法是不可能调用的,因此会出现内存泄漏。或许在倒计时程序中,你可以重写返回方法,先调用end再返回,但这不是一个好主意

这里采用block块的方法为NSTimer增加一个分类,具体细节看代码(程序员最好的语言是代码)。

代码语言:javascript复制
#import <Foundation/Foundation.h>
@interface NSTimer (SSBlockSupport)
  (NSTimer *)ss_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval
                                        repeats:(BOOL)repeats
                                          block:(void(^)(NSTimer *timer))block;
@end
----------
#import "NSTimer SSBlockSupport.h"
@implementation NSTimer (SSBlockSupport)
  (NSTimer *)ss_scheduledTimerWithTimeInterval:(NSTimeInterval)inerval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block{
    return [NSTimer scheduledTimerWithTimeInterval:inerval target:self selector:@selector(ss_blcokInvoke:) userInfo:[block copy] repeats:repeats];
}
  (void)ss_blcokInvoke:(NSTimer *)timer {
    void (^block)(NSTimer *timer) = timer.userInfo;
    if (block) {
        block(timer);
    }
}
@end
----------
#import <Foundation/Foundation.h>
@interface Student : NSObject
@property(nonatomic, strong) NSTimer *timer;
- (void)start;
- (void)end;
@end
----------
#import "Student.h"
#import "NSTimer SSBlockSupport.h"
@implementation Student
- (void)start{
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer ss_scheduledTimerWithTimeInterval:0.1 repeats:YES block:^(NSTimer *timer) {
        [weakSelf run];
    }];
}
- (void)end{
    [_timer invalidate];
    _timer = nil;
}
- (void)run{
    NSLog(@"--");
}
- (void)dealloc{
    [_timer invalidate];
    NSLog(@"%s",__func__);
}
@end
----------
#import "ViewController.h"
#import "Student.h"
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
   
    Student *stu = [[Student alloc]init];
    [stu start];
    
}
@end

在iOS10中,新的定时器即使不被引用,也可以正常运行,但是这依然会导致target不能释放,上面的方法依然十分有用。iOS10中,定时器的API新增了block方法,实现原理和这一样,只不过我这里用的是分类,而系统是在原始类中直接添加方法,最终的行为是一致的

代码语言:javascript复制
#import "Student.h"
@implementation Student
- (void)start{
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf run];
    }];
}
- (void)end{
    [_timer invalidate];
    _timer = nil;
}
- (void)run{
    NSLog(@"--");
}
- (void)dealloc{
    [_timer invalidate];
    NSLog(@"%s",__func__);
}
@end

0 人点赞