iOS开发网络多线程之多线程
一. 基本概念
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)适用于Unix\Linux\Windows等系统
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;
// 线程1
NSThread *threadA = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
threadA.name = @"售票员A";
self.threadA = threadA;
[threadA start];
// 线程2
NSThread *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转成image
UIImage *image = [UIImage imageWithData:data];
// 异步函数主函数 刷新UI
dispatch_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 加载URl
NSURL *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.创建异步函数下载图片2
dispatch_group_async(group, queue, ^{
// 3.1 加载URl
NSURL *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和图片2
UIGraphicsEndImageContext();
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 : 直接创建的任务在主线程中执行, 追加的任务在子线程中执行, 需手动开启start
NSBlockOperation *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:^{
// 获取图片URL
NSURL *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---------------- */
// 获取图片1URL
NSURL *url = [NSURL URLWithString:@"http://if.topit.me/f/53/3f/1104263230b173f53fl.jpg"];
// 加载二进制
NSData *data = [NSData dataWithContentsOfURL:url];
// 将二进制转为图片
UIImage *image1 = [UIImage imageWithData:data];
/* ------------获取图片2---------------- */
// 获取图片2
url = [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---------------- */
// 获取图片1URL
NSURL *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---------------- */
// 获取图片2
NSURL *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];
}