文档章节

GCD技术

就不穿小内
 就不穿小内
发布于 2015/12/25 22:33
字数 3847
阅读 38
收藏 0

GCD

GCD 核心概念

  1. 任务添加到队列,并且指定执行任务的函数

  2. 任务使用 block 封装

    • 任务的 block 没有参数也没有返回值

  3. 执行任务的函数

    • 必须等待当前语句执行完毕,才会执行下一条语句

    • 不会开启线程

    • 在当前执行 block 的任务

    • 不用等待当前语句执行完毕,就可以执行下一条语句

    • 会开启线程执行 block 的任务

    • 异步是多线程的代名词

    • 异步 dispatch_async

    • 同步 dispatch_sync

  4. 队列 - 负责调度任务

    • 专门用来在主线程上调度任务的队列

    • 不会开启线程

    • 主线程空闲时才会调度队列中的任务在主线程执行

    • dispatch_get_main_queue();

    • 一次可以"调度"多个任务

    • dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);

    • 一次只能"调度"一个任务

    • dispatch_queue_create("itheima", NULL);

    • 串行队列

    • 并发队列

    • 主队列

阶段性小结

  • 开不开线程由执行任务的函数决定

    • 异步开,异步是多线程的代名词

    • 同步不开

  • 开几条线程由队列决定

    • iOS 8.0 之后,GCD 能够开启非常多的线程

    • iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程

    • 串行队列开一条线程

    • 并发队列开多条线程,具体能开的线程数量由底层线程池决定

- 队列的选择

  • 多线程的目的:将耗时的操作放在后台执行!

  • 串行队列,只开一条线程,所有任务顺序执行

    • 如果任务有先后执行顺序的要求

    • 效率低 -> 执行慢 -> "省电"

    • 有的时候,用户其实不希望太快!例如使用 3G 流量,"省钱"

  • 并发队列,会开启多条线程,所有任务不按照顺序执行

    • 如果任务没有先后执行顺序的要求

    • 效率高 -> 执行快 -> "费电"

    • WIFI,包月

实际开发中,线程数量如何决定?

  • WIFI 线程数 6 条

  • 3G / 4G 移动开发的时候,2~3条,再多会费电费钱!


同步 & 异步

概念

  • 同步

    • 必须等待当前语句执行完毕,才会执行下一条语句

  • 异步

    • 不用等待当前语句执行完毕,就可以执行下一条语句

NSThread 中的 同步 & 异步

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    NSLog(@"start");    // 同步执行//    [self demo];
    // 异步执行
    [self performSelectorInBackground:@selector(demo) withObject:nil];    NSLog(@"over");
}

- (void)demo {    NSLog(@"%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];    NSLog(@"demo 完成");
}

代码小结

  • 同步 从上到下顺序执行

  • 异步 是多线程的代名词


block

概念

动画 block 回顾

self.demoView.center = CGPointMake(self.view.center.x, 0);// 此方法会立即执行动画 block[UIView animateWithDuration:2.0 delay:0 usingSpringWithDamping:0.3 initialSpringVelocity:10 options:0 animations:^{    NSLog(@"动画开始");    self.demoView.center = self.view.center;
} completion:^(BOOL finished) {    // 会在动画结束后执行
   NSLog(@"动画完成");
}];NSLog(@"come here");

block 基本演练

- (void)blockDemo1 {    // 定义block
   // 类型 变量名 = 值
   void (^block)() = ^ {        NSLog(@"Hello block");
   };    // 执行
   block();
}

使用 inlineBlock 可以快速定义 block,不过 block 一定要过关

- (void)blockDemo2 {    void (^block)() = ^ {        NSLog(@"Hello block");
   };

   [self demoBlock:block];
}///  演示 block 当作参数传递- (void)demoBlock:(void (^)())completion {    NSLog(@"干点什么");

   completion();
}
- (void)blockDemo3 {    // 栈区变量
   int i = 10;    NSLog(@"%p", &i);    void (^block)() = ^ {        // 定义 block 的时候会对栈区变量进行一次 copy
       NSLog(@"Hello block %d %p", i, &i);
   };

   [self demoBlock:block];
}

如果 block 中使用了外部变量,会对外部变量做一次 copy

