在多线程的几个解决方案中,NSOperation 是非常有必要把握的,因为相比较其他比如 GCD、NSThread 之类的,它要灵活的多,甚至可以自定义 NSOperation。

1.运用

了解操作与行列

NSOperation 是基于 GCD 的一层封装,对比 GCD,NSOperation 也有类似的 使命(NSOperation)行列(NSOperationQueue) 的概念。

  • 使命(NSOperation):在 NSOperation 中,使命是放在NSInvocationOperation(使命在 @selector 例)、NSBlockOperation(使命在 block 里),或自定义NSOperation来封装操作。三者都是经过承继 NSOperation 完结使命能力。
  • 行列(NSOperationQueue):把使命增加到行列即可开端履行使命。
    • 使命的履行次第取决于它们的依靠联系优先级联系
    • 行列的串行或并发取决于设置的最大并发操作数(maxConcurrentOperationCount)

下例是 NSBlockOperation 参加履行行列

- (void)blockOperationTest{
    //1:创立blockOperation
    NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    //设置使命完结的监听
    bo.completionBlock = ^{
        NSLog(@"完结了!!!");
    };
    //2:创立行列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //3:增加到行列
    [queue addOperation:bo];   
}

NSInvocationOperation 参加履行行列

- (void)invocationOperationTest{
    //1:创立操作
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
    //2:创立行列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //3:操作参加行列 --- 操作会在新的线程中
    [queue addOperation:op];
}
- (void)handleInvocation:(id)op{
    NSLog(@"%@ --- %@",op,[NSThread currentThread]);
}

串行与并发

NSOperationQueue 有个要害特点maxConcurrentOperationCount,叫做最大并发操作数。用来操控一个特定行列中可以有多少个操作一起参加并发履行。

留意:这儿maxConcurrentOperationCount操控的不是并发线程的数量,而是一个行列中一起能并发履行的最大操作数。

  • maxConcurrentOperationCount默认状况下为-1,表明不进行约束,可进行并发履行。
  • maxConcurrentOperationCount为1时,行列为串行行列。只能串行履行。
  • maxConcurrentOperationCount大于1时,行列为并发行列。操作并发履行,当然这个值不该超过体系约束,即便自己设置一个很大的值,体系也会主动调整为 min。
- (void)testMaxConcurrent{
    self.queue.name = @"bird";
    self.queue.maxConcurrentOperationCount = 2;
    for (int i = 0; i<10; i++) {
        [self.queue addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:1];
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        }];
    }
}

成果如下

0-<NSThread: 0x600001287bc0>{number = 6, name = (null)}
1-<NSThread: 0x6000012db840>{number = 8, name = (null)}
等了1s
3-<NSThread: 0x600001287bc0>{number = 6, name = (null)}
2-<NSThread: 0x6000012df500>{number = 9, name = (null)}
等了1s
5-<NSThread: 0x600001287bc0>{number = 6, name = (null)}
4-<NSThread: 0x6000012df440>{number = 10, name = (null)}
等了1s
7-<NSThread: 0x600001287bc0>{number = 6, name = (null)}
6-<NSThread: 0x6000012df500>{number = 9, name = (null)}
等了1s
8-<NSThread: 0x6000012df440>{number = 10, name = (null)}
9-<NSThread: 0x600001287bc0>{number = 6, name = (null)}

可以看出maxConcurrentOperationCount操控的是最大操作数,而不是并发线程数量。

线程通信

- (void)testMainQueue {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"bird";
    [queue addOperationWithBlock:^{
        NSLog(@"%@ = %@",[NSOperationQueue currentQueue],[NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"%@ --%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
        }];
    }];
}

依靠(addDependency)

如前面所说,经过增加依靠addDependency:可以操控操作的履行次第

- (void)addDependency {
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"恳求token");
    }];
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着token,恳求数据1");
    }];
    NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着数据1,恳求数据2");
    }];
    //因为异步,欠好操控,咱们借助依靠
    [bo2 addDependency:bo1];
    [bo3 addDependency:bo2];
    //留意这儿必定不要构成循环依靠 : 不会报错,可是一切操作不会履行
    //[bo1 addDependency:bo3];
    //waitUntilFinished 堵塞线程
    [self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:NO];
    NSLog(@"履行完了?我要干其他事");
}

