×

多线程知识总结

96
攻城狮GG
2017.06.04 23:21* 字数 6928

NSThread

第一种:通过NSThread的对象方法

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longOperation:) object:@"THREAD"];

[thread start]

第二种:通过NSThread的类方法

[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"DETACH"];

第三种:通过NSObject的方法

[self performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];

阻塞

当满足某个预定条件时,可以使用休眠或锁阻塞线程执行

sleepForTimeInterval:休眠指定时长

sleepUntilDate:休眠到指定日期

@synchronized(self):互斥锁

控制线程状态的方法

启动

[_thread start]

线程进入就绪状态,当线程执行完毕后自动进入死亡状态

休眠

方法执行过程中,符合某一条件时,可以利用 sleep 方法让线程进入 阻塞 状态

sleepForTimeInterval 从现在起睡多少秒

sleepUntilDate 从现在起睡到指定的日期

死亡

[NSThread exit]

一旦强行终止线程,后续的所有代码都不会被执行

注意:在终止线程之前,应该注意释放之前分配的对象!

取消

[_thread cancel]

并不会直接取消线程

只是给线程对象添加 isCancelled 标记

需要在线程内部的关键代码位置,增加判断,决定是否取消当前线程

线程的属性

常用属性

name - 线程名称

设置线程名称可以当线程执行的方法内部出现异常时,记录异常和当前线程

stackSize - 栈区大小

默认情况下,无论是主线程还是子线程,栈区大小都是 512K

栈区大小可以设置 [NSThread currentThread].stackSize = 1024 * 1024;

必须是 4KB 的倍数

isMainThread - 是否主线程

threadPriority - 线程优先级

优先级,是一个浮点数,取值范围从 0~1.0

1.0 表示优先级最高

0.0 表示优先级最低

默认优先级是 0.5

优先级高只是保证 CPU 调度的可能性会高

互斥锁 vs 自旋锁

相同点

能够保证同一时间,只有一条线程执行锁定范围的代码

不同点

互斥锁

如果发现其他线程正在执行锁定代码,线程会进入休眠(就绪状态)

等待其他线程时间片到打开锁后,线程会被唤醒(执行)

自旋锁

如果发现有其他线程正在锁定代码,线程会用死循环的方式,一直等待锁定的代码执行完成

结论

自旋锁更适合执行非常短的代码

无论什么锁,都是要付出代价

线程安全

线程安全

多个线程进行读写操作时,仍然能够得到正确结果,被称为线程安全

要实现线程安全,必须要用到锁

主线程(UI线程)

几乎所有 UIKit 􏰀提供的类都是线程不安全的,所有更新UI的操作都在主线程上执行

所有包含 MSMutable 的类都是线程不安全的

GCD

全局队列 和 主队列

全局队列

为了方便程序员的使用,苹果提供了全局队列 dispatch_get_global_queue(0, 0)

全局队列是一个并发队列

在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

主队列

为了方便线程间通讯,异步执行完网络任务,在主线程更新 UI

苹果提供了主队列 dispatch_get_main_queue()

主队列专门用于在主线程上调度任务执行

异步执行任务

- (void)gcdDemo1 {

// 1. 全局队列

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

// 2. 任务

dispatch_block_t task = ^ {

NSLog(@"hello gcd %@", [NSThread currentThread]);

};

// 3. 将任务添加到队列,并且指定异步执行

dispatch_async(queue, task);

}

NSThread 的对比

所有的代码写在一起的,让代码更加简单,易于阅读和维护

NSThread 通过 @selector 指定要执行的方法,代码分散

GCD 通过 block 指定要执行的代码,代码集中

使用 GCD 不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期

如果要开多个线程 NSThread 必须实例化多个线程对象

NSThread 靠 NSObject 的分类方法实现的线程间通讯,GCD 靠 block

线程间通讯

#pragma mark - 线程间通讯