- (void)blockDemo4 {    // 栈区变量
   __block int i = 10;    NSLog(@"%p", &i);    void (^block)() = ^ {        // 定义 block 的时候会对栈区变量进行一次 copy
       NSLog(@"Hello block %d %p", i, &i);
       i = 20;
   };    NSLog(@"block 定义完成 %p %d", &i, i);

   [self demoBlock:block];    NSLog(@"===>%d", i);
}

如果要在 block 内部修改栈区变量,需要使用 __block 修饰符,并且定义 block 之后,栈区变量的地址会变化为堆区地址

block 的内存位置

@property (nonatomic, copy) void (^myBlock)();
- (void)blockDemo5 {    int i = 10;    void (^block)() = ^ {        NSLog(@"i --- %d", i);
   };    NSLog(@"%@", block);    self.myBlock = block;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    NSLog(@"%@", self.myBlock);
}

注意:虽然目前 ARC 编译器在设置属性时,已经替程序员复制了 block,但是定义 block时,仍然建议使用 copy 属性

GCD 常用代码

体验代码

异步执行任务

- (void)gcdDemo1 {    // 1. 全局队列
   dispatch_queue_t q = dispatch_get_global_queue(0, 0);    // 2. 任务
   void (^task)() = ^ {        NSLog(@"%@", [NSThread currentThread]);
   };    // 3. 指定执行任务的函数
   // 异步执行任务 - 新建线程,在新线程执行 task
   dispatch_async(q, task);    NSLog(@"come here");
}

注意:如果等待时间长一些,会发现线程的 number 发生变化,由此可以推断 gcd 底层线程池的工作

同步执行任务

- (void)gcdDemo1 {    // 1. 全局队列
   dispatch_queue_t q = dispatch_get_global_queue(0, 0);    // 2. 任务
   void (^task)() = ^ {        NSLog(@"%@", [NSThread currentThread]);
   };    // 3. 指定执行任务的函数
   // 同步执行任务 - 不开启线程,在当前线程执行 task
   dispatch_sync(q, task);    NSLog(@"come here");
}

精简代码

- (void)gcdDemo2 {    for (int i = 0; i < 10; ++i) {        dispatch_async(dispatch_get_global_queue(0, 0), ^{            NSLog(@"%@ %@", [NSThread currentThread], @"hello");
       });
   }
}

与 NSThread 的对比

  1. 所有的代码写在一起的,让代码更加简单,易于阅读和维护

  2. 使用 GCD 不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期

  3. 如果要开多个线程 NSThread 必须实例化多个线程对象

  4. NSThread 靠 NSObject 的分类方法实现的线程间通讯,GCD 靠 block

线程间通讯

- (void)gcdDemo3 {    dispatch_async(dispatch_get_global_queue(0, 0), ^{        NSLog(@"耗时操作 %@", [NSThread currentThread]);        // 耗时操作之后,更新UI
       dispatch_async(dispatch_get_main_queue(), ^{            NSLog(@"更新 UI %@", [NSThread currentThread]);
       });
   });
}

以上代码是 GCD 最常用代码组合!

- (void)gcdDemo4 {    dispatch_async(dispatch_get_global_queue(0, 0), ^{        NSLog(@"耗时操作");        dispatch_sync(dispatch_get_main_queue(), ^{            NSLog(@"更新UI");
       });        NSLog(@"更新UI完毕");
   });
}

网络下载图片

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_async(dispatch_get_global_queue(00), ^{
        NSLog(@"%s %@", __FUNCTION__, [NSThread currentThread]);

        // 1. 异步下载网络图片
        NSURL *url = [NSURL URLWithString:@"
http://f.hiphotos.baidu.com/image/pic/item/1f178a82b9014a901bef674aaa773912b21bee70.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        // 2. 完成后更新 UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            [self.imageView sizeToFit];

            self.scrollView.contentSize = image.size;
        });
    });

}


串行队列

特点

  • 先进先出的方式,顺序调度队列中的任务执行

  • 无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务

队列创建

dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL);dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", NULL);

串行队列演练

  • 串行队列 同步执行