成果如下,多次履行,成果都相同

履行完了?我要干其他事
恳求token
拿着token,恳求数据1
拿着数据1,恳求数据2

优先级

NSOperation 的优先级主要是用于协助操作行列调度操作的履行次第,但并不直接操控操作的详细履行时间点。

经过运用操作优先级,可以影响操作在行列中的履行次第,但实践履行时间点还遭到多种因素的影响,比如操作之间的依靠联系、操作的耗时等,所以还需求考虑这些因素来正确地操控操作的履行次第。

NSOperation 的 queuePriority 特点是一个枚举类型的特点,有以下几个选项:

// 优先级的取值
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

以下是一个简略的示例代码,展示如何运用 NSOperation 的优先级特点

/**
 优先级,只会让CPU有更高的几率调用,不是说设置高就必定全部先完结
 */
- (void)demo4{
    NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"第1个操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 设置优先级 - 最高
    bo1.queuePriority = NSOperationQueuePriorityVeryHigh;
    //创立第二个操作
    NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    // 设置优先级 - 最低
    bo2.queuePriority = NSOperationQueuePriorityLow;
    //2:创立行列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //3:增加到行列
    [queue addOperation:bo1];
    [queue addOperation:bo2];
}

成果如下

第二个操作 0 --- <NSThread: 0x6000000c3980>{number = 6, name = (null)}
第1个操作 0 --- <NSThread: 0x6000000c1680>{number = 3, name = (null)}
第二个操作 1 --- <NSThread: 0x6000000c3980>{number = 6, name = (null)}
第1个操作 1 --- <NSThread: 0x6000000c1680>{number = 3, name = (null)}
第二个操作 2 --- <NSThread: 0x6000000c3980>{number = 6, name = (null)}
第1个操作 2 --- <NSThread: 0x6000000c1680>{number = 3, name = (null)}
第二个操作 3 --- <NSThread: 0x6000000c3980>{number = 6, name = (null)}
第1个操作 3 --- <NSThread: 0x6000000c1680>{number = 3, name = (null)}
第二个操作 4 --- <NSThread: 0x6000000c3980>{number = 6, name = (null)}
第1个操作 4 --- <NSThread: 0x6000000c1680>{number = 3, name = (null)}
第二个操作 5 --- <NSThread: 0x6000000c3980>{number = 6, name = (null)}
第1个操作 5 --- <NSThread: 0x6000000c1680>{number = 3, name = (null)}
第1个操作 6 --- <NSThread: 0x6000000c1680>{number = 3, name = (null)}
第二个操作 6 --- <NSThread: 0x6000000c3980>{number = 6, name = (null)}
第二个操作 7 --- <NSThread: 0x6000000c3980>{number = 6, name = (null)}
第1个操作 7 --- <NSThread: 0x6000000c1680>{number = 3, name = (null)}
第二个操作 8 --- <NSThread: 0x6000000c3980>{number = 6, name = (null)}
第1个操作 8 --- <NSThread: 0x6000000c1680>{number = 3, name = (null)}
第二个操作 9 --- <NSThread: 0x6000000c3980>{number = 6, name = (null)}
第1个操作 9 --- <NSThread: 0x6000000c1680>{number = 3, name = (null)}

可以看到第二个操作尽管优先级低,但也不必定就能先完结。

优先级不能替代依靠联系并且依靠联系高于优先级的设置,在确保依靠联系的状况下才会考虑优先级。

其他特点操作

  • 撤销/暂停/康复操作
    • - (void)cancelAllOperations; 可以撤销行列的一切操作。
    • - (BOOL)isSuspended; 判别行列是否处于暂停状况。 YES 为暂停状况,NO 为康复状况。
    • - (void)setSuspended:(BOOL)b; 可设置操作的暂停和康复,YES 代表暂停行列,NO 代表康复行列。
  • 操作同步
    • - (void)waitUntilAllOperationsAreFinished; 堵塞当前线程,直到行列中的操作全部履行完毕。也可以运用addOperations:waitUntilFinished:在增加操作到行列时设置。

