网络与多线程 2

 

1. Block
1.1 block的基础语法
1.2 block的内存管理
- NSStackBlock:位于栈内存,函数返回后Block将无效;
1.2.1. 不引用任何外部变量
1.2.2 引用外部变量的block
1.3 定义和赋值block属性
1.4 block的循环引用
2. GCD
2.1 将任务添加到队列
2.2 线程间通讯
2.3 实现异步下载网络图片
3. GCD任务和队列
3.1 Serial Dispatch Queue
3.1.1 创建队列
3.1.2 串行队列+同步任务
3.1.3 串行队列+异步任务
3.2 Concurrent Dispath Queue
3.2.1 创建队列
3.2.2 并发队列+同步任务
3.3.2 并发队列+异步任务
3.3 主队列
3.3.1 创建主队列
3.3.2 主队列+同步任务
3.3.3 主队列+异步任务
3.4 全局队列
3.4.1 全局同步
3.4.2 全局异步
3.5 总结
4. GCD阻塞(Barrier)

 

  • 当self.view = nil的时候,会调用loadView方法。 
     

1. Block

  • block最常用来逆向传值,又叫做回调
  • block是一段代码块
  • 是匿名函数,所以是C的
  • block代码块里面的代码只有被调用的时候才会被执行。
  • block可以当作参数传递
  • block可以作为属性

需要注意的是由于Objective-C在iOS中不支持GC机制,使用Block必须自己管理内存,而内存管理正是使用Block坑最多的地方,错误的内存管理 要么导致return cycle内存泄漏要么内存被提前释放导致crash。 Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅 实现函数的功能,还能携带函数的执行环境。

  • Block其实包含两个部分内容:
    • Block执行的代码。这是在编译的时候已经生成好的;
    • 一个包含Block执行时需要的所有外部变量值的数据结构。 Block将使用到的变量的值建立拷贝一个副本到栈上。

1.1 block的基础语法

  • block的类型是void(^)() ,和int一样。
  • block的名字要放在小尖尖里面。 void(^demo)() 这就是一个叫做demo的无参数的block。
  • block的定义
 
  1. // 无参无返回
  2. void(^block)();
  3. // 无参有返回
  4. int(^block1)();
  5. // 有参有返回
  6. int(^block1)(int number);

block的定义可以借助inlineBlock系统提供的代码块进行。

1.2 block的内存管理

  • 无论是ARC还是MRC,只要block没有访问外部变量,block始终在全局区
  • MRC情况下
    • block如果访问外部变量,block在栈里
    • 不能对block使用retain,否则不能保存在堆里
    • 只有使用copy,才能放到堆里
  • ARC情况下

    • block如果访问外部变量,block在堆里
    • block可以使用copy和strong,并且block是一个对象
  • NSMallocBlock :表示block在堆区

  • NSGlobalBlock:类似函数,位于text段;

NSStackBlock:位于栈内存,函数返回后Block将无效;

为什么block需要使用copy? 
ARC环境下栈区上面的对象,随时有可能被销毁。所以block在ARC下放在栈区很危险,很有可能在没有调用之前就被销毁了。 
使用了copy,访问了局部变量之后,block就从栈区到了堆区。

在ARC环境下,使用strong和copy的效果是一毛一样! 
但是把block作为属性的时候,老程序员还是习惯用copy。

单独把某个文件变成MRC环境。

1.2.1. 不引用任何外部变量

 
  1. - (void)blockDemo {
  2.  
  3. void(^myBlock)() = ^ {
  4. NSLog(@"hello world");
  5. };
  6.  
  7. NSLog(@"%@", myBlock);
  8. }
  • 不引用任何外部变量的 block 保存在全局区 NSGlobalBlock
  • 如果Block没有引用外部变量,那么这个Block的函数体内部包装的代码都不会发生变化,而且执行效率高,保存在全局区;(类似不变的字符串)
  • 但是实际开发中,不引用外部变量的 block 几乎是不存在的

1.2.2 引用外部变量的block

 
  1. - (void)blockDemo {
  2. int i = 10;
  3. void(^myBlock)() = ^ {
  4. NSLog(@"hello world %zd", i);
  5. };
  6. NSLog(@"%@", myBlock);
  7. }

引用外部变量的 block 保存在 :

  • ARC : 堆区 NSMallocBlock
  • MRC : 栈区 NSStackBlock 
    因此 : 在定义 block 属性时应该使用 copy 关键字,将 block 从栈区复制到堆区

