非入门级IOSiOS多线程首页投稿(暂停使用,暂停投稿)

OS X 和iOS 中的多线程技术(下)

2017-06-21  本文已影响111人  xiaoyouPrince

OS X 和iOS 中的多线程技术(下)

上篇文章中介绍了 pthread 和 NSThread 两种多线程的方式,本文将继续介绍 GCD 和 NSOperation 这两种方式。。

1.GCD

1.1 什么是GCD

1.2 使用 GCD 有什么优势

1.3 GCD 的使用

1.4 队列的类型

GCD 的队列可以分为 2 大类

注意:同步 、 异步、并发、串行的区分

1.5 GCD 的各种队列的组合

    // 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]);
    });

各种队列的执行效果 :

Snip20170620_1.png

注意:
使用 sync 函数往当前串行队列中添加任务,会卡住当前的串行队列

1.6 GCD 个线程之间通信

通常开辟子线程是为了执行耗时操作。如下载图片的等,使用 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;
        });
    });

1.7 GCD 其他常用函数

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;
   });
});

2. 使用 GCD 实现单例

2.1 单例模式

单例模式是开发过程中长期积累的一种编程习惯。

单例模式作用如下:

单例模式使用场合:

2.2 单例模式的实现(纯代码)

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;
}

2.3 单例模式的实现(宏)

从上面的实现中可以看到,单例的实现方式是一样的,我们可以把它抽取成一个宏来实现,这样更加方便使用.

如下是单例的宏实现,只需在对应的单例类中添加两个对应的宏,就可轻松实现单例。

// .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 这样的内部类对象不能用继承。

3. NSOperation

3.1 NSOperation 简介

NSOperation 是 OS X 和 iOS 开发中最后一种多线程实现方式,它是基于 GCD 的 OC 封装,使用更加面向对象。

3.2 NSOperation 的子类

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,就会异步执行操作

3.3 NSOperationQueue

- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;

3.4 最大并发数

- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

3.5 队列的取消、暂停、恢复

- (void)cancelAllOperations;

提示:也可以调用NSOperation的- (void)cancel方法取消单个操作

- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
- (BOOL)isSuspended;

3.6 操作依赖

[operationB addDependency:operationA]; // 操作B依赖于操作A
Snip20170621_1.png

注意:
不能相互依赖,比如A依赖B,B依赖A

3.7 操作的监听

可以监听一个操作的执行完毕

- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;

3.8 自定义NSOperation

自定义NSOperation的步骤很简单

苹果建议:应该对自定义的 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;
}

3.9 NSOperation 线程间通信

此处依旧以下载并合成一张图片为例,只需开启两个子线程分别下载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;
   }];
}];

4 小结

本文主要讲解了 GCD 和 NSOperation 两种多线程的创建和使用方式。加上上篇文章 共有 pthread 、 NSThread 、 GCD 和 NSOperation 四种多线程方案,实际使用中需要根据项目需求灵活使用。

上一篇 下一篇

猜你喜欢

热点阅读