多线程、GCD


多线程编程的三种方法

  • 方法1:NSThread
  • 方法2:GCD
  • 方法3:NSOperation和NSOperationQueue

创建线程(一个NSThread对象就代表一条线程)

  • 方式1:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];

  • 方式2:
NSThread *current = [NSThread currentThread];//获得当前线程
  • 方式3:
创建线程后自动启动线程(启动线程的方法start,被写入了系统底层,其实是存在的,只不过我们不能拿到)
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];//类方法
  • 方式4:
隐式创建并启动线程(启动线程的方法start,被写入了系统底层,其实是存在的,只不过我们不能拿到)
[self performSelectorInBackground:@selector(run) withObject:nil];//对象方法

  • 上述两种创建线程的方式(3,4)无法用线程创建出对象(两种方法的返回值类型为void,为viod的话,就说明地址是空的的,如果用等号左边的指针保存右边传递过来的空的地址,那么这个指针没有任何意义,只有用指针保存有值的地址,那么这个指针才有意义啊),只能通过 [类名/对象名 调用类方法/对象方法]的形式直接拿来用,无法对线程进行更详细的设置,因为只有创建出来对象,才能够让对象去访问属性,从而显示更详细的设置。

对以上的理解拓展出来一个思想:

//创建线程对象t
NSThread *t = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"创建线程1"];
//上面创建出来的对象t调用有返回值类型(instancetype)的对象方法
//用NSTread类型的指针z保存右边的地址,这样就可以用对象z来去访问属性,从而显示更详细的设置
NSThread  *z = [t initWithTarget:self selector:@selector(run:) object:nil];

创建主线程的其中一个做法+打印

  • 系统自带的大部分方法默认都是在主线程中执行,例如touchesBegan,你只要写了这个方法,点击屏幕时,就会在底层创建主线程(标志:打印时,number为1),并不需要在方法中创建线程.如果创建了,创建出的必定是子线程。
  • number值为1,值为1,肯定是主线程(后台线程),不为1,就为子线程
事例:
//主线程就是是点击屏幕时调用touchesBegan方法产生的。
//即点击屏幕的那一刻,就在系统底层产生了一个主线程,
//你在touchesBegan方法的花括号打印[NSThread currentThread],必定是主线程. 无论打印几个[NSThread currentThread,都是主线程
//如果在该方法手动创建一个线程,那么这个线程必定是子线程

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//打印主线程的地址
  NSLog(@"%@",[NSThread currentThread]);
  NSLog(@"--rhhhhhn--%@",[NSThread currentThread]);
}
打印结果如下
2016-03-13 20:45:58.452 04-了解-线程的状态[48288:1798704] {number = 1, name = main}
2016-03-13 20:45:58.452 04-了解-线程的状态[48288:1798704] --rhhhhhn--{number = 1, name = main}

创建子线程的其中一个做法+打印

事例:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//第一个参数:谁调用了run方法
//第二个参数:调用的方法的名称
//第三个参数:run方法的参数

//创建子线程(肯定是子线程)
    NSThread *a = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"哈哈哈"];
    //线程名(子线程)
    a.name = @"线程名张斌";
    //开启子线程
    [a start];
    //打印当前线程(主线程)的返回的地址
     NSLog(@"%@",[NSThread currentThread]);
 }

-(void)run:(NSString *)param{
        for (NSInteger i = 0; i<1; i++) {
             //打印子线程的名字和参数的名字
            NSLog(@"%zd--run---%@--%@",i,[NSThread currentThread].name,param);
        }
    }



打印结果如下:
2016-03-13 19:28:49.234 03-掌握-NSThread基本使用[47656:1752640] 0--{number = 2, name = 线程名张斌}---线程名张斌--哈哈哈
2016-03-13 19:28:49.234 03-掌握-NSThread基本使用[47656:1752503] {number = 1, name = main}


  • 以下例子经典+注意:只有在执行完[threadA start];时,才会执行run:方法,而不是执行完a就立刻跳转执行run方法[打断点已验证]
 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSThread *threadA = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"创建线程1"];//a
    threadA.name = @"线程A";
    //设置线程的优先级
    threadA.threadPriority = 1.0; //0.0~1.0 1.0是最高,默认是0.5
    //2.开始执行
    [threadA start];//b


}