- (void)gcdDemo3 {

dispatch_async(dispatch_get_global_queue(0, 0), ^{

// 异步下载图像

NSLog(@"下载图像 %@", [NSThread currentThread]);

// 主线程更新 UI

dispatch_async(dispatch_get_main_queue(), ^{

NSLog(@"主线程更新 UI %@", [NSThread currentThread]);

});

});

}

以上代码是 GCD 最常用代码组合!

网络下载图片

- (void)viewDidLoad {

[super viewDidLoad];

// 1. 准备 URL

NSString *urlString = @"http://image.tianjimedia.com/uploadImages/2011/286/8X76S7XD89VU.jpg";

NSURL *url = [NSURL URLWithString:urlString];

// 2. 下载图像

dispatch_async(dispatch_get_global_queue(0, 0), ^{

// 1> 所有从网络返回的都是二进制数据

NSData *data = [NSData dataWithContentsOfURL:url];

// 2> 将二进制数据转换成 image

UIImage *image = [UIImage imageWithData:data];

// 3> 主线程更新 UI

dispatch_async(dispatch_get_main_queue(), ^{

// 1> 设置图像

_imageView.image = image;

// 2> 调整大小

[_imageView sizeToFit];

// 3> 设置 contentSize

_scrollView.contentSize = image.size;

});

});

}

GCD串行队列

特点

以先进先出的方式,顺序调度队列中的任务执行

无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务

队列创建

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue = dispatch_queue_create("com.ith", NULL);

串行队列演练

串行队列 同步执行

/// 串行队列 - 同步执行

/// 提问:是否开线程?是否顺序执行?come here 的位置?

/// 回答:不会开线程/顺序执行/最后

- (void)gcdDemo1 {

// 1. 串行队列

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);

// 2. 添加同步执行的任务

for (NSInteger i = 0; i < 10; i++) {

dispatch_sync(queue, ^{

[NSThread sleepForTimeInterval:0.5];

NSLog(@"%@ %zd", [NSThread currentThread], i);

});

}

NSLog(@"come here");

}

串行队列 异步执行

/// 串行队列 - 异步执行

/// 提问:是否开线程?是否顺序执行?come here 的位置?

/// 回答:会开一条线程/顺序执行/不确定

- (void)gcdDemo2 {

// 1. 串行队列

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);

// 2. 添加异步执行的任务

for (NSInteger i = 0; i < 10; i++) {

dispatch_async(queue, ^{

NSLog(@"%@ %zd", [NSThread currentThread], i);

});

}

NSLog(@"come here");

}

GCD 并发队列特点

以先进先出的方式,并发调度队列中的任务执行

如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务

如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行

队列创建

dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);

并发队列演练

并发队列 异步执行

/// 并发队列 - 异步执行

/// 提问:是否开线程?是否顺序执行?come here 的位置?

/// 回答:会开线程(取决于底层线程池可用资源)/不是顺序执行/不确定

- (void)gcdDemo2 {

// 1. 并发队列

dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);

// 2. 添加异步执行的任务

for (NSInteger i = 0; i < 10; i++) {

dispatch_async(queue, ^{

NSLog(@"%@ %zd", [NSThread currentThread], i);

});

}

NSLog(@"come here");

}

并发队列 同步执行

/// 并发队列 - 同步执行

/// 提问:是否开线程?是否顺序执行?come here 的位置?

/// 回答:不开线程/顺序执行/最后

- (void)gcdDemo1 {

// 1. 并发队列

dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);

// 2. 添加同步执行的任务

for (NSInteger i = 0; i < 10; i++) {

dispatch_sync(queue, ^{

[NSThread sleepForTimeInterval:0.5];

NSLog(@"%@ %zd", [NSThread currentThread], i);

});

}

NSLog(@"come here");

}GCD全局队列

是系统为了方便程序员开发提供的,其工作表现与并发队列一致

全局队列 & 并发队列的区别

全局队列

没有名称

无论 MRC & ARC 都不需要考虑释放

