一. 基本概念
1. 进程
进程是指在系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内
1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程),线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行。
2. 进程中的线程运行状态
1> 单线程: 串行执行任务
1个线程中任务的执行是串行的,如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务。也就是说,在同一时间内,1个线程只能执行1个任务。
2> 多线程: 并行执行任务
1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务。
注:多线程并发执行任务的原理
在同一时间里,CPU只能处理1条线程,只有1条线程在工作(执行)。多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
3. 多线程的优缺点
优点
1)能适当提高程序的执行效率。
2)能适当提高资源利用率(CPU、内存利用率)
缺点
1)开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能。
2)线程越多,CPU在调度线程上的开销就越大。
3)程序设计更加复杂:比如线程之间的通信、多线程的数据共享
4. 多线程在iOS开发中应用
4.1 主线程
1)一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”。
2)作用。刷新显示UI,处理UI事件。
4.2 使用注意
1)不要将耗时操作放到主线程中去处理,会卡住线程。
2)和UI相关的刷新操作必须放到主线程中进行处理。
5. iOS中多线程的实现方法
5.1 pthread
特点:
1)一套通用的多线程API
2)适用于UnixLinuxWindows等系统
3)跨平台可移植
4)使用难度大
使用语言:c语言
使用频率:几乎不用
线程生命周期:由程序员进行管理
5.2 NSThread
特点:
1)使用更加面向对象
2)简单易用,可直接操作线程对象
使用语言:OC语言
使用频率:偶尔使用
线程生命周期:由程序员进行管理
5.3 GCD
特点:
1)旨在替代NSThread等线程技术
2)充分利用设备的多核(自动)
使用语言:C语言
使用频率:经常使用
线程生命周期:自动管理
5.4 NSOperation
特点:
1)基于GCD(底层是GCD)
2)比GCD多了一些更简单实用的功能
3)使用更加面向对象
使用语言:OC语言
使用频率:经常使用
线程生命周期:自动管理
二. 线程详解
1. PThread创建线程
第一个参数:线程对象地址
第二个参数:线程属性
第三个参数:指向函数的指针
第四个参数:传递给该函数的参数
// 1.PThread线程创建- (void)pthread{pthread_t pthread;// 1.用pthead创建线程pthread_create(&pthread, nil, run, nil);}void *run(void *param){NSLog(@"%s", __func__);NSLog(@"%@", [NSThread currentThread]);return nil;}
2.NSThread创建线程
1> 通过alloc创建
// NSThread线程的创建 : 方法一/*通过alloc创建可以拿到线程对象,需要手动启动线程线程执行完之后,系统自动销毁*/- (void)thread1{// 方法一 :NSThread *thread = [[LDThread alloc] initWithTarget:self selector:@selector(running) object:nil];thread.name = @"thread";[thread start];}
2> 分离出一条子线程
// NSThread线程的创建 : 方法二 : 分离出一条子线程- (void)thread2{// 方法二 :[NSThread detachNewThreadSelector:@selector(running) toTarget:self withObject:nil];}
3> 创建一条后台子线程
// NSThread线程的创建 : 方法三 : 创建一条后台子线程- (void)thread3{[self performSelectorInBackground:@selector(running) withObject:nil];}
4> NSThread线程安全问题
对于线程访问公有的资源会引起数据混乱
解决方案:加锁, 当前线程访问时,将访问资源加锁,其他线程在外等待,等待当前线程访问完之后再访问之后再加锁
锁必须是一把是公有的,建议直接使用当前控制器
案例:卖票,有3条线程相当于售票窗口,共同卖100张票,直到票卖完为止,如按普通开启3条线程,如当前票是100张,售票窗口A先去查看当前余票是100张,它拿出卖出一张,再把剩余99张票数放到数据库,而当A取出票的同时B也在取看到的余票也是100,卖出一张把自己计算出的剩余票99张放到数据库中把A计算的余票覆盖掉,这样卖出了2张票,而余票还是99张就造成了数据的混乱
解决方案就是:将访问公共资源时加锁
代码如下:
创建3条线程
- (void)viewDidLoad {[super viewDidLoad];self.ticketCount = 100;// 线程1NSThread *threadA = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];threadA.name = @"售票员A";self.threadA = threadA;[threadA start];// 线程2NSThread *threadB = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];threadB.name = @"售票员B";self.threadB = threadB;[threadB start];NSThread *threadC = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];threadC.name = @"售票员C";self.threadC = threadC;[threadC start];}
加锁方案 用 @synchronized(self){}将访问公共的资源包装起来
- (void)sellTicket{while (1) {// 对于线程访问公有的资源为防止数据混乱// 解决方案:加锁, 当前线程访问时,将访问资源加锁,其他线程在外等待,等待当前线程访问完之后再访问之后再加锁// 锁必须是一把是公有的,建议直接使用当前控制器@synchronized(self) {NSInteger count = self.ticketCount;if (count > 0) {self.ticketCount = count - 1;[NSThread sleepForTimeInterval:0.1];NSLog(@"%@卖了一张票, 还剩%zd张票, 线程%@", [NSThread currentThread].name, self.ticketCount, [NSThread currentThread]);} else {NSLog(@"%@已卖完票", [NSThread currentThread].name);break;// [NSThread exit];}}}}
3. NSThread线程间的通信(下载图片)
1> 点击按钮开启一条线程
- (IBAction)btnClick {// 开启一条线程[NSThread detachNewThreadSelector:@selector(download) toTarget:self withObject:nil];}
2> 实现线程方法
下载图片是耗时任务,放到子线程中执行,刷新图片放在主线程中执行
// 下载图片- (void)download{// NSLog(@"%@", [NSDate date]);// 1.创建URL路径NSURL *url = [NSURL URLWithString:@"http://src.house.sina.com.cn/imp/imp/deal/3f/e1/d/2c0401b364ae649b34e7d6999e4_p24_mk24_wm200_s500X0.png"];// NSLog(@"%@", [NSDate date]);// 执行开始时间CFTimeInterval start = CFAbsoluteTimeGetCurrent();// 2.加载二进制NSData *data = [NSData dataWithContentsOfURL:url];// 结束执行时间CFTimeInterval end = CFAbsoluteTimeGetCurrent();NSLog(@"消耗 %f s", end - start);// NSLog(@"%@", [NSDate date]);// 3.将二进制转为图片UIImage *image = [UIImage imageWithData:data];// 4.刷新imageView(主进程)[self performSelector:@selector(reloadImageV:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];}- (void)reloadImageV:(UIImage *)image{self.imageView.image = image;}
3> Xcode 7 需要设置允许加载http请求,修改info.plist文件,添加红色标记的键值对
4> 计算代码块执行时间
第一种方法
NSDate *start = [NSDate date];
//2.根据url地址下载图片数据到本地(二进制数据)
NSData *data = [NSData dataWithContentsOfURL:url];
NSDate *end = [NSDate date];
NSLog(@"第二步操作花费的时间为%f",[end timeIntervalSinceDate:start]);
第二种方法
CFTimeInterval start = CFAbsoluteTimeGetCurrent();
NSData *data = [NSData dataWithContentsOfURL:url];
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"第二步操作花费的时间为%f",end - start);
4.GCD
1> 基本知识
队列和任务
同步函数和异步函数
2> 基本使用
01 异步函数+:开启一条线程,串行执行任务
03 同步函数+并发队并发队列:开启多条线程,并发执行任务
02 异步函数+串行队列列:不开线程,串行执行任务
04 同步函数+串行队列:不开线程,串行执行任务
05 异步函数+主队列:不开线程,在主线程中串行执行任务
06 同步函数+主队列:不开线程,串行执行任务(注意死锁发生)
07 注意同步函数和异步函数在执行顺序上面的差异
3> 具体代码实现
1.GCD 异步函数并行队列(系统提供了一个获取全局并行队列)
- (void)asyncConcurrent{// 异步函数并行队列: 会开启多条线程, 无序执行// 1.并行队列/*DISPATCH_QUEUE_CONCURRENT : 并行队列DISPATCH_QUEUE_SERIAL : 串行队列第一参数: 队列的标识名, 主要用于程序奔溃时,根据奔溃log中的标识名快速定位到队列中的哪个代码块导致第二个参数: 队列的属性, 是串行队列还是并行队列*/// dispatch_queue_t queue = dispatch_queue_create("com.xfsrn.www", DISPATCH_QUEUE_CONCURRENT);// 并行全局队列/*#define DISPATCH_QUEUE_PRIORITY_HIGH 2#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0#define DISPATCH_QUEUE_PRIORITY_LOW (-2)#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN第一个参数: 队列优先级*/dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);// 2.异步并行队列for (int i = 0; i < 50; ++i) {dispatch_async(queue, ^{NSLog(@"%d - %@", i, [NSThread currentThread]);});}}
- 2.GCD 异步函数串行队列
- (void)asyncSerial{// 异步函数串行队列: 只会创建一条线程, 有序执行// 1.串行队列dispatch_queue_t queue = dispatch_queue_create("com.xfsrn.www", DISPATCH_QUEUE_SERIAL);// 2.异步函数dispatch_async(queue, ^{for (int i = 0; i < 10; ++i) {NSLog(@"%d - %@", i, [NSThread currentThread]);}});}
3.GCD 同步并行队列
- (void)syncConcurrent{// 同步并行队列 : 不会创建线程且在当前中执行// 1.创建并行队列dispatch_queue_t queue = dispatch_queue_create("com.xfsrn.www", DISPATCH_QUEUE_CONCURRENT);// 2.创建同步函数dispatch_sync(queue, ^{for (int i = 0; i < 10; ++i) {NSLog(@"%i - %@", i, [NSThread currentThread]);}});}
4.GCD 同步串行队列
// 4.GCD 同步串行队列- (void)syncSerial{// 同步并行队列 : 不会创建线程且在当前中执行// 1.创建串行队列dispatch_queue_t queue = dispatch_queue_create("com.xfsrn.com", DISPATCH_QUEUE_SERIAL);// 2.创建同步函数dispatch_sync(queue, ^{for (int i = 0; i < 10; ++i) {NSLog(@"%@", [NSThread currentThread]);}});}
5.GCD 异步函数主队列
// 5.GCD 异步函数主队列- (void)asyncMain{// 异步函数主队列 : 串行执行// 1.获取主队列dispatch_queue_t queue = dispatch_get_main_queue();// 2.创建异步函数dispatch_async(queue, ^{for (int i = 0; i < 10; ++i) {NSLog(@"%i - %@", i, [NSThread currentThread]);}});}
6.GCD 同步函数主队列
// 6.GCD 同步函数主队列- (void)syncMain{// 同步函数主队列 : 会照成死锁现象// 原因 : 同步函数是串行执行,且必须执行,队列是主函数,而要执行的任务也在主函数,这样就会造成死锁现象// 1.获取主队列dispatch_queue_t queue = dispatch_get_main_queue();NSLog(@"----");// 2.创建同步函数dispatch_sync(queue, ^{for (int i = 0; i < 10; ++i) {NSLog(@"%i - %@", i, [NSThread currentThread]);}});}
4> GCD线程间的通信
// 点击按钮- (IBAction)btnClick {// 创建异步函数并发队列dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 获取url路径NSURL *url = [NSURL URLWithString:@"http://www.chinanews.com/cr/2014/0108/1576296051.jpg"];// 加载二进制NSData *data = [NSData dataWithContentsOfURL:url];// data转成imageUIImage *image = [UIImage imageWithData:data];// 异步函数主函数 刷新UIdispatch_async(dispatch_get_main_queue(), ^{self.imageView.image = image;});});}
5> GCD其他函数的使用
1.延迟执行
// 1.延迟执行- (void)delay{NSLog(@"------start-------");// 方法1 : NSObject 方法// [self performSelector:@selector(run) withObject:self afterDelay:2.0];// 方法2 : 定时器// [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];// 方法3 : GDC延迟执行dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{[self run];});}
2.栅栏函数 : 栅栏函数不能用全局并行队列,否则栅栏函数不起作用
// 2.栅栏函数 : 栅栏函数不能用全局并行队列,否则栅栏函数不起作用- (void)barrier{dispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i = 0; i < 10; ++i) {NSLog(@"01----%@", [NSThread currentThread]);}});dispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i = 0; i < 10; ++i) {NSLog(@"02----%@", [NSThread currentThread]);}});// 栅栏函数,保证上面的函数执行完毕后再执行栅栏函数后面的函数(不能用全局并行队列)dispatch_barrier_async(dispatch_queue_create("com.xfsrn.www", DISPATCH_QUEUE_CONCURRENT), ^{NSLog(@"**********************************");});dispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i = 0; i < 10; ++i) {NSLog(@"03----%@", [NSThread currentThread]);}});dispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i = 0; i < 10; ++i) {NSLog(@"04----%@", [NSThread currentThread]);}});}
3.一次性函数
// 3.一次性函数- (void)once{NSLog(@"xxxx");static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{// 此代码块只会执行一次NSLog(@"once");});}
4.迭代函数
// 4.迭代函数- (void)iterative{// for循环迭代 : 有序执行for (int i = 0; i < 10; ++i) {NSLog(@"for = %i - %@", i, [NSThread currentThread]);}// GCD 迭代 : 无序执行/*第一参数: 迭代次数第二个参数: 队列类型第三个参数: 索引*/dispatch_apply(10, dispatch_queue_create("com.xfsrn.www", DISPATCH_QUEUE_CONCURRENT), ^(size_t index) {NSLog(@"apply = %zd - %@", index, [NSThread currentThread]);});}
5.迭代的应用(剪切文件)
// 5.迭代的应用(剪切文件)- (void)iterativeTest{// 1.创建文件管理器NSFileManager *mgr = [NSFileManager defaultManager];// 2.源和目标文件夹路径NSString *fromPath = @"/Users/admin/Desktop/from";NSString *toPath = @"/Users/admin/Desktop/to";// 3.获取源文件夹下的所有文件NSArray *fileArray = [mgr subpathsAtPath:fromPath];NSLog(@"%@", fileArray);// 4.for循环拼接文件路径剪切文件for (NSString *fileName in fileArray) {// 拼接源文件路径NSString *fromFile = [fromPath stringByAppendingPathComponent:fileName];// 拼接目的文件路径NSString *toFile = [toPath stringByAppendingPathComponent:fileName];[mgr moveItemAtPath:fromFile toPath:toFile error:nil];}// 5.GCD 迭代剪切文件dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{dispatch_apply(fileArray.count, dispatch_get_global_queue(0, 0), ^(size_t index) {// 拼接源文件路径NSString *fromFile = [fromPath stringByAppendingPathComponent:fileArray[index]];// 拼接目的文件路径NSString *toFile = [toPath stringByAppendingPathComponent:fileArray[index]];[mgr moveItemAtPath:toFile toPath:fromFile error:nil];});});}
6> 各种队列的执行效果
| 并发队列 | 手动创建的串行队列 | 主队列 | |
同步(sync) | 没有开启新线程 串行执行任务 | 没有开启新线程 串行执行任务 | 没有开启新线程 串行执行任务 |
异步(async) | 有开启新线程 并发执行任务 | 有开启新线程 串行执行任务 | 没有开启新线程 串行执行任务 |
注意
p使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列
7> 队列组
创建队列
// 1.创建队列dispatch_queue_t queue = dispatch_queue_create("com.xfsrn.www", DISPATCH_QUEUE_CONCURRENT);
队列组的创建
// 2.创建组dispatch_group_t group = dispatch_group_create();
创建异步函数下载图片1
dispatch_group_async(group, queue, ^{// 3.1 加载URlNSURL *url = [NSURL URLWithString:@"http://tb2.bdstatic.com/tb/static-puser/widget/celebrity/img/single_member_100_0b51e9e.png"];// 3.2 加载二进制NSData *data = [NSData dataWithContentsOfURL:url];// 3.3 二进制转为图片self.image1 = [UIImage imageWithData:data];});
创建异步函数下载图片2
// 4.创建异步函数下载图片2dispatch_group_async(group, queue, ^{// 3.1 加载URlNSURL *url = [NSURL URLWithString:@"http://tb2.bdstatic.com/tb/static-puser/widget/celebrity/img/single_member_100_0b51e9e.png"];// 3.2 加载二进制NSData *data = [NSData dataWithContentsOfURL:url];// 3.3 二进制转为图片self.image2 = [UIImage imageWithData:data];});
合并图片 (dispatch_group_notify 方法会等待group组中的任务执行完之后再执行此线程中的任务)
// 5.合并图片 (dispatch_group_notify 方法会等待group组中的任务执行完之后再执行此线程中的任务)dispatch_group_notify(group, queue, ^{// 5.1 创建上下文UIGraphicsBeginImageContext(CGSizeMake(200, 400));// 5.2 绘制图片1[self.image1 drawInRect:CGRectMake(0, 0, 200, 200)];// 5.3 绘制图片2[self.image2 drawInRect:CGRectMake(0, 200, 200, 200)];// 5.4 从上下文种获取图片UIImage *image = UIGraphicsGetImageFromCurrentImageContext();// 5.5 关闭上下文,销毁图片1和图片2UIGraphicsEndImageContext();self.image1 = nil;self.image2 = nil;// 5.6 刷新图片dispatch_async(dispatch_get_main_queue(), ^{self.imageView.image = image;});});
5. NSOperation
NSOperation并不具备创建线程的能力,但它的两个子类可以创建线程
子类:
NSInvocationOperation
NSBlockOperation
队列:
NSOpreationQueue
1. NSInvocationOperation的创建,在主线程中执行任务需要手动启动
NSInvocationOperation *p1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(running) object:nil];[p1 start];
// 2.NSBlockOperation : 直接创建的任务在主线程中执行, 追加的任务在子线程中执行, 需手动开启startNSBlockOperation *p2 = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"+++++++++%@", [NSThread currentThread]);}];// 追加任务[p2 addExecutionBlock:^{NSLog(@"1++++++++++%@", [NSThread currentThread]);}];[p2 start];
3. 操作和队列的应用
. 创建队列
. 创建操作
. 将操作添加到队列中
// 创建队列NSOperationQueue *queue = [[NSOperationQueue alloc] init];// 创建操作NSBlockOperation *p1 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 10000; ++i) {NSLog(@"4----%d-----%@", i, [NSThread currentThread]);}}];// 将操作添加到队列中// 向队列中添加任务[queue addOperation:p1];
4. 队列分为主队列和非主队列
主队列中的操作都在主线中执行
NSOperationQueue *queue = [NSOperationQueue mainQueue];
非主队列,通过alloc创建,具备并发和串行创建子线程能力,默认是并发,
NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
设置并发数,设置为1为串行
// 设置并发数, 如果值设置为1即为串行执行queue.maxConcurrentOperationCount = 2;
队列暂停,YES为暂停,正在执行的任务无法暂停,只能暂停未执行的操作
self.queue.suspended = YES;
队列取消,正在执行的任务无法取消,只能取消未执行的操作(取消之后无法再重新执行)
// 取消队列 任务[self.queue cancelAllOperations];
5. 自定义NSOpreation
创建一个类继承于NSOpreation,用这个类创建NSOpreation对象,添加到队列中,将任务封装在NSOpreation的main对象函数中,就可以在子线程中执行
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{LDOperation *q = [[LDOperation alloc] init];NSOperationQueue *queue = [[NSOperationQueue alloc] init];[queue addOperation:q];}
#import "LDOperation.h"@implementation LDOperation- (void)main{NSLog(@"---------main--------%@", [NSThread currentThread]);}@end
6. 设置任务的依赖关系
创建多个操作p1/p2/p3/p4, 可设定p2操作在p4操作完成之后再执行
// 设置依赖关系[p2 addDependency:p4];
7. NSOpreadtion线程间的通信
- (void)downloadOneImage{// 创建队列NSOperationQueue *queue = [[NSOperationQueue alloc] init];// 封装任务[queue addOperationWithBlock:^{// 获取图片URLNSURL *url = [NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018212924_ZXZLs.thumb.700_0.jpeg"];// 加载二进制NSData *data = [NSData dataWithContentsOfURL:url];// 将二进制转为图片UIImage *image = [UIImage imageWithData:data];// 刷新图片[[NSOperationQueue mainQueue] addOperationWithBlock:^{NSLog(@"%@", [NSThread currentThread]);self.imageView.image = image;}];}];}
8. 在NSOpreation创建的线程中合并图片
- (void)downloadTwoImage1{// 创建队列NSOperationQueue *queue = [[NSOperationQueue alloc] init];// 封装任务[queue addOperationWithBlock:^{/* ------------获取图片1---------------- */// 获取图片1URLNSURL *url = [NSURL URLWithString:@"http://if.topit.me/f/53/3f/1104263230b173f53fl.jpg"];// 加载二进制NSData *data = [NSData dataWithContentsOfURL:url];// 将二进制转为图片UIImage *image1 = [UIImage imageWithData:data];/* ------------获取图片2---------------- */// 获取图片2url = [NSURL URLWithString:@"http://imgsrc.baidu.com/forum/w%3D580/sign=2daebc56d8b44aed594ebeec831d876a/59ee3d6d55fbb2fbe8a0e70e4d4a20a44623dc27.jpg"];data = [NSData dataWithContentsOfURL:url];UIImage *image2 = [UIImage imageWithData:data];/* ------------合并图片---------------- */// 开启上下文UIGraphicsBeginImageContext(CGSizeMake(300, 150));// 绘制图片1[image1 drawInRect:CGRectMake(0, 0, 150, 150)];// 绘制图片2[image2 drawInRect:CGRectMake(150, 0, 150, 150)];// 获取上下文中图片UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();// 刷新图片[[NSOperationQueue mainQueue] addOperationWithBlock:^{NSLog(@"%@", [NSThread currentThread]);self.imageView.image = image;}];}];}
通过依赖关系
- (void)downloadTwoImage2{// 创建队列NSOperationQueue *queue = [[NSOperationQueue alloc] init];NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];// 封装任务NSBlockOperation *p1 = [NSBlockOperation blockOperationWithBlock:^{/* ------------获取图片1---------------- */// 获取图片1URLNSURL *url = [NSURL URLWithString:@"http://if.topit.me/f/53/3f/1104263230b173f53fl.jpg"];// 加载二进制NSData *data = [NSData dataWithContentsOfURL:url];// 将二进制转为图片UIImage *image1 = [UIImage imageWithData:data];self.image1 = image1;}];NSBlockOperation *p2 = [NSBlockOperation blockOperationWithBlock:^{/* ------------获取图片2---------------- */// 获取图片2NSURL *url = [NSURL URLWithString:@"http://imgsrc.baidu.com/forum/w%3D580/sign=2daebc56d8b44aed594ebeec831d876a/59ee3d6d55fbb2fbe8a0e70e4d4a20a44623dc27.jpg"];NSData *data = [NSData dataWithContentsOfURL:url];UIImage *image2 = [UIImage imageWithData:data];self.image2 = image2;}];NSBlockOperation *p3 = [NSBlockOperation blockOperationWithBlock:^{/* ------------合并图片---------------- */// 开启上下文UIGraphicsBeginImageContext(CGSizeMake(300, 150));// 绘制图片1[self.image1 drawInRect:CGRectMake(0, 0, 150, 150)];// 绘制图片2[self.image2 drawInRect:CGRectMake(150, 0, 150, 150)];// 获取上下文中图片UIImage *image = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();self.image1 = nil;self.image2 = nil;self.imageView.image = image;}];// 设置执行顺序[p3 addDependency:p1];[p3 addDependency:p2];// 任务添加到队列[queue addOperation:p1];[queue addOperation:p2];[mainQueue addOperation:p3];}