1.3 定义和赋值block属性

 
  1. // 定义 block 属性
  2. property (nonatomic, copy) void (^demoBlock)();
  3.  
  4. - (void)blockDemo2 {
  5. int i = 10;
  6. void(^myBlock)() = ^ {
  7. NSLog(@"hello world %zd", i);
  8. };
  9. NSLog(@"%@", myBlock);
  10.  
  11. // 错误的写法,不会调用 setter 方法,MRC下,无法拷贝到堆区
  12. // _demoBlock = myBlock;
  13.  
  14. // 正确的写法,调用 setter 方法,并且对 block 进行 copy
  15. self.demoBlock = myBlock;
  16. NSLog(@"%@", self.demoBlock);
  17. }

1.4 block的循环引用

  • block循环引用
 
  1. //定义属性
  2. @property (nonatomic, copy) void (^demoBlock)();
  3.  
  4. //在 viewDidLoad 中记录 block
  5. - (void)viewDidLoad {
  6. [super viewDidLoad];
  7. void(^block)() = ^ {
  8. NSLog(@"%@", self.view);
  9. };
  10. // 记录 block
  11. self.demoBlock = block;
  12. }

上述代码发生循环引用的原因:

  • viewController引用了block属性。也就是说所有声明的属性,都被控制器做了强引用。
  • block内部代码又引用了self (viewcontroller)
  • 解决循环引用的办法: 

  • 天坑!!!

    • 在block里面一定不要直接使用成员变量,因为所有的成员变量都已经被controller强引用了。
    • 如果需要引用成员变量,请使用weakself 
      __weak typeof(self) weakself = self

2. GCD

  • 全称是Grand Central Dispatch
  • 纯C语言的,提供了非常多强大的函数.

  1. GCD的优势:

  • 是苹果为多核的并行运算提出的解决方案
  • GCD会自动利用更多的CPU内核(例如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码.

  1. GCD的核心:将任务添加到队列

  • 任务:执行的操作。只有两种方式:同步、异步。
  • 队列:用来存放任务

  1. GCD使用的两个步骤:

  • 创建任务:任务都是使用block封装的
  • 将任务添加到队列中

    • GCD会自动将队列中的任务取出来,放到对应线程中执行
    • 任务的去处遵循first in first out原则
  • GCD中所有的函数,都是以dispatch开头的。 

  • 异步具备开启新线程的能力,也具备跳过当前代码继续往下执行的能力。

  • 同步不具备开启新线程的能力,也不具备跳过当前代码继续往下执行的能力。
名称开启新线程的能力跳过当前代码继续往下执行的能力
异步
同步NULLNULL

2.1 将任务添加到队列

 
  1. // 队列+任务
  2. - (void)gcdDemo1
  3. {
  4. // 全局并发队列
  5. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  6.  
  7. // 任务
  8. void (^task)() = ^ {
  9. NSLog(@"%@",[NSThread currentThread]);
  10. };
  11.  
  12. // 同步任务
  13. dispatch_sync(queue, task);
  14.  
  15. // 异步任务 : 每次执行任务的线程不一定是一样的
  16. dispatch_async(queue, task);
  17.  
  18. /*也可以简写
  19. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  20. NSLog(@"%@",[NSThread currentThread]);
  21. });
  22. */
  23. NSLog(@"end");
  24. }

2.2 线程间通讯

 
  1. /// 线程间通信
  2. - (void)gcdDemo3
  3. {
  4. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  5. NSLog(@"下载中... %@",[NSThread currentThread]);
  6.  
  7. dispatch_async(dispatch_get_main_queue(), ^{
  8. NSLog(@"下载完成 %@",[NSThread currentThread]);
  9. });
  10. });
  11. }

2.3 实现异步下载网络图片

 
  1. - (void)downloadImage
  2. {
  3. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  4. // 获取网络图片地址
  5. NSURL *url = [NSURL URLWithString:@"http://photocdn.sohu.com/20151209/mp47379110_1449633737507_2_th.png"];
  6. // 获取网络图片二进制数据
  7. NSData *data = [NSData dataWithContentsOfURL:url];
  8. // 获取图片对象
  9. UIImage *image = [UIImage imageWithData:data];
  10.  
  11. // 图片下载完成之后,回到主线程更新UI
  12. dispatch_async(dispatch_get_main_queue(), ^{
  13. // 设置图片视图
  14. self.imageView.image = image;
  15. // 图片视图自适应图片的大小
  16. [self.imageView sizeToFit];
  17. // 设置滚动视图
  18. [self.scrollView setContentSize:image.size];
  19. });
  20. });
  21. }