/**
 提问:是否开线程?是否顺序执行?come here 的位置?
 */- (void)gcdDemo1 {    // 1. 队列
    dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_SERIAL);    // 2. 执行任务
    for (int i = 0; i < 10; ++i) {        NSLog(@"--- %d", i);        dispatch_sync(q, ^{            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }    NSLog(@"come here");
}
  • 串行队列 异步执行

/**
 提问:是否开线程?是否顺序执行?come here 的位置?
 */- (void)gcdDemo2 {    // 1. 队列
    dispatch_queue_t q = dispatch_queue_create("itheima", NULL);    // 2. 执行任务
    for (int i = 0; i < 10; ++i) {        NSLog(@"--- %@ %d", [NSThread currentThread], i);        dispatch_async(q, ^{            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }    NSLog(@"come here");
}

并发队列

特点

队列创建

dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);

并发队列演练

/**
提问:是否开线程?是否顺序执行?come here 的位置?
*/- (void)gcdDemo3 {    // 1. 队列
   dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);    // 2. 执行任务
   for (int i = 0; i < 10; ++i) {        dispatch_async(q, ^{            NSLog(@"%@ - %d", [NSThread currentThread], i);
       });
   }    NSLog(@"come here");
}
/**
提问:是否开线程?是否顺序执行?come here 的位置?
*/- (void)gcdDemo4 {    // 1. 队列
   dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);    // 2. 执行任务
   for (int i = 0; i < 10; ++i) {        dispatch_sync(q, ^{            NSLog(@"%@ - %d", [NSThread currentThread], i);
       });        NSLog(@"---> %i", i);
   }    NSLog(@"come here");
}

主队列

特点

队列获取

dispatch_queue_t queue = dispatch_get_main_queue();

主队列演练

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   [self gcdDemo1];

   [NSThread sleepForTimeInterval:1];    NSLog(@"over");
}

- (void)gcdDemo1 {    dispatch_queue_t queue = dispatch_get_main_queue();    for (int i = 0; i < 10; ++i) {        dispatch_async(queue, ^{            NSLog(@"%@ - %d", [NSThread currentThread], i);
       });        NSLog(@"---> %d", i);
   }    NSLog(@"come here");
}

主线程空闲时才会调度队列中的任务在主线程执行

// MARK: 主队列,同步任务- (void)gcdDemo6 {    // 1. 队列
   dispatch_queue_t q = dispatch_get_main_queue();    NSLog(@"!!!");    // 2. 同步
   dispatch_sync(q, ^{        NSLog(@"%@", [NSThread currentThread]);
   });    NSLog(@"come here");
}

主队列主线程相互等待会造成死锁

同步任务的作用

同步任务,可以让其他异步执行的任务,依赖某一个同步任务

例如:在用户登录之后,再异步下载文件!

- (void)gcdDemo1 {    dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);    dispatch_sync(queue, ^{        NSLog(@"登录 %@", [NSThread currentThread]);
   });    dispatch_async(queue, ^{        NSLog(@"下载 A %@", [NSThread currentThread]);
   });    dispatch_async(queue, ^{        NSLog(@"下载 B %@", [NSThread currentThread]);
   });
}
- (void)gcdDemo2 {    dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);    void (^task)() = ^{        dispatch_sync(queue, ^{            NSLog(@"登录 %@", [NSThread currentThread]);
       });        dispatch_async(queue, ^{            NSLog(@"下载 A %@", [NSThread currentThread]);
       });        dispatch_async(queue, ^{            NSLog(@"下载 B %@", [NSThread currentThread]);
       });
   };    dispatch_async(queue, task);
}
- (void)gcdDemo3 {    dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);    void (^task)() = ^ {        dispatch_sync(dispatch_get_main_queue(), ^{            NSLog(@"死?");
       });
   };    dispatch_async(queue, task);
}


主队列在主线程空闲时才会调度队列中的任务在主线程执行



Barrier 异步

代码演练

@interface ViewController () {    // 加载照片队列
   dispatch_queue_t _photoQueue;
}@property (nonatomic, strong) NSMutableArray *photoList;@end- (NSMutableArray *)photoList {    if (_photoList == nil) {
       _photoList = [[NSMutableArray alloc] init];
   }    return _photoList;
}

NSMutableArray 是非线程安全的

- (void)viewDidLoad {
    [super viewDidLoad];

    _photoQueue = dispatch_queue_create("com.itheima.com", DISPATCH_QUEUE_CONCURRENT);    for (int i = 0; i < 20; ++i) {
        [self loadPhotos:i];
    }
}