日常开发中,建议使用"全局队列"

并发队列

有名字,和 NSThread 的 name 属性作用类似

如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象

dispatch_barrier 必须使用自定义的并发队列

开发第三方框架时,建议使用并发队列

全局队列 异步任务

/**

提问:是否开线程?是否顺序执行?come here 的位置?

*/

- (void)gcdDemo8 {

// 1. 队列

dispatch_queue_t q = dispatch_get_global_queue(0, 0);

// 2. 执行任务

for (int i = 0; i < 10; ++i) {

dispatch_async(q, ^{

NSLog(@"%@ - %d", [NSThread currentThread], i);

});

}

NSLog(@"come here");

}

运行效果与并发队列相同

参数

服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级

iOS 8.0

QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)

QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)

QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的)

QOS_CLASS_UTILITY 0x11, 实用工具(专门用来处理耗时操作!)

QOS_CLASS_BACKGROUND 0x09, 后台

QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 适配

iOS 7.0

DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级

DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级

DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级

DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级

为未来保留使用的,应该永远传入0

结论:如果要适配 iOS 7.0 & 8.0,使用以下代码: dispatch_get_global_queue(0, 0);


GCD主队列特点专门用来在主线程上调度任务的队列不会开启线程以先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度队列获取主队列是负责在主线程调度任务的会随着程序启动一起创建主队列只需要获取不用创建dispatch_queue_t queue = dispatch_get_main_queue();主队列演练主队列,异步执行- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {

[self gcdDemo1];

[NSThread sleepForTimeInterval:1.0];

NSLog(@"over");

}

/// 主队列异步

/// 提问:是否开线程?是否顺序执行?come here 的位置?

/// 回答:不开/顺序/最前面

- (void)gcdDemo1 {

// 1. 主队列

dispatch_queue_t queue = dispatch_get_main_queue();

// 2. 异步任务

for (NSInteger i = 0; i < 10; i++) {

dispatch_async(queue, ^{

NSLog(@"%@", [NSThread currentThread]);

});

}

NSLog(@"come here");

}

在主线程空闲时才会调度 主队列 中的任务在主线程执行

主队列,同步执行

/// 主队列同步

- (void)gcdDemo2 {

NSLog(@"begin");

// 1. 主队列

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"hello");

});

NSLog(@"end");

}

主队列和主线程相互等待会造成死锁


GCD 同步任务的作用

同步任务,可以让其他异步执行的任务,依赖某一个同步任务

例如:在用户登录之后,再异步下载文件!

- (void)gcdDemo1 {

// 1. 队列

dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);

// 2. 同步执行登录

dispatch_sync(queue, ^{

NSLog(@"Login %@", [NSThread currentThread]);

});

// 3. 异步下载文件

dispatch_async(queue, ^{

NSLog(@"Download File A %@", [NSThread currentThread]);

});

dispatch_async(queue, ^{

NSLog(@"Download File B %@", [NSThread currentThread]);

});

}

代码改造,让登录也在异步执行

- (void)gcdDemo2 {

// 1. 队列

dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);

// 2. 同步执行登录

dispatch_block_t task = ^ {

dispatch_sync(queue, ^{

NSLog(@"Login %@", [NSThread currentThread]);

});

// 3. 异步下载文件

dispatch_async(queue, ^{

NSLog(@"Download File A %@", [NSThread currentThread]);

});

dispatch_async(queue, ^{

NSLog(@"Download File B %@", [NSThread currentThread]);

});

};

// 3. 将任务添加到队列

dispatch_async(queue, task);

}

主队列调度同步队列不死锁

- (void)gcdDemo3 {

// 1. 串行队列

dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_SERIAL);

// 2. 串行队列异步

dispatch_async(queue, ^{

NSLog(@"begin %@", [NSThread currentThread]);

// 3. 主队列同步执行任务

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"%@", [NSThread currentThread]);

});

NSLog(@"end %@", [NSThread currentThread]);

});

}