线程安全

和 GCD 相同,就是经过给线程加锁完结线程安全。

2.自定义NSOperation

自定义 NSOperation 分为并发和非并发

  • 非并发:非并发很简略,重写 main 办法履行详细使命,当 main 办法完毕,使命也就完毕,不过因为非并发,运用起来意义不大。
// LFOperation.h 文件
#import <Foundation/Foundation.h>
@interface LFOperation : NSOperation
@end
// LFOperation.m 文件
#import "LFOperation.h"
@implementation YSCOperation
- (void)main {
    if (!self.isCancelled) {
        // 履行使命
    }
}
@end
  • 并发:并发需求重写下面几个办法或特点
    • start:把需求履行的使命放在start办法里,使命加到行列后,行列会办理使命并在线程被调度后,调用start办法,不需求调用父类的办法
    • asynchronous:表明是否并发履行
    • executing:表明使命是否正在履行,需求手动调用KVO办法来进行告诉,方便其他类监听了使命的该特点
    • finished:表明使命是否完毕,需求手动调用KVO办法来进行告诉,行列也需求监听该特点的值,用于判别使命是否完毕
// LFOperation.h 文件
#import <Foundation/Foundation.h>
@interface LFOperation : NSOperation
@property (nonatomic, readwrite, getter=isExecuting) BOOL executing; @property (nonatomic, readwrite, getter=isFinished) BOOL finished;
@end
// LFOperation.m 文件
#import "LFOperation.h"
// 因为父类的特点是Readonly的,重载时假如需求setter的话则需求手动合成。 
@synthesize executing = _executing;
@synthesize finished = _finished;
@implementation LFOperation
- (void)start {
    @autoreleasepool {
        @synchronized (self) {
            self.executing = YES;
            if (self.cancelled) {
                [self done];
                return;
            }
            // 使命。。。
        }
        // 使命履行完结,手动设置状况
        [self done];
    }
}
- (void)done {
    self.finished = YES;
    self.executing = NO;
}
#pragma mark - setter -- getter
// 需求手动调用 KVO 办法用来告诉状况的改变
- (void)setExecuting:(BOOL)executing {
    //调用KVO告诉
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    //调用KVO告诉
    [self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isExecuting {
    return _executing;
}
- (void)setFinished:(BOOL)finished {
    if (_finished != finished) {
        [self willChangeValueForKey:@"isFinished"];
        _finished = finished;
        [self didChangeValueForKey:@"isFinished"];
    }
}
- (BOOL)isFinished {
    return _finished;
}
// 回来YES 标识为并发 Operation
- (BOOL)isAsynchronous {
    return YES;
}
@end

在Objective-C中,主动开释池(Autorelease Pool)用来办理暂时目标的开释机遇。在一个主动开释池的作用范围内,经过调用autorelease办法的目标将会在主动开释池被开释时主动调用release办法开释内存。

在平常的开发中,咱们一般会在主线程中履行使命,而主线程会有一个默认的主动开释池。这意味着在主线程中创立的暂时目标所运用的主动开释池会跟随主线程的生命周期。

可是,当咱们在自定义的NSOperation中履行使命的时候,并不会主动创立一个主动开释池,这就意味着在start办法中履行的代码所创立的暂时目标并不会被主动开释,而是会等到主线程的主动开释池被开释时才会被开释。

因而,在start办法中运用@autoreleasepool可以手动创立一个主动开释池,确保start办法履行期间创立的暂时目标可以在合适的时候被及时开释,而不是等到主线程的生命周期完毕才开释,因而运用@autoreleasepool是为了更好地操控内存的生命周期。

3. 知名开源库的NSOperation运用

SDWebImage的自定义NSOperation
YYImage的自定义NSOperation