iOS 多线程

NSOperation

2018-06-03  本文已影响2人  SamCheck

概述

iOS并发编程中,把每个并发任务定义为一个Operation。NSOperation是一个抽象类,无法直接使用,它只定义了Operation的一些基本方法。我们需要创建一个继承于它的子类或者使用系统预定义的子类。

方法结构

NSOperation

把逻辑代码写在NSOperation中,就是把逻辑代码添加一层壳,执行NSOperation就是间接的执行逻辑代码。

  • 用来定义操作对象的基础(抽象)类。处理并发任务时,具体子类通常要重写main、isConcurrent、isExecuting 、isFinished方法。
  • Operation默认都是串行操作(FIFO),默认情况下Operation并不额外创建线程。
- (void)start;
@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;
- (void)main;
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous API_AVAILABLE(macos(10.8), ios(7.0), watchos(2.0), tvos(9.0));
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

下面就是intermediateOperation操作必须等到operation1operation2 完成后才能执行。

[intermediateOperation addDependency:operation1];
[intermediateOperation addDependency:operation2];
@property (nullable, copy) void (^completionBlock)(void) API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

@property NSOperationQueuePriority queuePriority;
isCancelled
isConcurrent
isExecuting
isFinished
isReady
dependencies
queuePriority
completionBlock

NSInvocationOperation

- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;

- (instancetype)initWithInvocation:(NSInvocation *)inv;
@property (readonly, retain) NSInvocation *invocation;
//个Operation完成后返回结果
@property (nullable, readonly, retain) id result;

NSBlockOperation

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;

Operation操作流程

一些公共方法:

-(void)operationComplete {
    NSLog(@"All task finished.");
}

-(void)logOperation:(NSOperation *)op keyPathes:(NSArray *)keyPathes {
    [keyPathes enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%@ %@ = %@",op.name,obj,[[op valueForKey:obj] boolValue]?@"YES":@"NO");
    }];
}

-(void)addObserverForOperation:(id)op keyPathes:(NSArray *)keyPathes {
    [keyPathes enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [op addObserver:self forKeyPath:obj options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL];
    }];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([object isKindOfClass:[NSOperation class]]) {
        NSLog(@"observeValueForKeyPath:%@---%@---%@",[object name],keyPath,change);
    }
}
-(void)operationKeysChange{
    TestBlockOperation * op = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter op");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave op");
    }];
    op.name = @"op";
    NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
    [self logOperation:op keyPathes:keyPathes];
    [self addObserverForOperation:op keyPathes:keyPathes];
op.completionBlock = ^{
        [self operationComplete];
    };
    [op start];
    [op cancel];
}

初始状态下,ready为YES,其他均为NO。
当我们调用 -start 后,执行 -main 之前 isExecuting 属性从NO被置为YES。
调用 -main 之后开始执行提交到Operation中的任务。
任务完成后 isExecuting 属性从YES被置为NO,isFinished 属性从NO被置为YES。

op isReady = YES
op isCancelled = NO
op isExecuting = NO
op isFinished = NO
op before start
observeValueForKeyPath:op---isExecuting---{kind = 1;new = 1;old = 0;}
op before main
enter bp1
leave bp1
/*这里执行业务代码,完成后才进行下面的操作*/
op after main
All task finished.
observeValueForKeyPath:op---isExecuting---{kind = 1;new = 0;old = 1;}
observeValueForKeyPath:op---isFinished---{kind = 1;new = 1;old = 0;}
op after start
op before cancel
op after cancel
-(void)operationKeysChange{
    TestBlockOperation * op = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter op");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave op");
    }];
    op.name = @"op";
    NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
    [self logOperation:op keyPathes:keyPathes];
    [self addObserverForOperation:op keyPathes:keyPathes];
op.completionBlock = ^{
        [self operationComplete];
    };
    [op cancel];
    [op start];
}

先调用 -start ,后调用 -cancel ,isCancelled 属性从NO被置为YES,isReady 属性无论什么状态都会被置为YES。
先调用 -start ,后调用 -cancel ,会将 isFinished 属性从NO被置为YES,然后并不调用 -main 方法。

op isReady = YES
op isCancelled = NO
op isExecuting = NO
op isFinished = NO
op before cancel
observeValueForKeyPath:op---isCancelled---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:op---isReady---{kind = 1;new = 1;old = 1;}
op after cancel
op before start
All task finished.
observeValueForKeyPath:op---isFinished---{kind = 1;new = 1;old = 0;}
op after start
-(void) operationKeysChange {
    TestBlockOperation * bp1 = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter bp1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave bp1");
    }];
    bp1.name = @"bp1";
    bp1.completionBlock = ^{
        NSLog(@"bp1 complete");
    };
    
    TestBlockOperation * bp2 = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter bp2");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave bp2");
    }];
    bp2.name = @"bp2";
    bp2.completionBlock = ^{
        NSLog(@"bp2 complete");
    };
    
    NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
    
    [self logOperation:bp1 keyPathes:keyPathes];
    [self logOperation:bp2 keyPathes:keyPathes];
    [self addObserverForOperation:bp1 keyPathes:keyPathes];
    [self addObserverForOperation:bp2 keyPathes:keyPathes];
    
    NSOperationQueue * q = [NSOperationQueue new];
    [q addOperation:bp1];
    [q addOperation:bp2];
}