主队列在主线程空闲时才会调度队列中的任务在主线程执行

GCD高级

目标

掌握 GCD 常用的几个高级技巧

Barrier(阻塞)

Once(一次性执行)

After(延迟)

Group(调度组)

GCD Delay 延迟操作

目标

掌握 dispatch_after 的基本使用

知道 performSelector:withObject:afterDelay: 的使用

代码演练

- (void)delay {

NSLog(@"%s", __FUNCTION__);

/**

从现在开始,经过多少纳秒,由"队列"调度异步执行 block 中的代码

参数

1. when    从现在开始,经过多少纳秒

2. queue  队列

3. block  异步执行的任务

*/

dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));

dispatch_block_t task = ^ {

NSLog(@"%@", [NSThread currentThread]);

};

// 主队列异步

dispatch_after(when, dispatch_get_main_queue(), task);

// 全局队列异步

dispatch_after(when, dispatch_get_global_queue(0, 0), task);

// 串行队列异步

dispatch_after(when, dispatch_queue_create("queue", NULL), task);

NSLog(@"end");

}

- (void)after {

[self.view performSelector:@selector(setBackgroundColor:) withObject:[UIColor orangeColor] afterDelay:1.0];

NSLog(@"%@", [NSThread currentThread]);

}

GCD Once 一次性执行有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”单例的特点在内存中只有一个实例提供一个全局的访问点提示:单例的使用在 iOS 中非常普遍,以下代码在很多公司的面试中,都要求能够手写出来- (void)once {    static dispatch_once_t onceToken;    NSLog(@"%zd", onceToken);    // onceToken == 0 的时候执行 block 中的代码    // block 中的代码是同步执行的    dispatch_once(&onceToken, ^{        NSLog(@"执行了");    });    NSLog(@"come here");}dispatch 内部也有一把锁,是能够保证"线程安全"的!而且是苹果公司推荐使用的以下代码用于测试多线程的一次性执行- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {

for (NSInteger i = 0; i < 10; i++) {

dispatch_async(dispatch_get_global_queue(0, 0), ^{

[self once];

});

}

}

单例测试

单例的特点

在内存中只有一个实例

提供一个全局的访问点

懒汉式单例实现

所谓懒汉式单例,表示在使用时才会创建

@implementation NetworkTools

+ (instancetype)sharedTools {

static id instance;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

instance = [[self alloc] init];

});

return instance;

}

@end

面试时只要实现上面 sharedTools 方法即可

饿汉式单例实现

所谓饿汉式单例,表示尽早地创建单例实例,可以利用 initialize 方法建立单例

static id instance;

/// initialize 会在类第一次被使用时调用

/// initialize 方法的调用是线程安全的

+ (void)initialize {

NSLog(@"创建单例");

instance = [[self alloc] init];

}

+ (instancetype)sharedTools {

return instance;

}


GCD 调度组

目标

掌握 GCD 调度组的使用

在网络开发时,可以统一监听一组网络请求结束后,再更新 UI

常规用法

- (void)group1 {

// 1. 创建调度组

dispatch_group_t group = dispatch_group_create();

// 2. 全局队列

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

// 3. 调度组异步

dispatch_group_async(group, queue, ^{

NSLog(@"下载图像 A %@", [NSThread currentThread]);

});

dispatch_group_async(group, queue, ^{

NSLog(@"下载图像 B %@", [NSThread currentThread]);

});

// 4. 监听调度组完成通知

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

NSLog(@"完成 %@", [NSThread currentThread]);

});

NSLog(@"come here");

}

enter & leavel

在终端输入 $ man dispatch_group_enter 按两次空格,可以看到

dispatch_group_async() 的说明,是以下代码的方便写法:

void

dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)

{

dispatch_retain(group);

dispatch_group_enter(group);

dispatch_async(queue, ^{

block();

dispatch_group_leave(group);

dispatch_release(group);

});

}