- (void)loadPhotos:(int)index {    dispatch_async(_photoQueue, ^{
       [NSThread sleepForTimeInterval:1.0];        NSString *fileName = [NSString stringWithFormat:@"%02d.jpg", index % 10 + 1];        NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];        UIImage *image = [UIImage imageWithContentsOfFile:path];

       [self.photoList addObject:image];        NSLog(@"添加照片 %@", fileName);
   });
}

运行测试

NSLog(@"添加照片 %@", fileName);
dispatch_barrier_async(_photoQueue, ^{
   [self.photoList addObject:image];    NSLog(@"OK %@", [NSThread currentThread]);

});

使用 dispatch_barrier_async 添加的 block 会在之前添加的 block 全部运行结束之后,才在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作!

Barrier 工作示意图

注意:dispatch_barrier_async 必须使用自定义队列,否则执行效果和全局队列一致

全局队列

全局队列 & 并发队列的区别

全局队列 异步任务

/**
提问:是否开线程?是否顺序执行?come here 的位置?
*/- (void)gcdDemo8 {    // 1. 队列
   dispatch_queue_t q = dispatch_get_global_queue(0, 0);    // 2. 执行任务
   for (int i = 0; i < 10; ++i) {        dispatch_async(q, ^{            NSLog(@"%@ - %d", [NSThread currentThread], i);
       });
   }    NSLog(@"come here");
}

运行效果与并发队列相同

参数

  1. 服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级

  2. 为未来保留使用的,应该永远传入0


结论:如果要适配 iOS 7.0 & 8.0,使用以下代码: dispatch_get_global_queue(0, 0);



延迟操作

// MARK: - 延迟执行- (void)delay {    /**
    从现在开始,经过多少纳秒,由"队列"调度异步执行 block 中的代码

    参数
    1. when    从现在开始,经过多少纳秒
    2. queue   队列
    3. block   异步执行的任务
    */
   dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));    void (^task)() = ^ {        NSLog(@"%@", [NSThread currentThread]);
   };    // 主队列//    dispatch_after(when, dispatch_get_main_queue(), task);
   // 全局队列//    dispatch_after(when, dispatch_get_global_queue(0, 0), task);
   // 串行队列
   dispatch_after(when, dispatch_queue_create("itheima", NULL), task);    NSLog(@"come here");
}

- (void)after {
   [self.view performSelector:@selector(setBackgroundColor:) withObject:[UIColor orangeColor] afterDelay:1.0];    NSLog(@"come here");
}

一次性执行

有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”

// MARK: 一次性执行- (void)once {    static dispatch_once_t onceToken;    NSLog(@"%ld", onceToken);    dispatch_once(&onceToken, ^{
       [NSThread sleepForTimeInterval:1.0];        NSLog(@"一次性吗?");
   });    NSLog(@"come here");
}
- (void)demoOnce {    for (int i = 0; i < 10; ++i) {        dispatch_async(dispatch_get_global_queue(0, 0), ^{
           [self once];
       });
   }
}

单例测试

单例的特点

  1. 在内存中只有一个实例

  2. 提供一个全局的访问点

单例实现

// 使用 dispatch_once 实现单例+ (instancetype)sharedSingleton {    static id instance;    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{
       instance = [[self alloc] init];
   });    return instance;
}// 使用互斥锁实现单例+ (instancetype)sharedSync {    static id syncInstance;    @synchronized(self) {        if (syncInstance == nil) {
           syncInstance = [[self alloc] init];
       }
   }    return syncInstance;
}

面试时只要实现上面 sharedSingleton 方法即可

单例测试

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    long largeNumber = 1000 * 1000;

    // 测试互斥锁
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    for (long i = 0; i < largeNumber; ++i) {
        [Singleton sharedSync];
    }
    NSLog(@"互斥锁: %f", CFAbsoluteTimeGetCurrent() - start);

    // 测试 dispatch_once
    start = CFAbsoluteTimeGetCurrent();
    for (long i = 0; i < largeNumber; ++i) {
        [Singleton sharedSingleton];
    }
    NSLog(@"dispatch_once: %f", CFAbsoluteTimeGetCurrent() - start);

}



调度组

常规用法