-(void)run:(NSString *)param
{
    for (NSInteger i = 0; i<100; i++) {
        NSLog(@"%zd--run---%@--%@",i,[NSThread currentThread].name,param);
    }
    
}

线程小知识点:

  • 进程,即应用程序

  • 1个进程可以开启多条线程.多线程(并发)执行,其实是cup快速第在多条线程之间的调度(切换)

  • 开启大量线程,会降低程序的性能,因为cpu调度某个子线程的次数就少了,降低程序的性能,开销增大

  • 单个线程是串行执行任务的()

  • 并行:多个线程一起执行任务。同一时间处理多个任务的能力 超市收银员

  • 并发:有很多顾客正在等待结账

  • 主线程

    • 一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”。
    • 作用:刷新显示UI,处理UI事件。
    • 使用注意
      • 不要将耗时操作放到主线程中去处理,因为会卡住线程。
      • 和UI相关的刷新操作必须放到主线程中进行处理。
  • 主线程和子线程中的代码

    • 主线程:显示/刷新UI界面 处理UI事件(点击事件,滚动事件,拖动事件,刷新等代码应该在主线程中执行)
    • 耗时操作一定放在子进程执行(必须某些操作需要10秒才能完成。例如for循环,文件下载等代码)
  • 开始执行线程和退出线程

    • 开始执行线程:[对象名 start],只要用对象调用start方法,系统底层执行start方法,外界不会有start方法的实现
    • 退出线程:[类名 exit],exit是类方法,直接用[NSThread exit],退出线程
  • 优先级高的线程不一定最先执行完毕,优先级高只决定了线程被cpu调度的概率大一点

  • 当线程中所有的任务执行完毕,线程会自动被释放


容易混淆的术语

  • 有4个术语比较容易混淆:同步、异步、并发、串行

    • 同步和异步主要影响:能不能开启新的线程
    • 函数用来执行任务
      • 同步函数:只是在当前线程中执行任务,不具备开启新线程的能力
      • 异步函数:可以在新的线程中执行任务,具备开启新线程的能力(不必须开启)
    • 并发和串行主要影响:任务的执行方式
    • 队列用来存放任务
      • 并发队列:允许多个任务并发(同时)执行.(宏观同时执行,微观真实是无序的,即没有顺序) 【可以多个窗口打饭】
      • 串行队列:一个任务执行完毕后,再执行下一个任务【只有一个窗口,必须排队打饭】
  • 全局队列是全局并发队列吗?有区别?(只有两个区别,其余的两个方法可以相互替代)

    • 写法上的区别:全局并发队列模式是存在的,不需要我们分配存储空间创建出来,只需要写出来就行
    • 用法上的区别:使用栅栏函数时,不要是用全局并发队列要是用自己创建的队列,否则栅栏函数就会失效,相当于没写
  • 异步解决了线程堵塞,而并发则是在异步的基础上,提高了符合特性事件的处理时间效率


线程注意点:

  • 刷新UI要在主线程中执行,从上面可知其实等价于在主队列中执行。因为只要是主队列,一定不开线程,所以若执行任务,必定是在主线程。其实不用看上面,以后管他有没有同步函数还是异步函数,只要是主队列,一定在主线程中执行任务。
-  dispatch_async(dispatch_get_main_queue(), ^{
             [self.tableView reloadData];
        });
  • 以后判断是否要回到主线程刷新UI时,直接在当前方法打印NSLog(@"%@",[NSThread currentThread]);如果为1,就是主线程,那么就不需要回到主线程进行刷新(collectionView)表格,刷新tableView.

    • 刷新表格: [self.collectionView reloadData];
    • 刷新tableView: [self.tableView reloadData];
  • 平时常用的类,如果创建和UI相关的类都在UIKit/UIKit.h框架中,创建其余的类都在Foundation/Foundation.h框架中