以下写法在实际开发中常用:

dispatch_group_enter(group); 之后,可以监听其后的 block 执行

在其后的 block 的末尾调用 dispatch_group_leave(group);,通知 group 一个任务完成

group 为空,所有任务完成!

注意:dispatch_group_enter 和 dispatch_group_leave 一定要配对使用!

- (void)group2 {

// 1. 创建调度组

dispatch_group_t group = dispatch_group_create();

// 2. 全局队列

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

// 3. 添加任务

// 1> 添加第 1 个任务

dispatch_group_enter(group);

dispatch_async(queue, ^{

[NSThread sleepForTimeInterval:1.0];

NSLog(@"download A %@", [NSThread currentThread]);

// dispatch_group_leave 必须是 block 的最后一句

dispatch_group_leave(group);

});

// 1> 添加第 2 个任务

dispatch_group_enter(group);

dispatch_async(queue, ^{

[NSThread sleepForTimeInterval:3.0];

NSLog(@"download B %@", [NSThread currentThread]);

// dispatch_group_leave 必须是 block 的最后一句

dispatch_group_leave(group);

});

// 4. 监听调度组完成通知

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

NSLog(@"完成 %@", [NSThread currentThread]);

});

NSLog(@"come here");

}


GCD Barrier主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新适合于大规模的 I/O 操作代码演练准备工作 —— 定义照片数组和队列@interface ViewController ()/// 照片数组@property (nonatomic, strong) NSMutableArray *photos;@end@implementation ViewController {    dispatch_queue_t _photoQueue;}NSMutableArray 是非线程安全的viewDidLoad- (void)viewDidLoad {    [super viewDidLoad];    // 1. 创建并发队列    _photoQueue = dispatch_queue_create("cn.itcast.photo", DISPATCH_QUEUE_CONCURRENT);    // 2. 实例化照片可变数组    _photos = [NSMutableArray array];    // 3. 加载照片    [self loadPhotos];}/// 模拟异步下载图像- (void)loadPhotos {}模拟下载照片并在完成后添加到数组- (void)loadPhotos {    NSInteger count = 10 * 100;    for (NSInteger i = 0; i < count; i++) {        dispatch_async(_photoQueue, ^{            NSString *imageName = [NSString stringWithFormat:@"%02zd.jpg", (i % 10 + 1)];            NSURL *url  = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];            NSData *data = [NSData dataWithContentsOfURL:url];            UIImage *image = [UIImage imageWithData:data];//            NSLog(@"下载图像 %@ %@", imageName, [NSThread currentThread]);            // 添加到图像数组            [self.photos addObject:image];//                NSLog(@"添加到数组 %@ %@", imageName, [NSThread currentThread]);        });    }}- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {

NSLog(@"%zd", self.photos.count);

}

运行测试

由于 NSMutableArray 是非线程安全的

如果出现两个线程在同一时间向数组中添加对象,可能会出现程序崩溃的情况

也可能出现数组数据错乱的情况

解决办法

// 添加到图像数组

dispatch_barrier_async(_photoQueue, ^{

[self.photos addObject:image];

NSLog(@"添加到数组 %@ %@", imageName, [NSThread currentThread]);

});

使用 dispatch_barrier_async 添加的 block 会在之前添加的 block 全部运行结束之后,才在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作!

Barrier 工作示意图

注意:dispatch_barrier_async 必须使用自定义队列,否则执行效果和全局队列一致

NSOperation 简介

NSOperation

是 OC 语言中基于 GCD 的面向对象的封装

使用起来比 GCD 更加简单(面向对象)

提供了一些用 GCD 不好实现的功能

苹果推荐使用,使用 NSOperation 不用关心线程以及线程的生命周期

核心概念

将 操作(异步执行的任务) 添加到 队列(并发队列)

NSOperation 是一个抽象类,不能直接使用

抽象类的用处是定义子类共有的属性和方法

已经学习过的抽象类包括:

UICollectionViewLayout

UIGestureRecognizer

CAAnimation

CAPropertyAnimation

在苹果的头文件中,有些抽象类和子类的定义是在同一个头文件中的

子类:

NSInvocationOperation (调用)

NSBlockOperation (块)

自定义 Operation

NSOperationQueue 队列

使用步骤

NSOperation 和 NSOperationQueue 实现多线程的具体步骤:

程序员

先将需要执行的操作封装到一个 NSOperation 对象中

然后将 NSOperation 对象添加到 NSOperationQueue 中

系统

系统会自动将 NSOperationQueue 中的 NSOperation 取出来

将取出的 NSOperation 封装的操作放到一条新线程中执行

NSOperation 基本演练

目标

通过 NSInvocationOperation 体会将操作添加到队列异步执行

知道 start 方法

NSOperationQueue - 并发队列

NSOperation - 异步执行的任务

代码演练

NSInvocationOperation

start 方法

- (void)opDemo1 {

// 1. 创建操作

NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@(__FUNCTION__)];

// 2. 启动操作

/**

Begins the execution of the operation.

在当前线程开始执行操作

The default implementation of this method updates the execution state of the operation and calls the receiver’s main method. This method also performs several checks to ensure that the operation can actually run. For example, if the receiver was cancelled or is already finished, this method simply returns without calling main.

- 此方法的默认实现更新操作的执行状态,并且调用接收者的 main 方法

- 此方法同时会执行一系列检查,以确保操作可以被正确执行

- 例如,如果接收者已经被取消或者已经结束,此方法直接返回而不再调用 main 方法

You can call this method explicitly if you want to execute your operations manually. However, it is a programmer error to call this method on an operation object that is already in an operation queue or to queue the operation after calling this method. Once you add an operation object to a queue, the queue assumes all responsibility for it.

- 如果希望手动执行操作,可以调用此方法

- 然而,如果一个操作已经被添加进操作队列,再调用 start 方法是错误的

- 因为,一旦将操作对象添加到队列,后续的操作应该由队列负责

*/

[op start];

}

- (void)demo:(id)obj {

NSLog(@"%@ %@", [NSThread currentThread], obj);

}

默认情况下,调用 start 方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作

只有将 NSOperation 放到一个 NSOperationQueue 中,才会异步执行操作

将操作添加到队列

- (void)opDemo2 {

// 1. 创建操作

NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@(__FUNCTION__)];

// 2. 创建队列

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 3. 将操作添加到队列

/**

Once added, the specified operation remains in the queue until it finishes executing.

- 一旦添加,指定的操作会保留在队列中,直至执行完毕

- 将操作添加到队列后,系统会从队列中取出操作,并新建线程,执行操作指定方法

*/

[queue addOperation:op];

}

添加多个操作

@implementation ViewController {

NSOperationQueue *_queue;

}

- (void)viewDidLoad {

[super viewDidLoad];

_queue = [[NSOperationQueue alloc] init];

[self opDemo3];

}

#pragma mark - NSOperation

/// 添加多个操作

- (void)opDemo3 {

for (NSInteger i = 0; i < 10; i++) {

NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@(i)];

[_queue addOperation:op];

}

}

执行效果:会开启多条线程,而且不是顺序执行。与 GCD 中 并发队列&异步执行 效果一样!

一般在使用 NSOpeartion 时,会建立一个全局队列,统一调度所有的异步操作

结论,在 NSOperation 中:

操作 -> 异步执行的任务

队列 -> 并发队列

NSBlockOperation

目标

熟悉 NSBlockOperation 的使用

NSBlockOperation blockOperationWithBlock 可以创建块操作

qualityOfService 可以指定优先级

completionBlock 可以设置操作完成回调

完成回调同样在异步执行,并且没有任何参数

代码演练

基本代码演练

@implementation ViewController {

NSOperationQueue *_queue;

}

