美文网首页OC 底层原理笔记
25-内存管理之定时器

25-内存管理之定时器

作者: zysmoon | 来源:发表于2020-02-11 13:43 被阅读0次
    一 CADisplayLink、NSTimer使用注意
    • CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用

    示例代码如下

    • CADisplayLink
    @property (strong, nonatomic) CADisplayLink *link;
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        // 保证调用频率和屏幕的刷帧频率一致,60FPS
        self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
        [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    }
    
    - (void)linkTest {
        NSLog(@"%s", __func__);
    }
    
    

    执行几秒后点击退出当前控制器

    执行结果如下

    1653926-2b14b150ac9547df.png

    由打印结果可知,虽然已经控制器已经消失了,但是没有调用其dealloc方法,造成内存泄露.

    • NSTimer
    @property (strong, nonatomic) NSTimer *timer;
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    }
    
    - (void)timerTest {
        NSLog(@"%s", __func__);
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [self.timer invalidate];
    }
    
    

    执行几秒后点击退出当前控制器

    执行结果如下

    1653926-ecf4809af703c4af.png

    由运行结果可知,控制器已经消失了,但是仍然没有调用其dealloc方法,导致内存泄露。

    解决方案
    • 使用block
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf timerTest];
    }];
    
    

    运行结果

    1653926-fa507d3d69b333e9.png

    由运行结果可知,控制器退出时,调用了其dealloc方法,不会造成内存泄露。

    • 使用代理对象(NSProxy)
    • CADisplayLink

    Proxy

    // Proxy.h
    @interface Proxy : NSObject
    + (instancetype)proxyWithTarget:(id)target;
    @property (weak, nonatomic) id target;
    @end
    
    // Proxy.m
    @implementation Proxy
    + (instancetype)proxyWithTarget:(id)target {
        Proxy *proxy = [[Proxy alloc] init];
        proxy.target = target;
        return proxy;
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        return self.target;
    }
    @end
    
    

    使用CADisplayLink

    self.link = [CADisplayLink displayLinkWithTarget:[Proxy proxyWithTarget:self] selector:@selector(linkTest)];
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    
    

    运行结果

    1653926-244ab58c1b421c70.png
    • NSTimer
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[Proxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    
    

    运行结果

    1653926-f64b7d101a1aa77b.png
    直接继承NSProxy
    // Proxy1.h文件
    @interface Proxy1 : NSProxy
    + (instancetype)proxyWithTarget:(id)target;
    @property (weak, nonatomic) id target;
    @end
    
    // Proxy1.m文件
    @implementation Proxy1
    + (instancetype)proxyWithTarget:(id)target {
        // NSProxy对象不需要调用init,因为它本来就没有init方法
        Proxy1 *proxy = [Proxy1 alloc];
        proxy.target = target;
        return proxy;
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        return [self.target methodSignatureForSelector:sel];
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation {
        [invocation invokeWithTarget:self.target];
    }
    @end
    
    
    • NSProxy的特点
    Proxy *proxy1 = [Proxy proxyWithTarget:self];
    Proxy1 *proxy2 = [Proxy1 proxyWithTarget:self];
    
    NSLog(@"%d %d",[proxy1 isKindOfClass:[ViewController class]],[proxy2 isKindOfClass:[ViewController class]]);
    
    

    运行结果

    1653926-1370f466197b108b.png

    分析:因为Proxy1是继承自NSProxy,会直接进行消息转发机制,所以执行[proxy2 isKindOfClass:[ViewController class]]),相当于vc执行执行isKindOfClass方法,而isKindOfClass内部也是进行了消息转发,所以返回1。

    二 GCD定时器
    • NSTimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时
    • 而GCD的定时器会更加准时

    分装GCD定时器类实例代码如下

    • CSTimer.h
    @interface CSTimer : NSObject
    
    + (NSString *)execTask:(void(^)(void))task
                     start:(NSTimeInterval)start
                  interval:(NSTimeInterval)interval
                   repeats:(BOOL)repeats
                     async:(BOOL)async;
    
    + (NSString *)execTask:(id)target
                  selector:(SEL)selector
                     start:(NSTimeInterval)start
                  interval:(NSTimeInterval)interval
                   repeats:(BOOL)repeats
                     async:(BOOL)async;
    
    + (void)cancelTask:(NSString *)name;
    
    @end
    
    
    • CSTimer.m
    @implementation CSTimer
    
    // 保存定时器的字典
    static NSMutableDictionary *timers_;
    // 信号量
    dispatch_semaphore_t semaphore_;
    
    // 初始化操作
    + (void)initialize {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            timers_ = [NSMutableDictionary dictionary];
            semaphore_ = dispatch_semaphore_create(1);
        });
    }
    
    + (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async {
        // 如果认为不存在 开始时间小于0 重复并且时间小于0 则返回空
        if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
    
        // 队列 - 是主队列还是并发队列
        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_source_set_timer(timer,
                                  dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
                                  interval * NSEC_PER_SEC, 0);
    
        // 保证线程安全
        dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
        // 定时器的唯一标识
        NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
        // 存放到字典中
        timers_[name] = timer;
        dispatch_semaphore_signal(semaphore_);
    
        // 设置回调
        dispatch_source_set_event_handler(timer, ^{
            task();
    
            if (!repeats) { // 不重复的任务
                [self cancelTask:name];
            }
        });
    
        // 启动定时器
        dispatch_resume(timer);
    
        return name;
    }
    
    + (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async {
        if (!target || !selector) return nil;
    
        return [self execTask:^{
            if ([target respondsToSelector:selector]) {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                [target performSelector:selector];
    #pragma clang diagnostic pop
            }
        } start:start interval:interval repeats:repeats async:async];
    }
    
    // 取消定时器操作
    + (void)cancelTask:(NSString *)name {
        if (name.length == 0) return;
    
        // 线程安全
        dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
    
        dispatch_source_t timer = timers_[name];
        if (timer) {
            dispatch_source_cancel(timer);
            [timers_ removeObjectForKey:name];
        }
    
        dispatch_semaphore_signal(semaphore_);
    }
    
    

    外部调用

    // 开始定时器操作
    - (void)startTimer {
        // 1.使用block回调
        self.task = [CSTimer execTask:^{
            NSLog(@"111111 - %@", [NSThread currentThread]);
        } start:2.0 interval:1.0 repeats:YES async:YES];
    
        // 2.使用selector
    //    self.task = [CSTimer execTask:self selector:@selector(doTask) start:2.0 interval:1.0 repeats:YES async:YES];
    }
    
    // 定时执行任务
    - (void)doTask {
        NSLog(@"doTask - %@", [NSThread currentThread]);
    }
    
    // 停止定时器
    [CSTimer cancelTask:self.task];
    
    

    本文参考:
    路飞_Luck (https://www.jianshu.com/p/07f7b96bb03f)
    以及借鉴MJ的教程视频
    非常感谢.


    项目连接地址 - MemoryManage-CADisplayLink+Timer

    相关文章

      网友评论

        本文标题:25-内存管理之定时器

        本文链接:https://www.haomeiwen.com/subject/lapefqtx.html