上篇文章中介绍了 pthread 和 NSThread 两种多线程的方式,本文将继续介绍 GCD 和 NSOperation 这两种方式。。
- dispatch_sync(dispatch_queue_t _Nonnull queue, ^(void)block)
- queue : 队列
- Block : 任务
- dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block)
GCD 的队列可以分为 2 大类
注意:同步 、 异步、并发、串行的区分
和
- 同步
主要影响: 能不能开启新的线程
- 异步
和
- 并发
主要影响: 任务的执行方式
- 串行
- // 1.获得全局的并发队列
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- // 2.将任务加入队列
- dispatch_async(queue, ^{
- for (NSInteger i = 0; i<10; i++) {
- NSLog(@"1-----%@", [NSThread currentThread]);
- }
- });
- dispatch_async(queue, ^{
- for (NSInteger i = 0; i<10; i++) {
- NSLog(@"2-----%@", [NSThread currentThread]);
- }
- });
- // 1.获得全局的并发队列
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- // 2.将任务加入队列
- dispatch_sync(queue, ^{
- NSLog(@"1-----%@", [NSThread currentThread]);
- });
- dispatch_sync(queue, ^{
- NSLog(@"2-----%@", [NSThread currentThread]);
- });
- // 1.创建串行队列
- dispatch_queue_t queue = dispatch_queue_create("com.coder.queue", DISPATCH_QUEUE_SERIAL);
- // dispatch_queue_t queue = dispatch_queue_create("com.coder.queue", NULL);
- // 2.将任务加入队列
- dispatch_async(queue, ^{
- NSLog(@"1-----%@", [NSThread currentThread]);
- });
- dispatch_async(queue, ^{
- NSLog(@"2-----%@", [NSThread currentThread]);
- });
- // 1.创建串行队列
- dispatch_queue_t queue = dispatch_queue_create("com.coder.queue", DISPATCH_QUEUE_SERIAL);
- // 2.将任务加入队列
- dispatch_sync(queue, ^{
- NSLog(@"1-----%@", [NSThread currentThread]);
- });
- dispatch_sync(queue, ^{
- NSLog(@"2-----%@", [NSThread currentThread]);
- });
- // 1.获得主队列
- dispatch_queue_t queue = dispatch_get_main_queue();
- // 2.将任务加入队列
- dispatch_sync(queue, ^{
- NSLog(@"1-----%@", [NSThread currentThread]);
- });
- dispatch_sync(queue, ^{
- NSLog(@"2-----%@", [NSThread currentThread]);
- });
各种队列的执行效果 :
注意: 使用 sync 函数往当前串行队列中添加任务,会卡住当前的串行队列
通常开辟子线程是为了执行耗时操作。如下载图片的等,使用 GCD 进行线程间通信非常方便,示例代码如下:
- // 子线程中下载网络图片 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- // 图片的网络路径
- NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
- // 加载图片
- NSData *data = [NSData dataWithContentsOfURL:url];
- // 生成图片
- UIImage *image = [UIImage imageWithData:data];
- // 回到主线程
- dispatch_async(dispatch_get_main_queue(), ^{
- self.imageView.image = image;
- });
- });
- dispatch_barrier_sync(dispatch_queue_t _Nonnull queue, ^(void)block)
- // 此函数起一个阻隔任务执行的作用, 它前面的任务执行完之后它才执行,等它执行完后面的任务才能执行
- // GCD 延迟执行
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- NSLog(@"run-----");
- });
- // iOS 中其他方式的延迟执行还有
- [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
- 和定时器
- [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
- ---------------- run 方法 -----------------
- - (void)run
- {
- NSLog(@"run-----");
- }
- 一次性函数在整个程序运行中只会执行一次
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSLog(@"------run-----");
- // 内部代码默认是线程安全的
- });
- 快速迭代行数,实际上在全局队列中遍历子线程执行任务,用于显著提高执行效率。
- 案例:【文件假拷贝】,【App Store 所有App同时更新】让每个任务都开子线程去并发执行会充分利用CPU,提高效率。
- // 本示例代码是将 From 文件夹下的内容拷贝到 TO 文件夹下
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- NSString *from = @"/Users/xiaoyou/Desktop/From";
- NSString *to = @"/Users/xiaoyou/Desktop/To";
- NSFileManager *mgr = [NSFileManager defaultManager];
- NSArray *subpaths = [mgr subpathsAtPath:from];
- dispatch_apply(subpaths.count, queue, ^(size_t index) {
- NSString *subpath = subpaths[index];
- NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
- NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
- // 剪切
- [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
- NSLog(@"%@---%@", [NSThread currentThread], subpath);
- });
- 队列组中的任务执行完,组会受到一个通知,然后执行最终的操作
- // 1. 创建全局队列
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- // 2. 创建一个队列组
- dispatch_group_t group = dispatch_group_create();
- // 任务 1.下载图片1
- dispatch_group_async(group, queue, ^{
- // 图片的网络路径
- NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
- // 加载图片
- NSData *data = [NSData dataWithContentsOfURL:url];
- // 生成图片
- self.image1 = [UIImage imageWithData:data];
- });
- // 任务 2.下载图片2
- dispatch_group_async(group, queue, ^{
- // 图片的网络路径
- NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
- // 加载图片
- NSData *data = [NSData dataWithContentsOfURL:url];
- // 生成图片
- self.image2 = [UIImage imageWithData:data];
- });
- // 任务 3.将图片1、图片2合成一张新的图片
- dispatch_group_notify(group, queue, ^{
- // 开启新的图形上下文
- UIGraphicsBeginImageContext(CGSizeMake(100, 100));
- // 绘制图片
- [self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
- [self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];
- // 取得上下文中的图片
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- // 结束上下文
- UIGraphicsEndImageContext();
- // 回到主线程显示图片
- dispatch_async(dispatch_get_main_queue(), ^{
- // 4.将新图片显示出来
- self.imageView.image = image;
- });
- });
单例模式是开发过程中长期积累的一种编程习惯。
单例模式作用如下:
单例模式使用场合:
- static id _instance;
- + (instancetype)allocWithZone:(struct _NSZone *)zone
- {
- // 使用GCD一次性函数,保证线程安全
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- _instance = [self allocWithZone:zone];
- });
- return _instance;
- }
- + (instancetype)shareInstance{
- // 使用GCD一次性函数,保证线程安全
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- _instance = [[self alloc] init];
- });
- return _instance;
- }
- + (id)copyWithZone:(struct _NSZone *)zone
- {
- return _instance;
- }
从上面的实现中可以看到,单例的实现方式是一样的,我们可以把它抽取成一个宏来实现,这样更加方便使用.
如下是单例的宏实现,只需在对应的单例类中添加两个对应的宏,就可轻松实现单例。
- // .h文件
- #define XMGSingletonH(name) + (instancetype)shared##name;
- // .m文件
- #define XMGSingletonM(name) \
- static id _instance; \
- \
- + (instancetype)allocWithZone:(struct _NSZone *)zone \
- { \
- static dispatch_once_t onceToken; \
- dispatch_once(&onceToken, ^{ \
- _instance = [super allocWithZone:zone]; \
- }); \
- return _instance; \
- } \
- \
- + (instancetype)shared##name \
- { \
- static dispatch_once_t onceToken; \
- dispatch_once(&onceToken, ^{ \
- _instance = [[self alloc] init]; \
- }); \
- return _instance; \
- } \
- \
- - (id)copyWithZone:(NSZone *)zone \
- { \
- return _instance; \
- }
思考:为什么不使用继承?
- 继承:看似可行,实际会有问题,程序中的GCD一次性代码只会执行一次,当第一次有子类 A 调用之后,再有子类 B 调用返回的直接是第一次调用 A 的实例,无法返回正确类型 B 单例
- 也就是说如果有 static 这样的内部类对象不能用继承。
NSOperation 是 OS X 和 iOS 开发中最后一种多线程实现方式,它是基于 GCD 的 OC 封装,使用更加面向对象。
NSInvocationOperation
- - (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
- - (void)start;
- 一旦执行操作,就会调用target的sel方法
注意
NSBlockOperation
- + (id)blockOperationWithBlock:(void (^)(void))block;
- - (void)addExecutionBlock:(void (^)(void))block;
注意:
只要 NSBlockOperation 封装的操作数 > 1,就会异步执行操作
- - (void)addOperation:(NSOperation *)op;
- - (void)addOperationWithBlock:(void (^)(void))block;
- - (NSInteger)maxConcurrentOperationCount;
- - (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
- - (void)cancelAllOperations;
提示:也可以调用 NSOperation 的 - (void)cancel 方法取消单个操作
- - (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
- - (BOOL)isSuspended;
- [operationB addDependency:operationA]; // 操作B依赖于操作A
注意: 不能相互依赖,比如 A 依赖 B,B 依赖 A
可以监听一个操作的执行完毕
- - (void (^)(void))completionBlock;
- - (void)setCompletionBlock:(void (^)(void))block;
自定义 NSOperation 的步骤很简单
方法,在里面实现想执行的任务
- - (void)main
方法的注意点
- - (void)main
方法检测操作是否被取消,对取消做出响应
- - (BOOL)isCancelled
- 苹果建议:应该对自定义的 Operation 中的执行完一个耗时操作,应该手动调用一下 isCancelled 方法查看是不是已经取消并做对应的操作
- /**
- * 需要执行的任务
- */
- - (void)main
- {
- for (NSInteger i = 0; i<1000; i++) {
- NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
- }
- if (self.isCancelled) return;
- for (NSInteger i = 0; i<1000; i++) {
- NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
- }
- if (self.isCancelled) return;
- for (NSInteger i = 0; i<1000; i++) {
- NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
- }
- if (self.isCancelled) return;
- }
此处依旧以下载并合成一张图片为例,只需开启两个子线程分别下载 image,第三个线程为合并操作, 然后添加线程依赖。并放到队列中
- NSOperationQueue *queue = [[NSOperationQueue alloc] init];
- __block UIImage *image1 = nil;
- // 下载图片1
- NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
- // 图片的网络路径
- NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
- // 加载图片
- NSData *data = [NSData dataWithContentsOfURL:url];
- // 生成图片
- image1 = [UIImage imageWithData:data];
- }];
- __block UIImage *image2 = nil;
- // 下载图片2
- NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
- // 图片的网络路径
- NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
- // 加载图片
- NSData *data = [NSData dataWithContentsOfURL:url];
- // 生成图片
- image2 = [UIImage imageWithData:data];
- }];
- // 合成图片
- NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
- // 开启新的图形上下文
- UIGraphicsBeginImageContext(CGSizeMake(100, 100));
- // 绘制图片
- [image1 drawInRect:CGRectMake(0, 0, 50, 100)];
- image1 = nil;
- [image2 drawInRect:CGRectMake(50, 0, 50, 100)];
- image2 = nil;
- // 取得上下文中的图片
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- // 结束上下文
- UIGraphicsEndImageContext();
- // 回到主线程
- [[NSOperationQueue mainQueue] addOperationWithBlock:^{
- self.imageView.image = image;
- }];
- }];
- [combine addDependency:download1];
- [combine addDependency:download2];
- [queue addOperation:download1];
- [queue addOperation:download2];
- [queue addOperation:combine];
简单的,只有下载图片然后放到主线程展示的线程通信如下:
- [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
- // 图片的网络路径
- NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
- // 加载图片
- NSData *data = [NSData dataWithContentsOfURL:url];
- // 生成图片
- UIImage *image = [UIImage imageWithData:data];
- // 回到主线程
- [[NSOperationQueue mainQueue] addOperationWithBlock:^{
- self.imageView.image = image;
- }];
- }];
本文主要讲解了 GCD 和 NSOperation 两种多线程的创建和使用方式。加上上篇文章 共有 pthread 、 NSThread 、 GCD 和 NSOperation 四种多线程方案,实际使用中需要根据项目需求灵活使用。
来源: http://www.cnblogs.com/xiaoyouPrince/p/7060900.html