- (void)viewDidLoad {

[super viewDidLoad];

_queue = [[NSOperationQueue alloc] init];

[self opDemo];

}

- (void)opDemo {

// 1. 新建操作

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"%@", [NSThread currentThread]);

}];

// 2. 将操作添加到队列

[_queue addOperation:op];

}

@end

相比较 NSInvocationOperation,NSBlockOperation 使用更加简单

更简单的使用

/// 更简单的使用

- (void)opDemo2 {

// 添加 block 操作

[_queue addOperationWithBlock:^{

NSLog(@"%@", [NSThread currentThread]);

}];

}

直接添加 block 更加简单,不过会少了一些控制,例如:完成监听、优先级等

设置完成监听

/// 完成监听

- (void)opDemo3 {

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{

[NSThread sleepForTimeInterval:1.0];

NSLog(@"%@", [NSThread currentThread]);

}];

// 设置完成 block - 会另外新建一条线程执行完成回调

[op setCompletionBlock:^{

NSLog(@"完成 %@", [NSThread currentThread]);

}];

[_queue addOperation:op];

}

设置优先级

/// 设置优先级

- (void)opDemo4 {

NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{

for (NSInteger i = 0; i < 10; i++) {

NSLog(@"%@ %@", [NSThread currentThread], @(i));

}

}];

op1.qualityOfService = NSQualityOfServiceUserInteractive;

[_queue addOperation:op1];

NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{

for (NSInteger i = 0; i < 10; i++) {

NSLog(@"%@ %@", [NSThread currentThread], @(i));

}

}];

op2.qualityOfService = NSQualityOfServiceBackground;

[_queue addOperation:op2];

}

设置执行块 —— 知道即可

/// 设置执行块

- (void)opDemo5 {

NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"==> %@", [NSThread currentThread]);

}];

[op addExecutionBlock:^{

NSLog(@"block 1 %@", [NSThread currentThread]);

}];

[_queue addOperation:op];

NSLog(@"%@", op.executionBlocks);

}

执行块和操作享有共同的属性设置

当 NSBlockOperation 封装的 操作数 > 1,就会异步执行操作

线程间通讯

目标

掌握 NSOperation 的线程间通讯

NSOperationQueue mainQueue 可以获得主队列,实现线程间通讯

NSOperationQueue currentQueue 当前队列

队列操作方法

addOperation: 添加操作

addOperations:waitUntilFinished: 添加多个操作,并且指定是否等待操作完成

addOperationWithBlock: 添加块操作

cancelAllOperations 取消所有操作

队列属性

name 队列名称

operations 操作数组

operationCount 操作数量

maxConcurrentOperationCount 最大并发数

suspended 挂起

qualityOfService 服务质量

代码操作

常见的线程间通讯代码

/// 线程间通讯

- (void)opDemo6 {

[_queue addOperationWithBlock:^{

NSLog(@"耗时操作 %@", [NSThread currentThread]);

NSString *json = @"我是 json";

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

NSLog(@"%@ %@", json, [NSThread currentThread]);

}];

}];

}