NSOperationQueue 执行顺序和NSOperation一样


bp1 isReady = YES
bp1 isCancelled = NO
bp1 isExecuting = NO
bp1 isFinished = NO

bp2 isReady = YES
bp2 isCancelled = NO
bp2 isExecuting = NO
bp2 isFinished = NO

bp1 before start
bp2 before start

observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 1;old = 0;}

bp1 before main
bp2 before main

enter bp1
enter bp2

leave bp1
leave bp2

bp2 after main
bp1 after main

bp2 complete
bp1 complete

observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 0;old = 1;}
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 0;old = 1;}

observeValueForKeyPath:bp1---isFinished---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:bp2---isFinished---{kind = 1;new = 1;old = 0;}

bp1 after start
bp2 after start
-(void) operationKeysChange {
    TestBlockOperation * bp1 = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter bp1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave bp1");
    }];
    bp1.name = @"bp1";
    bp1.completionBlock = ^{
        NSLog(@"bp1 complete");
    };
    
    TestBlockOperation * bp2 = [TestBlockOperation blockOperationWithBlock:^{
        NSLog(@"enter bp2");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"leave bp2");
    }];
    bp2.name = @"bp2";
    bp2.completionBlock = ^{
        NSLog(@"bp2 complete");
    };
    
    NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
    
    [self logOperation:bp1 keyPathes:keyPathes];
    [self logOperation:bp2 keyPathes:keyPathes];
    [self addObserverForOperation:bp1 keyPathes:keyPathes];
    [self addObserverForOperation:bp2 keyPathes:keyPathes];
    
    NSOperationQueue * q = [NSOperationQueue new];
    [bp1 addDependency:bp2];
    [q addOperation:bp1];
    [q addOperation:bp2];
}

当为bp1添加bp2作为依赖以后,bp1的 isReady 属性从YES置为NO。
由于bp2是bp1的依赖,所以优先执行bp2。
在bp2中任务完成之后,-main 方法调用结束之后, -start 方法调用结束之前,bp1调用 -start 并将 isReady 属性置为YES。

bp1 isReady = YES
bp1 isCancelled = NO
bp1 isExecuting = NO
bp1 isFinished = NO

bp2 isReady = YES
bp2 isCancelled = NO
bp2 isExecuting = NO
bp2 isFinished = NO

bp1 before addDependency:
observeValueForKeyPath:bp1---isReady---{kind = 1;new = 0;old = 1;}
bp1 after addDependency:

bp2 before start
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 1;old = 0;}
bp2 before main
enter bp2
leave bp2
bp2 after main
bp1 before start
observeValueForKeyPath:bp1---isReady---{kind = 1;new = 1;old = 0;}
observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 1;old = 0;}
bp2 complete
observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 0;old = 1;}
bp1 before main
enter bp1
observeValueForKeyPath:bp2---isFinished---{kind = 1;new = 1;old = 0;}
bp2 after start
leave bp1
bp1 after main
bp1 complete
observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 0;old = 1;}
observeValueForKeyPath:bp1---isFinished---{kind = 1;new = 1;old = 0;}
bp1 after start
NSOperation流程
@interface SAMOperation : NSOperation
@end
@implementation SAMOperation{
    BOOL isFinished;//监听是否执行结束
    BOOL isExecuting;//监听是否正在执行
}

/*1.自定义初始化方法*/
-(instancetype)init{
    if (self == [super init]) {
        isExecuting = NO;
        isFinished = NO;
    }
    return self;
}
-(void)start{
    //如果被取消了就直接返回结果,不会执行main方法
    if ([self isCancelled]) {
        [self willChangeValueForKey:@"isFinished"];
        isFinished = NO;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }

    //没有被取消,使用独立线程执行main方法中的操作
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    isExecuting = YES;
    [self didChangeValueForKey:@"isExecuting"];
    
}

/*2.自定义辅助方法*/
-(void)main{
    @autoreleasepool{//使用独立的内存释放池,不然会内存泄漏
        @try {
            if (![self isCancelled]) {
                NSLog(@"Begin%@",[NSThread currentThread]);
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"End%@",[NSThread currentThread]);
                //任务结束,修改状态值
                [self willChangeValueForKey:@"isFinished"];
                [self willChangeValueForKey:@"isExecuting"];
                isExecuting = NO;
                isFinished = YES;
                [self didChangeValueForKey:@"isExecuting"];
                [self willChangeValueForKey:@"isFinished"];

            }
        } @catch (NSException *exception) {
        } @finally {
        }
    }
}

-(BOOL)isConcurrent{
    return YES;
}

-(BOOL)isExecuting{
    return isExecuting;
}

-(BOOL)isFinished{
    return isFinished;
}
@end

