实现多线程的具体步骤
- 先将需要执行的操作封装到一个
NSOperation对象
中 - 然后将
NSOperation对象
添加到NSOperationQueue
中 - 系统自动从
NSOperationQueue
中取出NSOperation对象
放到适合的线程中执行
NSOperation
- NSOperation是个
抽象类
,并不具备封装操作的能力 - 只能通过它的
子类
来使用它的功能 - 有三种方式来使用NSOperation
- NS
Invocation
Operation - NS
Block
Operation -
自定义
继承自NSOperation的操作
- NS
NSInvocationOperation
- 创建NS
Invocation
Operation对象,通过对象方法创建
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
-
必须
手动调用start
方法才开始执行操作 -
默认
情况下,调用了start
方法后并不会
开一条新线程去执行操作,而是在当前线程中同步执行
操作
- (void)invocationOperation
{
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 调用start方法
[op start];
}
- (void)run
{
NSLog(@"------%@", [NSThread currentThread]);
}
NSBlockOperation
- 创建NS
Block
Operation对象,通过类方法创建
+ (id)blockOperationWithBlock:(void (^)(void))block;
- 通过
addExecutionBlock:
方法添加多个操作
- (void)addExecutionBlock:(void (^)(void))block;
-
必须
手动调用start
方法才开始执行操作
- (void)blockOperation
{
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^
{
// 这个任务是在【主线程】
NSLog(@"下载1------%@", [NSThread currentThread]);
}];
// 添加额外的任务(在子线程执行)
[op addExecutionBlock:^{
NSLog(@"下载2------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"下载3------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"下载4------%@", [NSThread currentThread]);
}];
[op start];
}
-
第一个
任务是在当前线程中执行,后续的任务才会在子线程中执行 -
只有
当封装的操作数> 1
,才会异步
执行操作
自定义继承自NSOperation的操作
- 重写
- (void)main
方法,在里面实现想执行的任务逻辑 - 自己创建
自动释放池
,如果操作是在子线程中完成,那么子线程中是无法访问主线程的自动释放池,所以自定Operation需要在外层添加@autoreleasepool{ }
- 合理布局,在适当位置调用
isCancelled
属性检测操作是否已被取消,及时中断操作
- (void)main
{
@autoreleasepool
{
for (NSInteger i = 0; i < 1000; i++)
{
NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
}
// 由于任务一旦开始执行就没办法停止下来
// 苹果官方建议,如果是自定义的Operation,而且内部的执行逻辑很耗时
// 如果外面调用了cancel方法,可以通过在一段耗时逻辑后调用一次isCancelled方法判断操作是否已经取消,以用来中断任务的执行
// 合理布局此方法
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;
}
}
NSOperation设置依赖
- 比如任务A执行完后,才能执行任务B,可以这么设置
// 操作B依赖于操作A
[operationB addDependency:operationA];
-
可以在不同队列的NSOperation之间创建依赖关系
不同队列的NSOperation之间创建依赖关系 - NSOperation之间
不能相互依赖
,比如:A依赖B,B依赖A
- (void)addDependencyTest
{
// 直接创建
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download1----%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download2----%@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download3----%@", [NSThread currentThread]);
}];
// 设置依赖后再添加到队列
// 当op1和op2都执行完才执行op3,但是op1和op2谁先执行完不确定
[op3 addDependency:op1];
[op3 addDependency:op2];
// 【不能】循环依赖
// [op3 addDependency:op1];
// [op1 addDependency:op3];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
- NSOperation
执行完毕后
想做一些收尾
的事情,可以设置completionBlock
属性
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^
{
NSLog(@"download----%@", [NSThread currentThread]);
}];
// 任务执行完毕就会执行这个block
op.completionBlock = ^
{
// 也是在子线程中执行
NSLog(@"op执行完毕后执行---%@", [NSThread currentThread]);
};
NSOperationQueue
- 直接创建
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
- 如果将NSOperation添加到NSOperationQueue中,系统会
自动异步
执行NSOperation中的操作 - 本质上NSOperationQueue内部也是自动调用了NSOperation的
start
方法来执行任务 - 同时包含了
串行 和 并行
功能
添加操作到NSOperationQueue中方法
- (void)addOperation:(NSOperation *)op;
// 推荐使用此种方式添加任务
- (void)addOperationWithBlock:(void (^)(void))block;
最大并发数
- 同时执行的最大任务数
- 当最大并发数为
1
的时候就是串行队列
,但所有任务不一定
在同一个
线程上执行完,可能在多个
线程上执行,但一定是按顺序
执行 - 最大并发数设置方式
// 调用示例
queue.maxConcurrentOperationCount = 1;
暂停和恢复队列:suspended
- 暂停后
还未执行
的任务将不会被执行,但是已经开始
的任务是不能
立即停下来了
// 调用示例
queue.suspended = !self.queue.suspended;
- suspended的应用场景:队列正在执行一些耗时操作,当用户执行滚动操作时,为了用户体验可以调用这个属性先让队列中的任务暂停执行,以用来提高用户体验
取消队列的所有操作:cancelAllOperations
- 取消队列中的所有任务,会将队列中的任务全部移除
- 这个方法内部其实是调用了NSOperation中的
cancel
方法 - 所以可以通过NSOperation中的cancel方法来取消
单个
操作 -
只能
取消还未执行
的任务,已经开始
的任务不能取消
,除非任务中主动判断了是否取消操作queue.isCancelled
来中断操作
// 调用示例
[queue cancelAllOperations];
线程间通信
- (void)test
{
[[[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的队列和NSOperationQueue队列对比
GCD的队列
- 并发队列
- 自己创建的(
DISPATCH_QUEUE_CONCURRENT
) - 全局队列
- 自己创建的(
- 串行队列
- 自己创建的(
DISPATCH_QUEUE_SERIAL or NULL
) - 主队列(特殊的串行队列)
- 自己创建的(
NSOperationQueue队列类型
- 主队列
- [NSOperationQueue mainQueue]
- 凡是添加到主队列中的任务(NSOperation),都会放到
主线程
中执行
- 非主队列(其他队列)
- 创建:[[NSOperationQueue alloc] init]
- 同时包含了
串行 和 并行
功能 - 当设置最大并发数为
queue.maxConcurrentOperationCount = 1
时就是串行队列
- 添加到队列中的操作,会
自动
放到适合的线程中执行
网友评论