iOS多线程
多线程基础
在学习多线程之前, 按照惯例, 先普及一些概念, 为接下来的学习做铺垫.
进程
进程
是指在系统
中正在运行的一个应用程序
, 在一个系统
中, 进程
之间是相互独立的. 每个进程
运行在其专用
且收保护的内存空间
内
线程
进程
想要执行任务, 必须在线程
下执行, 每1
个进程
至少要有一条线程, 我们称之为主线程
.
线程与进程的关系
一个进程
中至少包含一个线程
, 同一个进程
内的线程
共享进程
的资源.
主线程
一个iOS程序运行时, 默认会开启1条线程, 称为主线程
或UI线程
.
主线程的作用: 刷新UI 显示UI界面, 处理UI事件, 比如点击, 滑动
使用主线程注意:
不要把耗时操作放到主线程中, 会影响UI的流畅度
多线程
一个进程
中可以开启多条线程
, 多条线程
可以同时执行不同的任务, 多线程
技术可以提高程序的执行效率
.
多线程原理
对于单核CPU
, 同一时间, CPU
只能处理1
条线程
, 多线程并发
执行, 其实是CPU
快速的在多条线程
之间来回切换
, 如果CPU
调度线程到的速度足够快, 就造成了多线程
并发的假象.
多线程的优点
- 能适当提高程序的
执行效率
- 能适当提高资源利用率(
CPU
,内存
利用率)
多线程的缺点
- 创建线程是需要成本的, iOS下主要包括, 在栈空间的子线程
512KB
, 主线程1MB
, 创建线程大约需要90
毫秒的创建时间. - 线程越多, CPU在调度线程上的开销就越大
- 线程越多, 程序设计就越复杂, 因为要考虑到线程之间的通信, 多线程的数据共享
多线程实现方案
在iOS开发中, 有多重多线程方案可以供我们选择, 一般用其中三种
NSThread
- 使用面向对象
- 简单易用, 可以直接操作线程对象
- 使用
Objective-C
, 由程序员管理线程生命周期
GCD
- 旨在替代
NSThread
等多线程技术 - 充分利用设备的多核
- 使用
C
语言, 自动管理线程生命周期
NSOperation
- 基于
GCD
, 更加面向对象 - 使用
Objective-C
, 自动管理线程的生命周期
NSThread
NSThread
是基于线程
使用, 较轻量级
的多线程
编程方法(相对于GCD
和NSOperation
), 一个NSThread
对象代表一个线程
, 需要手动管理线程的生命周期
, 处理线程同步等问题.
NSThread的创建
动态创建, 动态方法返回一个新的thread
对象, 需要调用start
方法来启动线程
NSTHread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil];
静态创建, 静态方法没有返回值, 如果需要获取新创建的thread
, 需要在Selector
中调用获取当前线程的方法
[NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil];
设置线程的名字
[newThread setName:@"线程名字"];
newThread.name = @"线程名字";
线程开启
[newThread start];
线程暂停
NSThread
的暂停会有阻塞当前线程的效果
// 暂停1秒
[NSThread sleepForTimeInterval:1.0];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
线程取消
取消线程并不会马上停止并退出线程, 仅仅只作(线程是否需要退出)状态记录
[newThread cancel];
线程停止
停止方法会立即终止除主线程以外的所有线程(无论是否在执行任务)并退出, 需要在掌控所有线程状态的情况下调用此方法, 否则可能会导致内存问题
[NSThread exit];
获取当前线程
[NSThread currentThread];
获取主线程
[NSThread mainThread];
线程优先级设置
iOS8以前用
// 设置线程优先级, 默认0.5 取值范围0.0-1.0
[NSThread setThreadPriority:1.0];
iOS8以后推荐使用 枚举值
[newThread setQualityOfService:NSQualityOfServiceUserInitiated];
NSQualityOfServiceUserInteractive // 最高优先级, 用于用户交互事件
NSQualityOfServiceUserInitiated // 次高优先级, 用于用户需要马上执行的事件
NSQualityOfServiceDefault // 默认优先级, 主线程和没有设置优先级的线程都默认为这个优先级
NSQualityOfServiceUtility // 普通优先级, 用于普通任务
NSQualityOfServiceBackground // 最低优先级, 用于不重要的任务
- 获取指定线程的优先级
[newThread threadPriority];
线程间的通信
常见的有三种:
指定当前线程执行操作
[self performSelector:@selector(threadRun)];
[self performSelector:@selector(threadRun) withObject:nil];
[self performSelector:@selector(threadRun) withObject:nil afterDelay:2.0];
(在其他线程中)指定主线程执行操作
[self performSelectorOnMainThread:@selector(threadRun) withObject:nil waitUntilDone:YES];
(在主线程中)指定其他线程执行操作
// 这里为指定某个线程
[self performSelector:@selector(threadRun) onThread:newThread withObject:nil waitUntilDone:YES];
// 这里指定为后台线程
[self performSelectorInBackground:@selector(threadRun) withObject:nil];
线程同步
线程和其他线程可能会共享一些资源, 当多个线程同时读写同一份共享资源的时候, 可能会引起冲突. 线程同步是指在一定的时间内只允许某一个线程访问某个资源.
iOS实现线程加锁有NSLock
和@synchronized
两种方式
线程的持续运行和退出
怎样能让线程一直运行呢?
答案就是给线程加上RunLoop
线程状态
线程是否完成
BOOL ret = [newThread isFinished];
线程是否被取消
BOOL ret = [newThread isCancelled];
线程是否正在执行
BOOL ret = [newThread isExecuting];
GCD
GCD全称是Grand Central Dispatch
优势
- GCD是苹果公司为多核的并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(比如双核, 四核)
- GCD会自动管理线程的生命周期(创建线程, 调度任务, 销毁线程)
核心概念 - 任务
任务: 执行什么操作, 任务有两种执行方式: 同步函数
和异步函数
- 同步函数(dispatch_sync)
只能在当前线程中执行任务, 不具备开启新线程的能力,任务会立刻马上执行, 会阻塞当前线程并等待Block中的任务执行完毕, 然后当前线程才会继续往下运行. - 异步函数(dispatch_async)
可以在新的线程中执行任务, 具备开启新线程的能力, 但不一定会开启新线程, 当前线程会直接往下执行, 不会阻塞当前线程.
核心概念 - 队列
队列: 用来存放任务, 分为串行队列
和并行队列
串行队列(Serial Dispatch Queue)
让任务一个接着一个的执行, 一个任务执行完毕后再执行下一个任务并行队列(Concurrent Dispatch Queue)
可以放多个任务并发, 同时执行(自动开启多个线程同时执行任务)
并发功能只有在异步
函数下才有效.
GCD使用步骤
- 定制任务
确定想做的事 - 将任务添加到队列中
GCD
会自动将队列中的任务取出, 放到对应的线程中去执行, 任务的取出遵循FIFO
原则: 先进先出, 后进后出
GCD的创建
队列的创建
/*
@param label#> C语言字符串, 用来标识 description#>
@param attr#> 队列的类型 description#>
并发队列: DISPATCH_QUEUE_CONCURRENT
串行队列: DISPATCH_QUEUE_SERIAL 或者 NULL
@return dispatch_queue_t
*/
dispatch_queue_t queue = dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t _Nullable attr);
创建串行队列
方法一:
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_SERIAL);
方法二:
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", NULL);
创建并发队列
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
全局并发队列
GCD默认提供了全局并发队列, 供整个应用使用, 可以无需手动创建
@param identifier#> 优先级 也可以直接填后面的数字 description#>
DISPATCH_QUEUE_PRIORITY_HIGH = 2 高
DISPATCH_QUEUE_PRIORITY_DEFAULT = 0 默认
DISPATCH_QUEUE_PRIORITY_LOW = (-2) 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台
@param flags#> 预留参数 0 description#>
@return dispatch_queue_t->全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
任务的执行
队列在queue
中, 任务在block
块中.
- 开启
同步函数
,同步函数
: 要求立刻马上开始执行
@param queue: 队列
@param block: 执行的任务
dispatch_sync(queue, ^ {
// 执行任务
}
- 开启
异步函数
,异步函数
: 等主线程执行完毕之后, 回过头开线程执行任务
@param queue: 队列
@param block: 执行的任务
dispatch_async(queue, ^{
// 执行任务
}
任务和队列的组合
任务: 同步函数
和异步函数
队列: 串行队列
和并行队列
- 异步函数+并发队列: 会开启新的线程, 并发执行
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1------%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2------%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3------%@", [NSThread currentThread]);
});
控制台打印:
GCD_OC[18856:17195716] 1------{number = 3, name = (null)}
GCD_OC[18856:17195718] 3------{number = 5, name = (null)}
GCD_OC[18856:17195717] 2------{number = 4, name = (null)}
- 异步函数+串行队列: 会开启一条线程, 任务串行执行
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", NULL);
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
控制台打印
GCD_OC[19358:17243144] 1-----{number = 3, name = (null)}
GCD_OC[19358:17243144] 2-----{number = 3, name = (null)}
GCD_OC[19358:17243144] 3-----{number = 3, name = (null)}
- 同步函数+并发队列: 不会开线程, 任务串行执行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{
NSLog(@"1------%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2------%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3------%@", [NSThread currentThread]);
});
控制台打印
GCD_OC[19682:17270377] 1------{number = 1, name = main}
GCD_OC[19682:17270377] 2------{number = 1, name = main}
GCD_OC[19682:17270377] 3------{number = 1, name = main}
- 同步函数+串行队列: 不会开线程, 任务串行执行
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
控制台打印
GCD_OC[19875:17290416] 1-----{number = 1, name = main}
GCD_OC[19875:17290416] 2-----{number = 1, name = main}
GCD_OC[19875:17290416] 3-----{number = 1, name = main}
- 异步函数+主队列: 不会开线程, 任务串行执行
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"------%@", [NSThread currentThread]);
});
控制台打印
GCD_OC[20048:17307951] ------{number = 1, name = main}
- 同步函数+主队列 : 死锁
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSThread currentThread]);
});
死锁原因
首先这是一个同步函数
, 同步函数
要求立刻马上执行, 我们把这个同步函数
的任务加入到主队列
, 同步函数
代码块中的任务等待主队列
任务执行完毕, 然后执行代码块的任务, 主队列
在等代码块里的任务执行, 两个任务互相等待发生死锁, 将这个方法放入子线程中, 则不会发生死锁, 任务串行执行
总结
同步函数和异步函数的执行顺序
同步函数: 立刻马上执行, 会阻塞当前线程
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"------start-------");
dispatch_sync(queue, ^{
NSLog(@"1------%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2------%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3------%@", [NSThread currentThread]);
});
NSLog(@"------end-------");
控制台打印
GCD_OC[20695:17385076] ------start-------
GCD_OC[20695:17385076] 1------{number = 1, name = main}
GCD_OC[20695:17385076] 2------{number = 1, name = main}
GCD_OC[20695:17385076] 3------{number = 1, name = main}
GCD_OC[20695:17385076] ------end-------
异步函数: 当前线程会直接往下执行, 不会阻塞当前线程
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"------start-------");
dispatch_async(queue, ^{
NSLog(@"1------%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2------%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3------%@", [NSThread currentThread]);
});
NSLog(@"------end-------");
控制台打印
GCD_OC[20804:17399341] ------start-------
GCD_OC[20804:17399341] ------end-------
GCD_OC[20804:17399604] 3------{number = 5, name = (null)}
GCD_OC[20804:17399605] 2------{number = 4, name = (null)}
GCD_OC[20804:17399607] 1------{number = 3, name = (null)}
GCD线程之间的通信
GCD线程间的通信很简单, 使用同步或异步函数, 传入主队列即可
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 下载图片
dispatch_async(dispatch_get_main_queue(), ^ {
// 更新UI
});
});
GCD常用函数
栅栏函数
- 作用: 控制任务的执行顺序
dispatch_barrier_async(queue, ^{
}
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"我是栅栏函数");
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"4-----%@", [NSThread currentThread]);
});
控制台打印
GCD_OC[21103:17426839] 2-----{number = 4, name = (null)}
GCD_OC[21103:17426841] 1-----{number = 3, name = (null)}
GCD_OC[21103:17426841] 我是栅栏函数
GCD_OC[21103:17426841] 3-----{number = 3, name = (null)}
GCD_OC[21103:17426839] 4-----{number = 4, name = (null)}
延迟执行
/**
延迟执行
@param DISPATCH_TIME_NOW 延迟时间
@param int64_t 要执行的代码
@return 如果想要延迟的代码在子线程中执行, 可以将dispatch_get_main_queue()
改为其他队列
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"------%@", [NSThread currentThread]);
});
一次性代码
一般用于单例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行一次的代码
});
快速迭代
开启多个线程并发完成迭代操作
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/**
快速迭代
@param 5 次数
@param queue 在哪个队列中执行
@param block 执行的Block
*/
dispatch_apply(5, queue, ^(size_t i) {
NSLog(@"dispatch_apply == == ==........... %lu", i);
});
控制台打印
GCD_OC[21684:17472387] dispatch_apply == == ==........... 0
GCD_OC[21684:17473701] dispatch_apply == == ==........... 2
GCD_OC[21684:17473703] dispatch_apply == == ==........... 3
GCD_OC[21684:17473702] dispatch_apply == == ==........... 1
GCD_OC[21684:17472387] dispatch_apply == == ==........... 4
队列组
dispatch_group
创建一个任务组, 然后异步执行加入group
的每个任务.
一般用于: 异步多请求
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
// 执行请求1
NSLog(@"1------%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
// 执行请求2
NSLog(@"2------%@", [NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{
// 执行完毕, 可以在这里进行更新UI操作
NSLog(@"finish----%@", [NSThread currentThread]);
});
控制台打印
GCD_OC[22139:17507032] 1------{number = 3, name = (null)}
GCD_OC[22139:17506993] 2------{number = 4, name = (null)}
GCD_OC[22139:17506993] finish----{number = 4, name = (null)}
dispatch_group_async
里的block
执行的是异步任务, 如果还是使用上面的方法会发现, 在异步任务还没跑完就已经进入到了dispatch_group_notify
方法了, 这时用dispatch_group_enter
和dispatch_group_leave
就可以解决这个问题.
解决方法:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
// 进入组
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
// 网络请求1 异步代码块
{
// 离开组
dispatch_group_leave(group);
}
NSLog(@"1-----%@", [NSThread currentThread]);
});
// 进入组
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
// 网络请求1 异步代码块
{
// 离开组
dispatch_group_leave(group);
}
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{
// 获取到网络请求的数据, 更新UI
NSLog(@"finish----%@", [NSThread currentThread]);
});
控制台打印
GCD_OC[23625:17613380] 1-----{number = 3, name = (null)}
GCD_OC[23625:17613378] 2-----{number = 4, name = (null)}
GCD_OC[23625:17613378] finish----{number = 4, name = (null)}
NSOperation
NSOperation
是苹果公司对GCD
的封装, 完全面向对象, 并且比GCD
多了一些简单使用的功能, 所以使用起来更加方便容易理解
优点:
- 可添加完成的代码块, 在操作完成后执行
- 添加操作之间的依赖关系, 方便的控制执行顺序
- 设定操作执行的优先级
- 可以很方便的取消一个操作的执行
- 使用KVO观察对操作执行状态的更改:
isExecuting
,isFinished
,isCancelled
.
核心概念 - 操作(Operation)
相当于GCD
的任务
执行操作的意思, 在GCD
中是放在block
中的, 在NSOperation
中, 我们使用NSOperation
的子类NSInvocationOperation
, NSBlockOperation
或者自定义子类来封装操作.
核心概念 - 操作队列(Operation Queues)
NSOperation
的操作队列和GCD
的队列差不多, 都是用来存放操作(任务)的.
- 不同于
GCD
的先进先出原则,NSOperationQueue
对于添加到队列中的操作, 首先进入准备就绪的状态, 然后进入就绪状态的操作的开始执行顺序由操作之间相对的优先级决定 - 操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制
并发
,串行
-
NSOperationQueue
为我们提供了两种不同类型的队列:主队列
和自定义队列
- 主队列: 运行在主线程之上
- 自定义队列: 在后台执行
NSOperation, NSOperationQueue使用步骤
NSOperation
要配合NSOperationQueue
来实现多线程, 因为默认情况下, NSOperation
单独使用时系统同步执行操作, 配合NSOperationQueue
我们能更好的实现异步执行.
NSOperation实现多线程的使用步骤分为三步:
- 创建操作: 先将需要执行的操作封装到一个
NSOperation
对象中 - 创建队列: 创建
NSOperationQueue
对象 - 将操作加入队列中: 将
NSOperation
对象添加到NSOperationQueue
对象中
系统就会自动将NSOperationQueue
中的NSOperation
取出来, 在新线程中执行操作
创建操作(NSOperation)
NSOperation
是一个抽象类, 不能用来封装操作, 我们只能使用它的子类来封装操作, 有三种方式来封装操作.
- 使用子类
NSInvocationOperation
- 使用子类
NSBlockOperation
- 自定义继承自
NSOperation
的子类, 通过实现内部相应的方法来封装操作
NSInvocationOperation
- (void)viewDidLoad {
[super viewDidLoad];
// 创建NSInvocationOperation 对象
NSInvocationOperation *opertaion = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"校长"];
// 调用start方法 执行操作
[opertaion start];
}
- (void)task:(NSString *)name {
// 打印当前线程
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"%@", name);
}
控制台打印
[53559:18598091] {number = 1, name = main}
[53559:18598091] 校长
可以看到在没有使用NSOperationQueue
的情况下, 单独使用NSOperation
子类, NSInvocationOperation
执行一个操作的情况下, 并没有开启新的线程.
NSBlockOperation
// 创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
// 执行操作
[operation start];
控制台打印
{number = 1, name = main}
可以看到在没有使用NSOperationQueue
, 在单独使用NSBlockOperation
执行一个操作的情况下, 操作是当前线程执行的, 并没有开启新线程.
但是, NSBlockOperation
还提供了一个方法addExecutionBlock
, 通过addExecutionBlock
就可以为NSBlockOperation
添加额外
的操作, 这些操作
可以在不同的线程
中并发
执行, 只有当所有相关的操作已经完成执行时, 才视为完成.
// 创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1--------%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"2--------%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"3--------%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"5--------%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"6--------%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"7--------%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"8--------%@", [NSThread currentThread]);
}];
// 执行操作
[operation start];
控制台打印
[54248:18665109] 3--------{number = 4, name = (null)}
[54248:18664485] 5--------{number = 5, name = (null)}
[54248:18664486] 2--------{number = 3, name = (null)}
[54248:18661846] 1--------{number = 1, name = main}
[54248:18664485] 7--------{number = 5, name = (null)}
[54248:18664486] 8--------{number = 3, name = (null)}
[54248:18665109] 6--------{number = 4, name = (null)}
可以看出, 使用子类NSBlockOperation
, 并调用方法addExecutionBlock:
的情况下, blockOperationWithBlock:
和addExecutionBlock:
中的操作是在不同的线程中异步执行的.
一般情况下, 如果一个NSBlockOperation
对象封装了多个操作, NSBlockOperation
是否开启新线程
, 取决于操作的个数
, 如果添加的操作
的个数多, 就会自动开启新线程
. 开启线程
的个数由系统
决定.
使用自定义继承自NSOperation的子类
如果使用子类NSInvocationOperation
和NSBlockOperation
都不能满足开发需求, 我们可以尝试使用自定义继承自NSOperation
的子类. 可以通过重写main
或start
方法来自定义自己的NSOperation
对象, 重写main
方法比较简单, 我们不需要管理操作的状态属性isExecuting
, isFinished
. 当main
执行返回的时候, 这个操作就结束了.
一个简单的
#import
@interface MYOperation : NSOperation
@end
#import "MYOperation.h"
@implementation MYOperation
- (void)main {
if (!self.isCancelled) {
NSLog(@"MyOperation -- %@", [NSThread currentThread]);
}
}
@end
导入头文件MYOperation.h
MYOperation *operation = [[MYOperation alloc] init];
[operation start];
控制台打印
MyOperation -- {number = 1, name = main}
可以看出在没有使用NSOperationQueue
, 单独使用自定义继承自NSOperation
的子类的情况下, 并没有开启新线程
创建队列(NSOperationQueue)
NSOperationQueue
有两种队列
- 主队列
- 添加到主队列的操作, 都会放到主线程去执行
- 自定义队列
- 添加到自定义队列的操作, 都会放到子线程中执行
- 自定义队列包含了
串行
和并发
功能
获取主队列
[NSOperationQueue mainQueue];
自定义队列创建方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
将操作加入队列中
NSOperation
要配合NSOperationQueue
来实现多线程, 那么需要将创建好的操作
添加到队列
中去.
两种方法:
- (void)addOperation:(NSOperation *)op;
// 创建NSInvocationOperation任务对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"校长"];
// 创建NSBlockOperation任务对象
NSBlockOperation *blockOperaion = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation -- %@", [NSThread currentThread]);
}];
// 创建自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 将任务添加到队列中
[queue addOperation:operation];
[queue addOperation:blockOperaion];
- (void)task:(NSString *)name {
// 打印当前线程
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"%@", name);
}
控制台打印
[55782:18793884] blockOperation -- {number = 4, name = (null)}
[55782:18793886] task1 --- {number = 3, name = (null)}
[55782:18793886] task1 --- 校长
使用NSOperation
子类创建操作, 并使用addOperation:
将操作加入到操作队列后能够开启
新线程, 进行并发
执行.
-
- (void)addOperationWithBlock:(void (^)(void))block;
addOperationWithBlock:
无需创建操作, 在block
中添加操作, 直接将包含操作的block
加入到队列中.
// 创建自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 添加操作到队列中
[queue addOperationWithBlock:^{
NSLog(@"1-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"2-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"3-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"4-----%@", [NSThread currentThread]);
}];
控制台打印
[56017:18816692] 3-----{number = 5, name = (null)}
[56017:18816689] 1-----{number = 4, name = (null)}
[56017:18816690] 4-----{number = 6, name = (null)}
[56017:18816691] 2-----{number = 3, name = (null)}
addOperationWithBlock:
将操作加入到操作队列后能够开启新线程
, 并发
执行.
控制NSOperationQueue队列的串行, 并发
设置NSOperationQueue
的串行和并发, 需要设置maxConcurrentOperationCount
这个属性.
maxConcurrentOperationCount
: 最大并发数, 用来控制一个自定义队列
中可以有多少个操作同时
执行.
- 最大操作并发数:
maxConcurrentOperationCount
- 默认为
-1
: 表示不进行限制, 可进行并发执行 -
1
: 队列为串行队列, 只能串行执行 -
大于1
: 队列为并发队列, 操作并发执行 (这个值不应该超过系统限制, 即使自己设置一个很大的值, 系统也会自动调整为min
)
- 默认为
// 创建自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置最大操作数为 1
queue.maxConcurrentOperationCount = 1;
// 添加操作到队列中
[queue addOperationWithBlock:^{
NSLog(@"1-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"2-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"3-----%@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"4-----%@", [NSThread currentThread]);
}];
控制台打印
- 最大操作为
1
的结果
[56307:18843375] 1-----{number = 3, name = (null)}
[56307:18843377] 2-----{number = 4, name = (null)}
[56307:18843377] 3-----{number = 4, name = (null)}
[56307:18843377] 4-----{number = 4, name = (null)}
可以看到, 在maxConcurrentOperationCount
为1
时, 操作串行
执行.
- 最大操作为
2
的结果
[56491:18863093] 2-----{number = 4, name = (null)}
[56491:18863094] 1-----{number = 3, name = (null)}
[56491:18863092] 3-----{number = 5, name = (null)}
[56491:18863094] 4-----{number = 3, name = (null)}
可以看到, 任务不是串行执行的, 而是并发执行, 而开启线程数量是由系统决定的, 不需要我们来管理.
NSOperation操作依赖
NSOperation
,NSOperationQueue
最引人的地方就是它能添加操作之间的依赖关系.
通过操作依赖, 我们可以很方便的控制操作之间的执行先后顺序, NSOperation
提供了3
个接口供我们管理和查看依赖
-
- (void)addDependency:(NSOperation *)op;
- 添加依赖, 使当前操作依赖于
操作op
的完成;
- 添加依赖, 使当前操作依赖于
-
- (void)removeDependency:(NSOperation *)op;
- 移除依赖, 取消当前操作对
操作op
的依赖
- 移除依赖, 取消当前操作对
-
@property (readonly, copy) NSArray
*dependencies; - 在当前
操作
开始执行之前完成执行
的所有操作对象数组
- 在当前
操作依赖应用场景
有两个操作A操作
和B操作
, 要求A
执行完毕后, B
才能执行
// 创建自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 创建操作
NSInvocationOperation *operationA = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"校长"];
// 创建操作
NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation B%@", [NSThread currentThread]);
}];
// 添加依赖
// 要求: A执行完毕, 再执行B , 那就是B依赖于A的完成
// 调用者 : 当前操作
// 参数 : 被依赖对象
// 这个方法的意思就是调用者依赖于参数的完成
[operationB addDependency:operationA];
// 添加操作到队列中
[queue addOperation:operationA];
[queue addOperation:operationB];
控制台打印
[56978:18909531] task1 --- {number = 3, name = (null)}
[56978:18909531] task1 --- 校长
[56978:18909530] operation B{number = 4, name = (null)}
可以发现, 通过添加依赖, Operation A
先执行, 然后再执行Operation B
NSOperation优先级
NSOperation
提供了NSOperationQueuePriority
队列优先级, 用来设置同一队列中的操作优先级
.
// 从低到高排列
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0, // 默认优先级
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
可以通过[operationA setQueuePriority:NSOperationQueuePriorityLow];
改变操作的优先级状态.
- 什么是就绪状态?
- 没有需要依赖的操作等待执行的状态叫做就绪状态. 有依赖的操作都不是准备就绪状态下的操作.
-
queuePriority
属性决定了进入准备就绪状态下的操作之间的执行顺序, 并且, 优先级不能取代依赖关系
NSOperation, NSOperationQueue线程间的通信
// 创建自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
// 异步执行耗时操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2];
NSLog(@"异步线程---%@", [NSThread currentThread]); // 打印当前线程
}
// 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"回到主线程 --- %@", [NSThread currentThread]);
}];
}];
// 将操作添加到队列中
[queue addOperation:operation];
控制台打印
[58135:19050484] 异步线程---{number = 3, name = (null)}
[58135:19050484] 异步线程---{number = 3, name = (null)}
[58135:19048720] 回到主线程 --- {number = 1, name = main}
NSOperationQueue, NSOperation常用属性和方法总结
NSOPeration常用属性和方法
- 取消操作方法
-
-(void)cancel;
可以取消操作, 实质是标记isCancelled
状态
-
- 判断操作状态方法
-
- (BOOl)isFinished;
判断操作是否已经结束 -
- (BOOL)isCancelled;
判断操作是否已经标记为取消 -
- (BOOL)isExecuting;
判断操作是否正在运行 -
- (BOOL)isReady;
判断操作是否处于准备就绪状态, 这个值和操作的依赖关系相关
-
- 操作同步
-
- (void)waitUntilFinished;
阻塞当前线程, 直到该操作结束, 可用于线程执行顺序的同步. -
- (void)setCompletionBlock:(void (^)(void))block;
,completionBlock
会在当前操作执行完毕时执行completionBlock
-
- (void)addDependency:(NSOperation *)op;
添加依赖 -
- (void)removeDependency:(NSOperation *)op;
移除依赖 -
@property (readonly, copy) NSArray
在当前操作开始执行之前完成执行的所有操作对象数组.*dependencies;
-
NSOperationQueue常用属性和方法
-
取消
暂停
恢复
操作-
- (void)cancelAllOperation;
取消队列内的所有操作 -
- (BOOL)isSuspended;
判断队列是否处于暂停状态, YES为暂停状态, NO为恢复状态 -
- (void)setSuspendes:(BOOL)b;
可设置操作的暂停和回复, YES代表暂停队列, NO代表恢复队列
-
- 操作同步
-
- (void)waitUntilAllOperationAreFinished;
阻塞当前线程, 直到队列中的操作全部执行完毕.
-
- 添加/获取操作
-
- (void_addOperationWithBlock:(void (^)(void))block;
向队列中添加一个NSBlockOperation类型操作对象 -
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
向队列中添加操作数组, wait标志是否阻塞当前线程直到所有操作结束 -
- (NSArray *)operations;
当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除). -
- (NSUInteger)operationCount;
当前队列中的操作数
-
- 获取队列
-
+ (id)currentQueue;
获取当前队列, 如果当前线程不是在NSOperationQueue
上运行则返回nil
. -
+ (id)mainQueue;
获取主队列
-
问题 - 线程和队列又有什么区别
简单来说, 队列
就是用来存放任务
的暂存区
, 而线程
是执行任务的路径
, GCD
将这些存在于队列
的任务
取出来放到相应的线程
上去执行, 而队列
的性质决定了在其中的任务在哪种线程
上执行.