NSOperation高级操作目标掌握 NSOperation 的高级操作设置最大并发数(同时执行的任务数量,不是开启的线程数量)暂停、继续和取消设置 suspended 可以暂停和继续队列的调度挂起队列,不会影响正在执行中的操作cancelAllOperations 可以给队列中所有任务发送 cancel 消息如果操作还没有调度,会被从队列中移除如果操作已经被执行,需要通过自定义操作取消执行,否则操作会继续执行,直至完成代码演练代码准备@implementation ViewController {    NSOperationQueue *_queue;}- (void)viewDidLoad {    [super viewDidLoad];    _queue = [[NSOperationQueue alloc] init];    [self demo];}- (void)demo {    for (NSInteger i = 0; i < 20; i++) {        [_queue addOperationWithBlock:^{            // 注意休眠的位置            [NSThread sleepForTimeInterval:1.0];            NSLog(@"%@ %@", [NSThread currentThread], @(i));        }];    }}@end运行测试开启的线程数量很多- (void)viewDidLoad {    [super viewDidLoad];    _queue = [[NSOperationQueue alloc] init];    // 设置最大并发数    _queue.maxConcurrentOperationCount = 2;    [self demo];}注意:最大并发数,是同时执行的任务数量,不是开启的线程数量!队列的暂停、继续和取消暂停和继续- (IBAction)pause:(id)sender {    if (_queue.operationCount == 0) {        NSLog(@"队列中没有操作");        return;    }    _queue.suspended = !_queue.isSuspended;    NSLog(@"%@", (_queue.suspended ? @"暂停": @"继续"));    NSLog(@"操作数量 %zd", _queue.operationCount);}注意:挂起队列只会暂停队列中的操作继续被调度当前正在执行的操作不会被挂起操作完成之后,会被从队列中移除如果将队列设置为挂起,后续再添加操作,队列同样不会被调度取消全部- (IBAction)cancelAll:(id)sender {    [_queue cancelAllOperations];    NSLog(@"取消全部 %zd", _queue.operationCount);}注意当前正在执行的操作不会被取消自定义操作除外自定义操作的取消新建操作@interface CZDemoOperation : NSOperation@property (nonatomic, assign) NSInteger num;@end实现 main 方法main 方法是每一个线程的入口注意:需要在 main 方法中添加自动释放池@implementation CZDemoOperation- (void)main {    @autoreleasepool {        if (self.isCancelled) {            NSLog(@"被取消 %zd", _num);            return;        }        [NSThread sleepForTimeInterval:1.0];        if (self.isCancelled) {            NSLog(@"被取消 %zd", _num);            return;        }        NSLog(@"%@ %zd", [NSThread currentThread], _num);    }}@end测试取消代码@implementation ViewController {    NSOperationQueue *_queue;}- (void)viewDidLoad {    [super viewDidLoad];    _queue = [[NSOperationQueue alloc] init];    _queue.maxConcurrentOperationCount = 2;    for (NSInteger i = 0; i < 20; i++) {        CZDemoOperation *op = [[CZDemoOperation alloc] init];        op.num = i;        [_queue addOperation:op];    }}- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {

NSLog(@"取消所有操作");

[_queue cancelAllOperations];

}

操作的依赖关系

掌握操作的依赖关系

通过 addDependency 可以设置操作之间的依赖关系

注意不要设置循环依赖

代码演练

- (void)demo {

NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"login %@", [NSThread currentThread]);

}];

NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"download A %@", [NSThread currentThread]);

}];

NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"download B %@", [NSThread currentThread]);

}];

[op2 addDependency:op1];

[op3 addDependency:op1];

// 注意不要添加循环依赖

//    [op1 addDependency:op2];

[_queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];

NSLog(@"come here");

}

注意不要设置循环依赖

与 GCD 的对比

GCD

将任务(block)添加到队列(串行/并发/主队列),并且指定任务执行的函数(同步/异步)

GCD 是底层的 C 语言构成的 API

iOS 4.0 推出的,针对多核处理器的并发技术

在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构

要停止已经加入 queue 的 block 需要写复杂的代码

需要通过 Barrier 或者同步任务设置任务之间的依赖关系

只能设置队列的优先级

高级功能:

一次性 once

延迟操作 after

调度组

NSOperation

核心概念:把操作(异步)添加到队列(并发队列)

OC 框架,更加面向对象,是对 GCD 的封装

iOS 2.0 推出的,苹果推出 GCD 之后,对 NSOperation 的底层全部重写

Operation作为一个对象,为我们提供了更多的选择

可以随时取消已经设定要准备执行的操作,已经执行的除外

可以跨队列设置操作的依赖关系

可以设置队列中每一个操作的优先级

高级功能:

最大操作并发数(GCD不好做)

继续/暂停/全部取消

跨队列设置操作的依赖关系

IOS技术分享
Web note ad 1