美文网首页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