GCD

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

  • GCD是异步执行任务的技术之一。一般将应用程序中记述的线程管理代码在系统集中实现,开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就可以生成必要的线程并计划执行任务。

  • GCD的优势

    • GCD是苹果公司为多核的并行运算提出的解决方案
    • GCD会自动利用更多的CPU内核(比如双核、四核)
    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
    • GCD可以将花费时间极其长的任务放到后台线程,可以改善应用的响应性能

// 创建了一个并行队列queue,并异步执行耗时操作
// 当耗时操作执行完毕,我拿到其中的资源回到主线程来更新相应的UI
// 在这个Block代码块之外,主线程并不会被耗时任务所堵塞,可以流畅的处理其他事情。
dispatch_queue_t queue = dispatch_queue_create("www.CoderZb.com", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
     
    /**
      
     放一些极其耗时间的任务在此执行
     */
     
    dispatch_async(dispatch_get_main_queue(), ^{
         
        /**
         耗时任务完成,拿到资源,更新UI
         更新UI只可以在主线程中更新
         */
         
    });
     
});

```

GCD基本使用【重点】
01 异步函数+并发队列:开启多条线程,并发(无顺序)执行任务
02 异步函数+串行队列:开启一条线程,串行执行任务
03 同步函数+并发队列:不开线程,串行执行任务
04 同步函数+串行队列:不开线程,串行执行任务
05 异步函数+主队列:不开线程,在主线程中串行执行任务
06 同步函数+主队列:不开线程,串行执行任务(注意死锁发生)
对06的拓展(精华):同步函数在子线程的方法中肯定不会发生死锁
                  同步函数在主线程的方法中肯定会发生死锁
07 注意同步函数和异步函数在执行顺序上面的差异

巧记:异步函数+不是主队列,一定会开线程
GCD中有2类核心的函数如下:
和任务相关的函数
//同步函数(第一个参数:队列,第二个参数:block(封装任务))
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

//同步函数的事例
  dispatch_sync(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);//任务1
    });
    
    
//异步函数
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

//异步函数的事例
  dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);//任务2
    });
    
    
//栅栏函数  
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
//栅栏函数的事例
dispatch_barrier_async(queue, ^{
        NSLog(@"+++++++++++++%@",[NSThread currentThread]);
    });
    
和队列相关的函数
注意区分这两个 DISPATCH_QUEUE_CONCURRENT(并发队列),DISPATCH_QUEUE_SERIAL(串行队列)

//创建并发队列(第一个参数:队列的名字,第二个参数:队列的类型)
 dispatch_queue_t queue = dispatch_queue_create("com.zhangbin.www.download", DISPATCH_QUEUE_CONCURRENT);

//获得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


//创建串行队列(第一个参数:队列的名字,第二个参数:队列的类型)
dispatch_queue_t queue = dispatch_queue_create("com.zhangbin.www.download", DISPATCH_QUEUE_SERIAL);


//获得主队列(本质是串行队列)
dispatch_queue_t queue = dispatch_get_main_queue();



GCD呕心沥血总结+截图验证

队列,都是用来存放任务
并发队列:允许多个任务同时执行
    1)直接create dispatch_queue_create
    2)全局并发队列
 
串行队列:只能一个接着一个的执行
    1)直接create dispatch_queue_create
    2)主队列:
        1)所有在主队列中的任务都会被放在主线程中执行
        2)主队列中的任务在执行之前会先检查主线程的状态,
          如果发现主线程当前正在执行任务那么会暂停队列中任务的调度
 
同步函数:同步函数中的队列是主队列的情况下,同步函数中的任务必须要得到所在方法的返回值才能够继续往下执行--->如果前面的任务没有执行完毕,那么后面的任务永远无法执行
异步函数:可以继续往下执行,等前面的任务执行完毕之后再回头执行-->我无所谓,你可以先执行后面的代码
 
 
 如果syncMain是主线程创建出来的方法,那么syncMain方法中的任务都将会成为主线程中处理的任务.
 
任务1没有执行完毕,那么后面的4个任务永远无法执行.
第1个任务之所以没有执行完,是因为主线程还没有执行完它的syncMain方法中的5个任务(任务1就是5个任务的其中一个),
那么第1个任务只能等待主线程执行完5个任务之后,拿到返回值之后才能执行。
因为第1个任务正在等主线程执行完任务,主线程在执行任务的过程中发现任务1正在等别人(主线程并不知道任务1其实就在等它),
所以主线程只能等待任务1完成,才能处理任务1。造成了任务1等主线程,主线程等任务1的状况(你等我,我等你),也就是死锁
 
 
 如果syncMain是子线程创建出来的方法,执行到syncMain时,任务1要放在主队列中
 又因为主队列要放在主线程中执行,所以就相当于任务1要放在主线程中执行。
 又又因为主队列中的任务在执行之前会先检查主线程的状态,检查之后发现主线程没有要执行的任务,
 这时候任务1就可以放在主线程中了。近而处理任务1,接着处理任务2,3,4,5.所以不会造成死锁

同步函数+主队列:不开线程,串行执行任务(注意死锁发生)

  • 同步函数在子线程的方法中肯定不会发生死锁
  • 同步函数在主线程的方法中肯定会发生死锁
多线程、GCD_第1张图片
26-07.png

26-08.png

多线程、GCD_第2张图片
26-12.png

异步函数+并发队列:开启多条线程,并发(无顺序)执行任务

多线程、GCD_第3张图片
26-06.png

异步函数+串行队列:开启一条线程,串行执行任务

多线程、GCD_第4张图片
26-11.png

同步函数+并发队列:不开线程,串行执行任务

多线程、GCD_第5张图片
26-10.png

同步函数+串行队列:不开线程,串行执行任务

多线程、GCD_第6张图片
26-09.png
  • 看开不开启线程,就看number值是否为1,如果为1(只有主线程),那么就不会开启线程
  • 看是串行执行任务还是并发执行任务,就看代码中NSLog的顺序和控制台中输出NSLog的顺序是否一致,如果一致,那么就是串行执行任务,如果顺序乱套了,那么就是并发执行任务

同步函数放到主队列中,会发生死锁,里面的任务不会执行

  • 同步函数;必须得到所在方法的返回值才能够继续往下执行,如果没有执行完毕,后面的将无法执行

  • 异步函数:可以继续往下执行,等前面的任务执行完毕之后再回头执行 ,可以限先执行后面的代码

  • 同步函数放到子线程中,不会发生死锁

  • 同步函数添加到主队列中---》结果:串行执行任务+死锁

  • 因为任务添加到了队列中,且串行执行任务,所以一次只能执行一个任务,所以这个任务还没有执行完成(只有出了花括号才算执行完),下面又有一个任务添加到了队列中,所以这个任务添加不到队列中,只能等待,下下下面的任务也是这种情况,最终就会造成死锁

  • 同步函数添加到子线程---》不会死锁

  • 在主线程中执行,等价于打印的number=1

  • [NSThread currentThread]打印的结果是下面的格式:
    {number = 4, name = 线程C}


线程安全

  • 互斥锁就是排队上厕所 .用@synchronized加锁

  • 什么时候才加锁:多个线程访问同一个资源(用NSLog打印时,number的值有重复的)

     互斥锁使用格式
     @synchronized(锁对象) { // 需要锁定的代码  }
     注意:锁定1份代码只用1把锁,用多把锁是无效的
    
     互斥锁的优缺点
     优点:能有效防止因多线程抢夺资源造成的数据安全问题
     缺点:需要消耗大量的CPU资源
    
     互斥锁的使用前提:多条线程抢夺同一块资源
    
  • 代码演示:

#import "ViewController.h"

@interface ViewController ()
/** 线程*/
@property (nonatomic ,strong) NSThread *threadA;
@property (nonatomic ,strong) NSThread *threadB;
@property (nonatomic ,strong) NSThread *threadC;
/** 总票数*/
@property (nonatomic ,assign) NSInteger totalSize;
/** 锁对象*/
@property (nonatomic ,strong) NSObject *obj;
@end

@implementation ViewController

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.totalSize = 100;
    self.obj = [[NSObject alloc]init];

    
    //创建线程
    self.threadA = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    [self.threadA setName:@"售票员A"];
    self.threadB = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    [self.threadB setName:@"售票员B"];
    self.threadC = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    [self.threadC setName:@"售票员C"];
    
    //开始执行
    [self.threadA start];
    [self.threadB start];
    [self.threadC start];
    
}

-(void)saleTicket
{
    
    
         while (1) {
             
        //加锁
        //1.锁对象必须是同一个,加多把锁死无效的
        //2.锁对象只要是唯一的都可以,例如,self
        //3.加锁需要注意位置
        //4.不要乱加锁,加锁的前提条件:多个线程访问同一个资源
        //5.加锁是需要耗费性能的
        //对加锁是需要耗费性能的理解:(会延长程序执行完的时间。执行for循环中的一个i就要打开一把锁,假设需要2秒.如果执行到了100,就相当于打开了100把锁,需要延迟200秒才执行完程序。相比于没加锁的情况,多了200秒)
    
             
             
             
             //线程C
             //线程B
             //线程A
             //加锁时,线程A先执行到加锁的地方,发现没有人,就打开锁进入for循环,此时B也执行到了加锁的地方,因为A还在for循环中,所以B只能等待A出来,B才能进去,C亦然.
             //三个子线程按照固定的顺序执行任务,即一定是线程A线程B线程C的顺序
             @synchronized(self) {
             
             //不加锁,一样的票数都将会打印3次,例如打印3张99.这就是三个子线程访问同一个资源的结果。如果加锁的话,就不会造成这种情况.
            NSInteger count = self.totalSize;
            if (count >0) {
               //如果i<100,for循环被处理完的时间非常快,从打印可知需要0秒,可见非常快。
               //如果i<10000000,处理完for循环的时间需要70s或者更长,这种情况就会造成三个线程访问一个资源,就会产生线程的安全问题
                for (NSInteger i = 0; i<100000; i++) {
                    //演示耗时操作
                }
                
                //检查余票的数量,如果发现有余票就卖出去一张
                self.totalSize = count - 1;
                NSLog(@"%@卖出去了一张,还剩下%zd张票",[NSThread currentThread].name,self.totalSize);
            }else
            {
                NSLog(@"不要回家的,或者做飞机回去吧");
                break;
            }

       }
           }
   
}

@end

上述代码解锁的截图

多线程、GCD_第7张图片
26-04.png

将上述代码的@synchronized(self) 去掉,并将i<100改为i<1000000

多线程、GCD_第8张图片
26-05.png
拓展:
  • OC在定义属性时有nonatomic和atomic两种选择
    • atomic:原子属性,为setter方法加锁(默认就是atomic)
    • nonatomic:非原子属性,不会为setter方法加锁

GCD线程间通信

  • 2是从子线程(1所在的代码块到右边的}都是子线程)回到主线程刷新UI,所以对于2来说,2所在的方法就是1的代码块{},因为1整体就是子线程,所以不会死锁

dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //1. 执行耗时的异步操作...
      dispatch_async(dispatch_get_main_queue(), ^{
        // 2.回到主线程,执行UI刷新操作
        });
});

26-13.png

实现线程间通信的两个方法

//a
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

//b
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

a和b的具体代码:

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation ViewController

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //开启一条子线程,执行download方法
    [NSThread detachNewThreadSelector:@selector(download) toTarget:self withObject:nil];
}

-(void)download
{
    //1.确定url
    NSURL *url = [NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.700_0.jpeg"];
    
    //2.下载图片的二进制数据到本地
    NSData *data = [NSData dataWithContentsOfURL:url];
    
    //3.转换格式 二进制数据转换UIimage
    UIImage *image = [UIImage imageWithData:data];
    
    NSLog(@"%@---",[NSThread currentThread]);
    //4.回到主线程刷新UI
    /*
     第一个参数:调用的方法
     第个参数:调用方法需要传递的参数
     第三个参数:是否等待调用的方法执行完毕。如果为NO,那么先打印++++++,再打印showImage中的内容.如果为YES,则相反
     */
    //a和b的区别:a不用写设置图片的具体实现的方法了(系统底层自动实现setImage:)。b必须写设置图片的具体实现的方法(showImage)
    
    //a.从一个子线程跳转至主线程(只能是主线程.方法本身的功能决定的)
    //注意点: setImage:方法是image属性的setter方法,用于设置图片.是系统底层的方法.不能改成别的名字
    
    //self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];//1    让主线程执行系统底层的setImage方法,近而刷新UI
    
    //b.从子线程跳转至另一个线程(可以是主线程,也可以是子线程)。当跳转至子线程时必须得创建runloop,还得运行runloop.
   [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];//2   1和2这时是等价的.因为1方法就是让主线程执行系统底层的setImage方法。2方法需要指定哪个线程执行showImage方法,因为2方法指定了让主线程[NSThread mainThread]执行showImage方法,所以1和2是等价的。
    
    NSLog(@"++++++++++++");
}

//b方法必须写这个方法,a方法不用写
-(void)showImage:(UIImage *)image
{
     NSLog(@"%s",__func__);
    self.imageView.image = image;//显示图片必须设置到imageView的image属性上。
    //打印 当前的什么线程执行了showImage:方法
    NSLog(@"UI----%@",[NSThread currentThread]);
}
@end

打印结果

2016-03-18 12:50:51.956 06-掌握-线程间通信[12163:614287] {number = 2, name = (null)}---
2016-03-18 12:50:51.956 06-掌握-线程间通信[12163:614137] -[ViewController showImage:]
2016-03-18 12:50:51.957 06-掌握-线程间通信[12163:614137] UI----{number = 1, name = main}
2016-03-18 12:50:51.957 06-掌握-线程间通信[12163:614287] ++++++++++++

线程安全


GCD常用函数

栅栏函数

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
多线程、GCD_第9张图片
27-06.png

延迟函数

dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);

多线程、GCD_第10张图片
27-07.png

一次性代码函数

dispatch_once _dispatch_once
多线程、GCD_第11张图片
27-08.png

快速迭代函数

dispatch_apply(size_t iterations, dispatch_queue_t queue,
        void (^block)(size_t));
多线程、GCD_第12张图片
27-09.png

分组函数


#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@property(nonatomic,strong)UIImage *image1;
@property(nonatomic,strong)UIImage *image2;
@end

@implementation ViewController

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

// 分组函数 -- 先将分组内的任务完成,再继续完成别的任务
// 开启线程同时加载两张图片,最后开启线程合成两张图片
- (void)group
{
    // 创建分组函数
    dispatch_group_t group = dispatch_group_create();
    
    // 下载图片,添加到组队列中执行
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSURL *url = [NSURL URLWithString:@"http://seopic.58pic.com/photo/00001/0055.jpg_wh1200.jpg"];
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        self.image1 = [UIImage imageWithData:data];
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSURL *url = [NSURL URLWithString:@"http://att2.citysbs.com/hangzhou/2016/08/09/00/middle_585x780-001133_v2_13171470672693059_712c6c95e8b2b290909a2b581d11e552.jpg"];
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        self.image2 = [UIImage imageWithData:data];
    });
    
    // 合成图片,队列中的任务执行完后,执行这段代码
    dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        // 开启新的图形上下文
        UIGraphicsBeginImageContext(CGSizeMake(300, 150));
        
        // 绘制图片
        [self.image1 drawInRect:CGRectMake(0, 0, 150, 150)];
        [self.image2 drawInRect:CGRectMake(150, 0, 150, 150)];
        
        // 取得上下文中的图片
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        
        // 结束上下文
        UIGraphicsEndImageContext();
        
        // 回到主线程显示图片
        dispatch_async(dispatch_get_main_queue(), ^{
            // 4.将新图片显示出来
            self.imageView.image = image;
        });
    });
}

@end
Snip20160906_11.png

你可能感兴趣的:(多线程、GCD)