- (void)group1 {    // 1. 调度组
   dispatch_group_t group = dispatch_group_create();    // 2. 队列
   dispatch_queue_t q = dispatch_get_global_queue(0, 0);    // 3. 将任务添加到队列和调度组
   dispatch_group_async(group, q, ^{
       [NSThread sleepForTimeInterval:1.0];        NSLog(@"任务 1 %@", [NSThread currentThread]);
   });
   dispatch_group_async(group, q, ^{        NSLog(@"任务 2 %@", [NSThread currentThread]);
   });
   dispatch_group_async(group, q, ^{        NSLog(@"任务 3 %@", [NSThread currentThread]);
   });    // 4. 监听所有任务完成
   dispatch_group_notify(group, q, ^{        NSLog(@"OVER %@", [NSThread currentThread]);
   });    // 5. 判断异步
   NSLog(@"come here");
}

enter & leave

// MARK: - 调度组 2- (void)group2 {    // 1. 调度组
   dispatch_group_t group = dispatch_group_create();    // 2. 队列
   dispatch_queue_t q = dispatch_get_global_queue(0, 0);    // dispatch_group_enter & dispatch_group_leave 必须成对出现
   dispatch_group_enter(group);
   dispatch_group_async(group, q, ^{        NSLog(@"任务 1 %@", [NSThread currentThread]);        // dispatch_group_leave 必须是 block 的最后一句
       dispatch_group_leave(group);
   });

   dispatch_group_enter(group);
   dispatch_group_async(group, q, ^{        NSLog(@"任务 2 %@", [NSThread currentThread]);        // dispatch_group_leave 必须是 block 的最后一句
       dispatch_group_leave(group);
   });    // 4. 阻塞式等待调度组中所有任务执行完毕
   dispatch_group_wait(group, DISPATCH_TIME_FOREVER);    // 5. 判断异步
   NSLog(@"OVER %@", [NSThread currentThread]);
}


© 著作权归作者所有

上一篇: NSOperation 抽象类
下一篇: NSThread
就不穿小内
粉丝 4
博文 59
码字总数 32988
作品 0
海淀
私信 提问
iOS OS X 和 iOS 中的多线程技术-4 (GCD)

//联系人:石虎QQ:1224614774昵称:嗡嘛呢叭咪哄 一、GCD GCD(Grand Central Dispatch)是 Apple 公司为了提高 OS X 和 iOS 系统在多核处理器上运行并行代码的能力而开发的一系列相关技术,它...

石虎132
2017/12/03
0
0
iOS基础知识整理之多线程技术

多线程技术 多线程(multithreading) 是指软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。 原理...

无忌不悔
2018/11/07
0
0
iOS多线程的实现方案

技术方案 简介 语言 生命周期 使用频率 NSThread 使用更加面向对象 简单实用,可直接操作线程对象 OC 程序猿管理 偶尔使用 GCD 旨在替代NSThread等线程技术 充分利用设备的多核 C 自动管理 ...

JlongTian
2016/01/07
34
0
ios 使用GCD 多线程 教程

什么是GCD Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。该方法在Mac OS X 10.6雪豹中首次推出,并随后被引入到了iOS4.0中。GCD是一个替代诸如NSThread, NSOperation...

孙启超
2013/10/10
0
8
传智播客学习笔记 网络多线程

主线程处理UI,避免耗时操作 iOS多线程技术有4种 pthread,通用技术,跨平台 c语言,程序员管理生命周期,几乎不用 NSThread 面向对象,可以直接操作线程 OC语言 程序员 管理生命周期,偶尔使...

云飞扬v5
2015/08/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Android双向绑定原理简述

Android双向绑定原理简述 双向绑定涉及两个部分,即将业务状态的变化传递给UI,以及将用户输入信息传递给业务模型。 首先我们来看业务状态是如何传递给UI的。开启dataBinding后,编译器为布局...

tommwq
今天
4
0
Spring系列教程八: Spring实现事务的两种方式

一、 Spring事务概念: 事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。...

我叫小糖主
今天
9
0
CentOS 的基本使用

1. 使用 sudo 命令, 可以以 root 身份执行命令, 必须要在 /etc/sudoers 中定义普通用户 2. 设置 阿里云 yum 镜像, 参考 https://opsx.alibaba.com/mirror # 备份mv /etc/yum.repos.d/CentO...

北漂的我
昨天
5
0
Proxmox VE技巧 移除PVE “没有有效订阅” 的弹窗提示

登陆的时候提示没有有效的订阅You do not have a valid subscription for this server. Please visit www.proxmox.com to get a list of available options. 用的是免费版的,所以每次都提示......

以谁为师
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部