使用:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"begin func");
    SAMOperation *op1 = [SAMOperation new];
    [op1 start];
    NSLog(@"end func");
}

NSOperationQueue

  • 两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。在两种类型中,这些队列所处理的任务都使用 NSOperation 的子类来表述。
  • NSOperationQueue是一个Operation执行队列,你可以将任何你想要执行的Operation添加到Operation Queue中,以在队列中执行。
  • NSOperationQueue 可以动态的创建多个线程来完成相应Operation,总线程数量通过maxConcurrentOperationCount属性来控制。
  • 当操作添加到队列中,它会待在队列中,直到被显式取消或者执行完为止。
// 主队列队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
// 自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
@property NSInteger maxConcurrentOperationCount;
- (void)cancelAllOperations;

NSOperationQueue 就相当于管道,Operation以FIFO的形式通过管道,maxConcurrentOperationCount 就是管道数量。下面的demo就是限制总管道数量为1,也就是所有的Operation必须以FIFO形式通过管道,也就是串行。

- (void)demo {
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation1-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation2-%@",[NSThread currentThread]);
    }];
    operation2.completionBlock = ^{NSLog(@"operation2-completionBlock-%@",[NSThread currentThread]);};
    
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation3-%@",[NSThread currentThread]);
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue waitUntilAllOperationsAreFinished];
}

执行结果:从结果可以看出,maxConcurrentOperationCount = 1 相当于在addOperation的时候就设置了操作之间的依赖。

operation1-<NSThread: 0x604000270100>{number = 3, name = (null)}
operation2-<NSThread: 0x604000270540>{number = 4, name = (null)}
operation2-completionBlock-<NSThread: 0x604000270100>{number = 3, name = (null)}
operation3-<NSThread: 0x604000270540>{number = 4, name = (null)}

设置queue.maxConcurrentOperationCount = 2;的执行结果

operation2-<NSThread: 0x60400027be80>{number = 4, name = (null)}
operation1-<NSThread: 0x600000474140>{number = 3, name = (null)}
operation3-<NSThread: 0x60400027be80>{number = 4, name = (null)}
operation2-completionBlock-<NSThread: 0x600000474340>{number = 5, name = (null)}
- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- (void)addOperationWithBlock:(void (^)(void))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
@interface SAMOperation : NSOperation
@end
@implementation SAMOperation
-(void)main{
    @autoreleasepool{
        @try {
            if (![self isCancelled]) {
                NSLog(@"Begin");
                [NSThread sleepForTimeInterval:1.0];
                NSLog(@"End");
            }
        } @catch (NSException *exception) {
        } @finally {
        }
    }
}
@end

使用:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"begin func");
    SAMOperation *op1 = [SAMOperation new];
    NSOperationQueue *queue = [NSOperationQueue new];
    [queue addOperation:op1];
    //[queue waitUntilAllOperationsAreFinished];
    NSLog(@"end func");
}

打印

begin func
end func
Begin
End

//把[queue waitUntilAllOperationsAreFinished];注释打开的打印
begin func
Begin
End
end func

线程安全和 线程同步

@interface ViewController ()
@property (nonatomic, assign) NSInteger ticketSurplusCount;
@property (nonatomic, strong)NSLock *lock;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.ticketSurplusCount = 50;
    self.lock = [[NSLock alloc] init];
    
    [self initTicketStatusNotSave];
}

/**
 * 非线程安全:不使用 semaphore
 * 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
 */
- (void)initTicketStatusNotSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    self.ticketSurplusCount = 50;
    
    __weak typeof(self) weakSelf = self;
    
    // queue1 代表北京火车票售卖窗口
    NSOperationQueue *queueO1 = [[NSOperationQueue alloc]init];
    queueO1.maxConcurrentOperationCount = 1;
    
    [queueO1 addOperationWithBlock:^{
        [weakSelf saleTicketNotSafe:@"北京"];
    }];
    // queue2 代表上海火车票售卖窗口
    NSOperationQueue *queueO2 = [[NSOperationQueue alloc]init];
    queueO2.maxConcurrentOperationCount = 1;
    [queueO2 addOperationWithBlock:^{
        [weakSelf saleTicketNotSafe:@"上海"];
    }];
    
    // queue2 代表上海火车票售卖窗口
    NSOperationQueue *queueO3 = [[NSOperationQueue alloc]init];
    queueO3.maxConcurrentOperationCount = 1;
    [queueO3 addOperationWithBlock:^{
        [weakSelf saleTicketNotSafe:@"深圳"];
    }];
}


/**
 * 售卖火车票(非线程安全)
 */
- (void)saleTicketNotSafe:(NSString*)who{
    while (1) {
        [self.lock lock];
        if (self.ticketSurplusCount > 0) {  //如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@====%@", (long)self.ticketSurplusCount,who, [NSThread currentThread]]);
            //[NSThread sleepForTimeInterval:0.2];
            [self.lock unlock];
        } else { //如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
             [self.lock unlock];
            break;
        }
    }
}
@end
上一篇下一篇

猜你喜欢

热点阅读