ShayneChow +
微博 Github Twitter

iOS多线程 -- NSOperation(一)

NSOperation的作用

配合使用NSOperationNSOperationQueue也能实现多线程编程

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

NSOperation的具体使用

NSOperation的子类

NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类。

使用NSOperation子类的方式有3种:

NSInvocationOperation

- (void)invocation {
    // 注意: 父类不具备封装操作的能力
    //    NSOperation *op = [[NSOperation alloc] init];
    
    // 1.封装任务
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    // 2.要想执行任务必须调用start
    [op1 start];
    
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
    [op2 start];
}

- (void)run {
    NSLog(@"%@", [NSThread currentThread]);
}

- (void)run2 {
    NSLog(@"%@", [NSThread currentThread]);
}

输出结果如下:

<NSThread: 0x7fa2c1e15700>{number = 1, name = main}
<NSThread: 0x7fa2c1e15700>{number = 1, name = main}

NSBlockOperation

- (void)blockOperation {
    //1. 封装任务
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        // 主线程
        NSLog(@"1---%@", [NSThread currentThread]);
    }];
    
    // 2.追加其它任务
    // 注意: 在没有队列的情况下, 如果给BlockOperation追加其它任务, 那么其它任务会在子线程中执行
    [op1 addExecutionBlock:^{
        NSLog(@"2---%@", [NSThread currentThread]);
    }];
    [op1 addExecutionBlock:^{
        NSLog(@"3---%@", [NSThread currentThread]);
    }];
    
    // 3.启动任务
    [op1 start];
}

输出结果如下:

3---<NSThread: 0x7f8d6049dc30>{number = 2, name = (null)}
2---<NSThread: 0x7f8d6071bec0>{number = 3, name = (null)}
1---<NSThread: 0x7f8d60508df0>{number = 1, name = main}

NSOperationQueue

NSOperationQueue的作用

// 添加操作到NSOperationQueue中的方法
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;

下面使用NSOperationQueue实现多线程操作:

首先创建一个继承自NSOperation的类ZXOperation

// ZXOperation.h

#import <Foundation/Foundation.h>

@interface ZXOperation : NSOperation

@end
// ZXOperation.m

#import "ZXOperation.h"

@implementation ZXOperation

/*
 只要将任务添加到队列中, 那么队列在执行自定义任务的时候
 就会自动调用main方法
 */
- (void)main {
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
}

@end
// ViewController.m

#import "ZXOperation.h"

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 2.创建任务
    // 自定义任务的好处: 提高代码的复用性
    ZXOperation *op1 = [[ZXOperation alloc] init];
    ZXOperation *op2 = [[ZXOperation alloc] init];
    
    // 3.添加任务到队列
    [queue addOperation:op1];
    [queue addOperation:op2];
}

输出结果如下:

-[ZXOperation main], <NSThread: 0x7f90b9f46ce0>{number = 2, name = (null)}
-[ZXOperation main], <NSThread: 0x7f90b9e0ece0>{number = 3, name = (null)}

maxConcurrentOperationCount最大并发数

关于maxConcurrentOperationCount的翻译,曾经有过激烈地讨论,很多人更喜欢翻译为“最大并发操作数”,因为“最大并发数”给人感觉是指的最大的可并发执行的线程数量,其实在NSOperationQueue中并不然,这里的线程是由系统控制的,子线程数目完全由不得你,所以你设置一个最大值意义不大,这里指的是所有线程中同时执行的操作数总和的最大值。

最大并发数的相关方法:

- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
- (void)opration {
	// 1、创建队列
	NSOperationQueue *queue = [[NSOperationQueue alloc] init];
	// 自己创建的队列默认是并发, 如果设置maxConcurrentOperationCount = 1,就是串行
	// 注意: 不能设置为0, 如果设置为0就不行执行任务\
	//       默认情况下maxConcurrentOperationCount = -1
	//       在开发中并发数最多尽量不要超过5~6条
	queue.maxConcurrentOperationCount = 1;

	// 2.创建任务
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.1];
        NSLog(@"1 == %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.1];
        NSLog(@"2 == %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.1];
        NSLog(@"3 == %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.1];
        NSLog(@"4 == %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.1];
        NSLog(@"5 == %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.1];
        NSLog(@"6 == %@", [NSThread currentThread]);
    }];
}

输出结果如下:

1 == <NSThread: 0x7fbb005177a0>{number = 2, name = (null)}
2 == <NSThread: 0x7fbb0050c910>{number = 3, name = (null)}
3 == <NSThread: 0x7fbb005177a0>{number = 2, name = (null)}
4 == <NSThread: 0x7fbb0050c910>{number = 3, name = (null)}
5 == <NSThread: 0x7fbb0050c910>{number = 3, name = (null)}
6 == <NSThread: 0x7fbb005177a0>{number = 2, name = (null)}

队列的取消、暂停、恢复

// 取消队列的所有操作
- (void)cancelAllOperations;
提示:也可以调用NSOperation的- (void)cancel方法取消单个操作

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

线程间通信

// 线程间通信示例

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 1.创建一个新的队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2.添加任务(操作)
    [queue addOperationWithBlock:^{
        // 2.1在子线程中下载图片
        NSURL *url  = [NSURL URLWithString:@"https://ruby-china-files.b0.upaiyun.com/photo/2014/04e78d8c5f92a2d6bf058ffe60deabfd.png"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        
        // 2.2回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.downloadImageView.image = image;
            self.downloadImageView.contentMode = UIViewContentModeScaleAspectFill;
        }];
    }];
}

NSOperation的其他用法

操作依赖

// 比如一定要让操作A执行完后,才能执行操作B,可以这么写
[operationB addDependency:operationA]; // 操作B依赖于操作A

操作监听

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

// 操作监听的相关方法
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;

自定义NSOperation

自定义NSOperation将利用多图片下载的实例来说明,将单独开博。

0

Opinion

Blog

About