3. GCD任务和队列

  • GCD的自定义队列分为两种:串行队列(Serial Dispatch Queue)、并发队列(Concurrent Dispatch Queue)
  • GCD 公开有 5 个不同的队列:运行在主线程中的 main queue,3 个不同优先级的后台队列,以及一个优先级更低的后台队列(用于 I/O)。 
    另外,开发者可以创建自定义队列:串行或者并行队列。自定义队列非常强大,在自定义队列中被调度的所有 block 最终都将被放入到系统的全局队列中和线程池中。

  • 串行队列(Serial Dispatch Queue):

    • 让任务一个接着一个有序的执行:不管队列里面放的是什么任务.一个任务执行完毕后,再执行下一个任务.
    • 同时只能调度一个任务执行.
  • 并发队列(Concurrent Dispatch Queue)

    • 可以让多个任务并发/同时执行.自动开启多个线程同时执行多个任务.
    • 同时可以调度多个任务执行
    • 并发队列的并发功能只有内部的任务是异步任务时,才有效.

3.1 Serial Dispatch Queue

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

3.1.1 创建队列

 
  1. // 参数1 : 队列的标示符
  2. // 参数2 : 队列的属性.决定了队列是串行的还是并行的.
  3. dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);

3.1.2 串行队列+同步任务

 
  1. #pragma mark - 串行队列+同步任务
  2. /*
  3. 1. 没有开新线程
  4. 2. 循环是顺序打印
  5. 3. @"end"最后执行
  6. */
  7. - (void)gcdDemo1
  8. {
  9. // 串行队列
  10. dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);
  11.  
  12. for (int i = 0; i < 10; i++) {
  13. dispatch_sync(queue, ^{
  14. NSLog(@"%d %@",i,[NSThread currentThread]);
  15. });
  16. }
  17.  
  18. NSLog(@"end");
  19. }

3.1.3 串行队列+异步任务

 
  1. #pragma mark - 串行队列+异步任务
  2. /*
  3. 1. 开一条新线程 : 因为队列是顺序调度任务,前一个任务执行完成以后才能调度后面的任务,开一条新线程就够了
  4. 2. 循环是顺序打印
  5. 3. @"end"不是在最后执行
  6. */
  7. - (void)gcdDemo2
  8. {
  9. // 串行队列
  10. dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);
  11.  
  12. for (int i = 0; i < 10; i++) {
  13. dispatch_async(queue, ^{
  14. NSLog(@"%d %@",i,[NSThread currentThread]);
  15. });
  16. }
  17.  
  18. NSLog(@"end");
  19. }

3.2 Concurrent Dispath Queue

  • 并行同步:不会开启新的线程

3.2.1 创建队列

 
  1. // 参数1 : 队列的标示符
  2. // 参数2 : 队列的属性.决定了队列是串行的还是并行的.
  3. dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);

3.2.2 并发队列+同步任务

 
  1. #pragma mark - 并发队列+同步任务
  2. /*
  3. 1. 没有开新线程
  4. 2. 循环顺序打印
  5. 3. @"end"最后执行
  6. */
  7. - (void)gcdDemo1
  8. {
  9. // 并发队列
  10. dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);
  11.  
  12. for (int i = 0; i < 10; i++) {
  13. dispatch_sync(queue, ^{
  14. NSLog(@"%d %@",i,[NSThread currentThread]);
  15. });
  16. }
  17.  
  18. NSLog(@"end");
  19. }

3.3.2 并发队列+异步任务

 
  1. #pragma mark - 并发队列+同步任务
  2. /*
  3. 1. 开启多条新线程
  4. 2. 循环无序打印
  5. 3. @"end"不是最后执行
  6. */
  7. - (void)gcdDemo2
  8. {
  9. // 并发队列
  10. dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);
  11.  
  12. for (int i = 0; i < 10; i++) {
  13. dispatch_async(queue, ^{
  14. NSLog(@"%d %@",i,[NSThread currentThread]);
  15. });
  16. }
  17.  
  18. NSLog(@"end");
  19. }

3.3 主队列

  • 主队列是串行队列的特殊的一种。
  • 主队列中的任务,只能在主线程中进行
  • 主队列中的任务,只有当主线程空闲的时候才能执行。

