GCD中常用的函数

  我们在上一篇笔记中讲异步函数和同步函数,以及各种队列时,学习过不少GCD函数,今天再补充两个GCD中常用的函数:dispatch_after( )函数dispatch_once( )函数。其中,dispatch_after( )函数常用于延时执行任务,而dispatch_once( )是一次性执行代码的函数,常用于单例设计模式中。

一、GCD中延时执行任务的函数

  
  首先来看GCD中用于延时执行任务的函数。不过,在此之前,先来回忆一下OC中常用的两个延时执行任务的方法,以便和GCD中的延时执行任务函数做一个对比。

  1、OC中常用的延时执行任务的方法

  先来看第一个方法- performSelector: withObject: afterDelay:,它是声明在NSObject的分类当中的,可以进入其头文件看一下:


Delayed perform.png

  这就意味着,只要是任意一个继承自NSObject的类,都可以调用它。因此,它的使用场合比较灵活:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    // 输出字符串
    NSLog(@"%s", __func__);

    // 演示执行
    [self delay];
}

// MARK:- 延时执行任务
- (void)delay {

    // 延时执行任务方式
    [self performSelector:@selector(printString:) withObject:@"我是参数" afterDelay:2.0];
    /**
     *  performSelector:<#(nonnull SEL)#> withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#>
     *  第一个参数 : 需要传入一个方法,在这个方法中执行任务;
     *  第二个参数 : 方法选择器中那个方法的参数,可以为空;
     *  第三个参数 : NSTimeInterval其实就是double,它表示需要延时多长时间再执行前面方法选择器中的那个方法。
     */
}

// MARK:- 输出一段字符串
- (void)printString:(NSString *)str {

    NSLog(@"%@---%@", str, [NSThread currentThread]);
}

  这个方法经常使用,也非常简单。运行程序,然后点击屏幕,注意一下控制台打印信息的时间戳,看看是不是隔了两秒以后才执行打印任务的:


延时两秒以后执行任务.gif

  从上面的GIF图上看出,它的确可以办到,在我们设定两秒的延时以后才执行任务的。同时,要留意一下,它这个任务是在主线程中执行的。

  除了上面这个- performSelector: withObject: afterDelay:方法以外,NSTimer中还有一个类方法可以控制延时执行任务:+ scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:。它的参数稍微多一点,但是也相对较容易:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    // 输出字符串
    NSLog(@"%s", __func__);

    // 延时执行任务
    [self delay];
}

// MARK:- 延时执行任务
- (void)delay {

    // 延时执行任务方式
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(printString:) userInfo:nil repeats:NO];
    /**
     *  scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> target:<#(nonnull id)#> selector:<#(nonnull SEL)#> userInfo:<#(nullable id)#> repeats:<#(BOOL)#>
     *  第一个参数 : 表示延时多少秒执行;
     *  第二个参数 : 表示目标对象,通常情况下就是self;
     *  第三个参数 : 方法选择器,用于封装任务;
     *  第四个参数 : 和NSTimer相关的信息,可以传空;
     *  第五个参数 : 传YES表示持续重复调用方法选择器中的方法,传NO则只调用一次。
     */
}

// MARK:- 输出一段字符串
- (void)printString:(NSString *)str {

    NSLog(@"%@---%@", str, [NSThread currentThread]);
}

  关于上面这个方法,前三个参数都好理解,后面两个参数需要单独说一下。userInfo里面一般保存的是NSTimer相关的信息,通常可以传空;repeats后面需要传一个BOOL值,如果出入YES,就表示每间隔多少秒以后重复执行某个任务,而传NO则表示只需执行一次。运行程序看一下效果:


还是延时两秒执行任务.gif

  从控制台打印信息的时间戳来看,它也能办到两秒之后执行我们设定的任务。而且,这个任务也是在主线程当中去执行的。

  2、GCD中延时执行任务的函数

  看完了上面这两个OC中常用的延时执行任务的方法,在来看一下GCD中常用的延时执行任务的函数。一般情况下,敲一个dispatch_after,然后选择前面有代码块标识符的那一个函数就可以了,它的很多参数系统已经帮你填写好了:


GCD中延时执行任务的函数.png

  选择这个代码块以后,你会发现,我们只需要填写需要延时的时长,以及需要延时执行的代码块就可以了:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    // 输出字符串
    NSLog(@"%s", __func__);

    // 延时执行任务
    [self delay];
}

// MARK:- 延时执行任务
- (void)delay {

    // GCD中延时执行任务的方法
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        // 延时两秒以后要执行的任务
        NSLog(@"%@", [NSThread currentThread]);
    });
    /**
     *  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
     <#code to be executed after a specified delay#>
     });
     *  在这个GCD代码块中,参数delayInSeconds比较直白,就是表示延时多少秒;
     *  后面的block代码块是用来封装任务的。
     */
}

  运行程序,然后点击屏幕,看一下控制台打印信息的时间戳,它是不是在我们设定两秒的延时之后执行任务的:


