美文网首页iOS 开发 Objective-C
iOS 底层 day23 多线程 NSTimer CADispl

iOS 底层 day23 多线程 NSTimer CADispl

作者: 望穿秋水小作坊 | 来源:发表于2020-09-22 14:25 被阅读0次

    一、NSTimer

    1.基本特点
    • NSTimer 需要加入 RunLoop 中,精度最低,用于执行一般定时任务。
    • 比如 NSTimer 的触发时间到的时候,如果 RunLoop 在阻塞状态,触发时间就会推迟到下一个 RunLoop 周期。
    • 最好加入到 RunLoop 的 common 中,避免被 UI 页面拖拽所暂停
    2. 用法一 block
    #import "ViewController.h"
    @interface ViewController ()
    @property(nonatomic, strong) NSTimer *timer;
    @property(nonatomic, assign) int age;
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.age = 10;
        __weak typeof(self) weakSelf = self;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"age is age %d", weakSelf.age);
        }];
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [self.timer invalidate];
        self.timer = nil;
    }
    @end
    
    • 注意点:需要用 weakSelf 避免 timerself 循环引用,容易导致dealloc 方法不被调用,从而内存泄露。
    3. 用法二 target,思考如下代码有问题吗?
    #import "ViewController.h"
    @interface ViewController ()
    @property(nonatomic, strong) NSTimer *timer;
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
    }
    - (void)task {
        NSLog(@"do tesk");
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [self.timer invalidate];
        self.timer = nil;
    }
    @end
    
    • 会,dealloc 方法并不会调用,会导致循环引用。
    • self 强引用了 timertimer 强引用了 self
    • 如何解决这个问题呢?请思考,并往下阅读。
    4. 用法二 target,可能有人会这么想,如下代码能解决循环引用吗?
    #import "ViewController.h"
    @interface ViewController ()
    @property(nonatomic, strong) NSTimer *timer;
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        __weak typeof(self) weakSelf = self;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(task) userInfo:nil repeats:YES];
    }
    - (void)task {
        NSLog(@"do tesk");
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [self.timer invalidate];
        self.timer = nil;
    }
    @end
    
    • 并不能解决循环引用
    • 这么想有一个严重的概念错套的问题
    • weakSelf 本质还是执行 self 的指针,通常用于 block 内部解决循环引用,因为 block 内部weakSelfself捕获做了区别对待。
    • 上面的代码使用 weakSelf 和 使用 self 没有任何区别,都会产生强引用
    5. 用法二 target,viewWillDisappear手动断环思路
    #import "ViewController.h"
    @interface ViewController ()
    @property(nonatomic, strong) NSTimer *timer;
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
    }
    - (void)task {
        NSLog(@"do tesk");
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
        NSLog(@"%s", __func__);
        [self.timer invalidate];
        self.timer = nil;
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    @end
    
    • 既然循环引用会导致 dealloc 无法调用,那么我们在 viewWillDisappear进行手动断环即可。
    6. 用法二 target,代理断环(代理继承自 NSObject)
    代理断环原理图
    • 我们无法避免 Timer 强引用 target,所以我们只能不让 self 当 NSTimer 的 target,我们找个代理,这样就能破除循环引用了。
    #import "YYObjectProxy.h"
    @interface YYObjectProxy ()
    @property(nonatomic, weak)id target;
    @end
    @implementation YYObjectProxy
    + (instancetype)proxyWithTarget:(id)target {
        YYObjectProxy *proxy =  [[YYObjectProxy alloc] init];
        proxy.target = target;
        return proxy;
    }
      // 消息转发
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        return self.target;
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    @end
    
    • 调用代码(其他代码不变)
    #import "ViewController.h"
    #import "YYObjectProxy.h"
    @interface ViewController ()
    @property(nonatomic, strong) NSTimer *timer;
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        YYObjectProxy *proxy = [YYObjectProxy proxyWithTarget:self];
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:proxy selector:@selector(task) userInfo:nil repeats:YES];
    }
    - (void)task {
        NSLog(@"do tesk");
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [self.timer invalidate];
        self.timer = nil;
    }
    @end
    
    7. 用法二 target,代理断环(代理继承自 NSProxy,也是暂时比较完美的解决方案)

    我们使用继承自 NSObject 的代码固然能解决循环引用问题,但是上面代码还有更好的继承方案,那就是继承自 NSProxy。

    • NSProxy 非常特别,它本身也是一个基类,不继承自 NSObject
    • 对于 NSObject 的子类而言,会从当前类superclass 去找匹配的 selector ;如果找不到,会进入消息动态解析阶段;如果找不到,会进入消息转发阶段
    • 对于 NSProxy 的子类而言,会从当前类 去找匹配的 selector ;如果找不到,会直接进入 消息转发阶段
    • NSProxy消息机制会比 NSObject 简单很多,相对来讲效率也高很多
    // YYProxy.m
    #import "YYProxy.h"
    
    @interface YYProxy ()
    @property(nonatomic, weak) id target;
    @end
    
    @implementation YYProxy
    +(instancetype)proxyWithTarget:(id)target {
        // NSProxy对象不需要调用init,因为它本来就没有init方法
        YYProxy *proxy = [YYProxy alloc];
        proxy.target = target;
        return proxy;
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation {
        [invocation invokeWithTarget:self.target];
    }
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        return  [self.target methodSignatureForSelector:sel];
    }
    
    -(void)dealloc {
        NSLog(@"%s", __func__);
    }
    @end
    
    #import "ViewController.h"
    #import "YYProxy.h"
    @interface ViewController ()
    @property(nonatomic, strong) NSTimer *timer;
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        YYProxy *proxy = [YYProxy proxyWithTarget:self];
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:proxy selector:@selector(task) userInfo:nil repeats:YES];
    }
    - (void)task {
        NSLog(@"do tesk");
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [self.timer invalidate];
        self.timer = nil;
    }
    @end
    
    8. 结合上面实例7和实例6的代码,下面代码输出什么?
    #import "ViewController.h"
    #import "YYProxy.h"
    #import "YYObjectProxy.h"
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        YYProxy *proxy1 = [YYProxy proxyWithTarget:self];
        YYObjectProxy *proxy2 = [YYObjectProxy proxyWithTarget:self];
        
        NSLog(@"%d %d",
              [proxy1 isKindOfClass:[ViewController class]],
              [proxy2 isKindOfClass:[ViewController class]]
              );
    }
    
    • 输出 1 0
    • 因为NSProxy除开自定义的方法外,其他方法基本上都会转换到消息转发机制进行调用,所以proxy1本质上是在调用 ViewControllerclass 方法。

    二、CDDisplayLink

    1.基本特点
    • CDDisplayLink 需要加入 RunLoop 中,精度中等,跟随屏幕刷新频率调用(大部分iOS设备是60FPS),主要用于和屏幕刷新有关的动画绘制
    2.基本用法
    • 创建代理
    #import "YYProxy.h"
    @interface YYProxy ()
    @property(nonatomic, weak) id target;
    @end
    
    @implementation YYProxy
    +(instancetype)proxyWithTarget:(id)target {
        // NSProxy对象不需要调用init,因为它本来就没有init方法
        YYProxy *proxy = [YYProxy alloc];
        proxy.target = target;
        return proxy;
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation {
        [invocation invokeWithTarget:self.target];
    }
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        return  [self.target methodSignatureForSelector:sel];
    }
    
    -(void)dealloc {
        NSLog(@"%s", __func__);
    }
    @end
    
    • 调用 CADisplayLink
    #import "ViewController.h"
    #import "YYProxy.h"
    #import "YYObjectProxy.h"
    @interface ViewController ()
    @property(nonatomic, strong) CADisplayLink *displayLink;
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        YYProxy *proxy = [YYProxy proxyWithTarget:self];
        self.displayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(task)];
        self.displayLink.preferredFramesPerSecond = 1;
        [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
    - (void)task {
        NSLog(@"do tesk");
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
    @end
    
    • 调用 CADisplayLink用法注意点基本和NSTimer使用一致

    三、GCD定时器

    1. 基本特点
    • 是iOS所有定时器中最精准的定时器
    • GCD定时器是和系统内核挂钩的,和 RunLoop 没有关系
    • GCD定时器还可以自动创建线程,只要我们传入一个自创的队列就行了
    • GCD 创建的东西,在 ARC 的环境下,都不需要我们销毁
    2. 主要方法
    主要方法介绍
    3. 代码演示(已封装成 CGD 定时器工具类)
    • 要注意封装的思想,先从外部调用来看需要提供哪些方法,然后再去实现工具类内部代码
    • 封装的时候,把要注意的点写成注释,一条条去实现,才不容易遗漏。
    #import "YYTimer.h"
    
    @implementation YYTimer
    
    NSMutableDictionary *timerDictionary_;
    dispatch_semaphore_t timerSemaphore_;
    
    +(void)initialize{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            timerDictionary_ = [NSMutableDictionary dictionary];
            timerSemaphore_ = dispatch_semaphore_create(1);
        });
    }
    + (NSString*)executeTask:(taskBlock)task start:(uint64_t)startInSeconds interval:(uint64_t)intervalInSeconds repeat:(BOOL)repeat async:(BOOL)async {
        // 创建timer
        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, startInSeconds * NSEC_PER_SEC), intervalInSeconds * NSEC_PER_SEC, 0);
        
        dispatch_semaphore_wait(timerSemaphore_, DISPATCH_TIME_FOREVER);
        // 创建唯一标识
        static int counter = 0;
        NSString *timerName = [NSString stringWithFormat:@"%d",counter++];
        // 放入全局字典中
        [timerDictionary_ setObject:timer forKey:timerName];
        dispatch_semaphore_signal(timerSemaphore_);
        dispatch_source_set_event_handler(timer, ^{
            task();
            if (!repeat) {
                [YYTimer cancelTimer:timerName];
            }
        });
        dispatch_resume(timer);
        return timerName;
    }
    
    + (void)cancelTimer:(NSString *)timerName {
        if (timerName.length == 0) return;
        dispatch_semaphore_wait(timerSemaphore_, DISPATCH_TIME_FOREVER);
        dispatch_source_t timer = [timerDictionary_ objectForKey:timerName];
        if (timer) {
            [timerDictionary_ removeObjectForKey:timerName];
    
        }
        dispatch_semaphore_signal(timerSemaphore_);
    }
    @end
    
    
    #import <Foundation/Foundation.h>
    
    typedef void(^taskBlock)(void);
    
    @interface YYTimer : NSObject
    
    + (NSString*)executeTask:(taskBlock)task
                       start:(uint64_t)startInSeconds
                    interval:(uint64_t)intervalInSeconds
                      repeat:(BOOL)repeat
                       async:(BOOL)async;
    
    + (void)cancelTimer:(NSString *)timerName;
    @end
    
    • 调用代码
    #import "ViewController.h"
    #import "YYTimer.h"
    @interface ViewController ()
    @property(nonatomic, strong) NSString *timerName;
    @end
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.timerName = [YYTimer executeTask:^{
            NSLog(@"112%@",[NSThread currentThread]);
        } start:2.0 interval:1.0 repeat:YES async:YES];
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
        [YYTimer cancelTimer:self.timerName];
    }
    @end
    

    相关文章

      网友评论

        本文标题:iOS 底层 day23 多线程 NSTimer CADispl

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