3.3.1 创建主队列

  • 主队列是负责在主线程调度任务的,会随着程序启动一起创建,只需要获取不用创建.
 
  1. dispatch_queue_t queue = dispatch_get_main_queue();

3.3.2 主队列+同步任务

  • 会死锁
  • Xcode8.1 之后运行的时候会直接崩溃
  • 造成死锁的原因是因为主队列是本质上是串行的,要等到依照顺序执行。而同步任务不会产生新的线程,也是按照顺序执行的。这样双方都在互相等待。
 
  1. //下面代码是会被死锁的代码
  2. - (void)gcdDemo2
  3. {
  4. dispatch_queue_t queue = dispatch_get_main_queue();
  5.  
  6. NSLog(@"start");
  7.  
  8. dispatch_sync(queue, ^{
  9. NSLog(@"执行中...%@",[NSThread currentThread]);
  10. });
  11.  
  12. NSLog(@"end");
  13. }
  14.  
  15.  
  16. #pragma mark - 死锁解决办法
  17. // 主队列中的同步任务放进子线程中,不使其阻塞主线程
  18. - (void)gcdDemo3
  19. {
  20. dispatch_async(dispatch_queue_create("ZJ", DISPATCH_QUEUE_CONCURRENT), ^{
  21.  
  22. NSLog(@"start");
  23.  
  24. dispatch_sync(dispatch_get_main_queue(), ^{
  25. NSLog(@"执行中...%@",[NSThread currentThread]);
  26. });
  27.  
  28. NSLog(@"end");
  29. });
  30. }

3.3.3 主队列+异步任务

 
  1. #pragma mark - 主队列+异步任务
  2. /*
  3. 1. 执行顺序 : @"start"->@"end"->执行中...
  4. 2. 没有开启新线程
  5. */
  6. - (void)gcdDemo1
  7. {
  8. // 主队列 : 程序一启动就创建好的,不需要创建
  9. dispatch_queue_t queue = dispatch_get_main_queue();
  10.  
  11. NSLog(@"start");
  12.  
  13. dispatch_async(queue, ^{
  14. NSLog(@"执行中...%@",[NSThread currentThread]);
  15. });
  16.  
  17. NSLog(@"end");
  18. }

3.4 全局队列

  • 全局队列是并行队列的特殊一种。
 
  1. // 全局队列,跟主队列一样不需要创建
  2. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  • 开发中最常用的队列之一。另外一个是主队列。
  • 是系统为了方便程序员开发提供的。直接get就可以得到一个并发队列。

3.4.1 全局同步

  • 在当前线程执行,任务依次执行
 
  1. - (void)gcdDemo
  2. {
  3. // 全局队列,跟主队列一样不需要创建
  4. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  5.  
  6. for (int i = 0; i < 10; i++) {
  7. dispatch_sync(queue, ^{
  8. NSLog(@"%d %@",i,[NSThread currentThread]);
  9. });
  10.  
  11. /*
  12. dispatch_sync(queue, ^{
  13. NSLog(@"%d %@",i,[NSThread currentThread]);
  14. });
  15. */
  16. }
  17.  
  18. NSLog(@"end");
  19. }

3.4.2 全局异步

  • 开启多条线程,任务不按顺序执行
 
  1. #pragma mark - 全局队列
  2. // 执行效果跟并发队列一样的
  3. - (void)gcdDemo
  4. {
  5. // 全局队列,跟主队列一样不需要创建
  6. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  7.  
  8. for (int i = 0; i < 10; i++) {
  9. dispatch_async(queue, ^{
  10. NSLog(@"%d %@",i,[NSThread currentThread]);
  11. });
  12.  
  13. /*
  14. dispatch_sync(queue, ^{
  15. NSLog(@"%d %@",i,[NSThread currentThread]);
  16. });
  17. */
  18. }
  19.  
  20. NSLog(@"end");
  21. }

3.5 总结

任务\队列串行队列Serial并行队列concurrent主队列main全局队列global
同步任务sync无新线程、串行执行无新线程、串行执行没事会锁死无新线程、串行执行
异步任务async新线程、串行执行新线程并行执行无新线程、串行执行新线程并行执行

4. GCD阻塞(Barrier)

注意:

  • barrier使用的时候规定要使用自定义的并行队列
  • 大部分情况下都是正确的,但是不能绝对信任。

转载于:https://my.oschina.net/u/3135213/blog/805329

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值