使用GCD中的延时函数来执行任务.gif

  关于上面这个dispatch_after( )函数,它有两个参数。其中,第一个参数是一个dispatch_time( )函数,第二个参数是我们非常熟悉的获取主队列的函数。并且,在dispatch_time( )这个函数中,它的第一个参数系统已经帮我们填写好了,DISPATCH_TIME_NOW表示从现在开始计算延时时间;它的第二个参数,前面一部分需要我们自己填,后面一部分NSEC_PER_SEC是一个常数,表示10的9次方纳秒,其作用就是将纳秒转换为秒(GCD的时间是精确到纳秒的)。

  如果仅仅只是这样,那么就看不出GCD的这个延时执行任务的函数跟前面那两个方法有什么区别了。其实,GCD的这个延时执行任务的函数非常强大,它可以自由控制延时任务是在主线程中执行还是子线程中执行。比如说,下面,我们就把dispatch_after( )函数的第二个参数由主队列修改为全局并发队列,让延时任务在子线程中去执行:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    // 输出字符串
    NSLog(@"%s", __func__);

    // 延时执行任务
    [self delay];
}

// MARK:- 延时执行任务
- (void)delay {

    // GCD中延时执行任务的方法
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // 延时两秒以后要执行的任务
        NSLog(@"%@", [NSThread currentThread]);
    });
}

  运行程序,然后点击屏幕,注意看一下控制台打印信息,看看这个延时任务是不是真的是在子线程中去执行的:


让延时任务在子线程中去执行.gif

二、GCD中一次性执行代码的函数

  
  GCD中除了上面那个延时执行任务的函数之外,还有一个一次性执行代码的函数也很好用,尤其是在单例设计模式中用得最多。下面就简单的介绍一下这个函数。

  1、GCD中的一次性代码函数

  在使用GCD的一次性执行代码函数时,和上面一样,只需敲一个dispatch_once,然后选择前面有代码块标识符的那一行就可以了:


GCD中的一次性代码函数.png

  选择完毕以后,代码块的其它参数,系统已经帮你填写好了,你只需要将只需要执行一次的代码块封装进去就可以了:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    // 输出字符串
    NSLog(@"%s", __func__);

    // 一次性代码
    [self singleUseCode];
}

// MARK:- 一次性代码:整个应用程序运行过程中只会执行一次
- (void)singleUseCode {

    // GCD中一次性执行的代码块
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        // 输出一段消息
        NSLog(@"%s, Line = %d", __FUNCTION__, __LINE__);

    });
    /**
     *static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
     <#code to be executed once#>
     });
     *  onceToken是使用static修饰的,是一个全局变量,它在整个应用运行过程中只有一份内存,直到整个应用
     *  都被释放时,onceToken才会被释放。因此,它可以保证这段代码在整个应用程序运行过程中只会被执行一次;
     *  特别注意:一次性代码是不能放在懒加载中去执行的,否则,运行会出问题;
     *  一次性代码通常用于单例设计模式中。
     */
}

  运行程序,然后多次点击模拟器屏幕,看看我们封装进去的代码是不是只执行一次:


GCD中一次性执行代码的函数.gif

  从上面的GIF图上看,尽管我们多次点击模拟器屏幕,但是,整个过程中,- singleUseCode方法只被执行了一次。GCD中的一次性执行代码函数,可以保证整个应用在运行期间,它里面的代码只会被执行一次。

  2、GCD中一次性代码函数与懒加载的区别

  了解完GCD中一次性执行代码的函数,我们可能会想到一iOS开发中的懒加载技术。那么,它和懒加载有什么却别呢?下面我们就演示一下它们之间到底有什么区别。

  新建一个继承自NSObject的ESPerson类,在它里面声明一个dogs属性,然后再通过懒加载对这个属性进行懒加载:

@interface ESPerson : NSObject

@property (strong, nonatomic) NSArray *dogs;

@end

@implementation ESPerson

- (NSArray *)dogs {

    if (!_dogs) {

        _dogs = @[@"Wangcai"];
    }

    return _dogs;
}

@end

  回到ViewController中,包含ESPerson的头文件,然后在- viewDidLoad方法中创建两个ESPerson对象,打印这两个对象的dogs属性的值:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建ESPerson对象
    ESPerson *p1 = [[ESPerson alloc] init];
    ESPerson *p2 = [[ESPerson alloc] init];

    NSLog(@"p1 = %@, p2 = %@", p1.dogs, p2.dogs);
}

  运行程序,注意观察控制台打印出来的消息:


懒加载.gif

  如果将GCD中一次性代码执行的函数放在懒加载中,结果会怎样呢?修改dogs属性的懒加载代码:

@implementation ESPerson

- (NSArray *)dogs {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        _dogs = @[@"Wangcai"];
    });

    return _dogs;
}

@end

  再次运行程序,然后看一下控制台打印出来的消息:


将GCD中一次性执行代码的函数放在懒加载中.gif

  你会发现,p2的值竟然是空的!为什么会出现这种情况呢?首先,我们说过,GCD中一次性执行代码的函数,它在整个应用运行期间只会执行一次。当我们打印p1的值时,会调用dogs属性的getter方法,执行dispatch_once ( )函数给_dogs赋值;当我们打印p2的值时,会再次调用dogs属性的getter方法执行dispatch_once ( )函数给_dogs赋值,但是,dispatch_once ( )函数此时已经不可能再执行了,因此,也就不可能通过它再给_dogs赋值了,所以,最后的结果一定为空。

  通过上面的实例可以说明,一次性代码函数和懒加载还是有区别的。一次性代码是在整个应用程序运行过程中只会被执行一次;而懒加载是在发现没有值的时候再执行,有值的时候不执行。这也说明,一次性代码函数是不能放在懒加载中的。

  dispatch_after( )函数和dispatch_once( )函数的介绍到这里暂时先告一段落,后面会接着介绍GCD的其它知识。详细代码参见《GCDFunctions》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值