iOS多线程方案之NSOperation详解

iOS开发中一共有四种多线程方案:Pthreads、NSThread、GCD、NSOperation。

  1. Pthreads:是线程的 POSIX 标准。该标准定义了创建和操纵线程的一整套 API,是基于 C 语言的框架。
  2. NSThread:是苹果封装后的,完全面向对象的类库。但是线程的生命周期还需要我们手动管理,不常用。
  3. GCD:是苹果为多核并行运算提供的线程方案,它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),基于 C 语言,使用 block,使用方便、灵活。
  4. NSOperation:是苹果对 GCD 的封装,完全面向对象。 NSOperationQueue 可以设置并发队列的依赖关系、执行顺序、最大并发数,更加灵活,解决复杂的线程设计。

GCD和NSOperation对比:

1. GCD更接近底层,处理速度更快;NSOperation是GCD的高级抽象。
2. GCD仅支持FIFO队列,而NSOperationQueue中的队列可以设置优先级、依赖关系、最大并发数。
3. NSOperationQueue支持KVO,我们可以观察任务的执行状态。

NSOperation详解

为什么要使用 NSOperation、NSOperationQueue?
  1. 可添加完成的代码块,在操作完成后执行。
  2. 添加操作之间的依赖关系,方便的控制执行顺序。
  3. 设定操作执行的优先级。
  4. 可以很方便的取消一个操作的执行。
  5. 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。
任务和队列

和GCD一样,NSOperation 中也有类似的任务(操作) 和 队列(操作队列) 的概念。

  1. 操作(NSOperation):NSOperation 只是一个抽象类,所以要使用子类 NSInvocationOperation、NSBlockOperation 来封装操作
  2. 操作队列(NSOperationQueue):指用来存放操作的队列,可以设置操作的优先级、操作的依赖关系和最大并发数;NSOperationQueue 提供了两种队列:主队列和自定义队列,主队列运行在主线程之上,而自定义队列在后台执行。

用NSOperation 实现多线程步骤

  1. 创建操作:先将需要执行的操作封装到一个 NSOperation 对象中
  2. 创建队列:创建 NSOperationQueue 对象
  3. 将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中
/**
 * 基础实现方式
 */
- (void)addOperationToQueue {
    // 获取主队列(凡是添加到主队列的操作,都会放到主线程中执行)一般不用
    //NSOperationQueue *queue = [NSOperationQueue mainQueue];
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 使用 NSInvocationOperation 创建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    
    // 使用 NSBlockOperation 创建操作2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    }];
    // 为 NSBlockOperation 添加额外操作
    [op2 addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    }];

    // 使用 addOperation 添加所有操作到队列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
}
/**
 * 简单实现方式(无需创建操作,直接block中添加操作)
 */
- (void)addOperationWithBlockToQueue {
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 使用 addOperationWithBlock 添加操作到队列中
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    }];
    // 使用 addOperationWithBlock 添加操作到队列中
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    }];
}
操作依赖

NSOperationQueue 能添加操作之间的依赖关系,使我们可以很方便的控制操作之间的执行先后顺序

/**
 * 操作依赖
 */
- (void)addDependency {
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 创建操作1
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    }];
    // 创建操作2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    }];

    // 添加依赖关系
    [op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2

    // 添加操作到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}
最大并发操作数(maxConcurrentOperationCount)

maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而一个操作也并非只能在一个线程中运行。

  • 默认情况下为-1,表示不限制,可进行并发执行。
  • 为1时,队列为串行队列,只能串行执行。
  • 大于1时,队列为并发队列,操作并发执行。当然这个值不应超过系统限制,即使自己设置一个很大的值,也会是系统的默认最大值。
优先级(queuePriority)

queuePriority 属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal,但我们可以通过 setQueuePriority 方法来改变当前操作在同一队列中的执行优先级。

  • queuePriority 属性设置了进入准备就绪状态下的操作(无依赖)之间的开始执行顺序。优先级不能取代依赖关系。
  • 如果一个队列中既包含高优先级操作,又包含低优先级操作,并且两个操作都已经准备就绪,那么队列先执行高优先级操作。
  • 如果一个队列中既包含了准备就绪状态的操作,又包含了未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作的优先级高。那么,虽然准备就绪的操作优先级低,也会优先执行。
线程安全

线程安全:可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。
iOS 实现线程加锁有很多种方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock 等各种方式。
下面介绍一下使用 NSLock 对象来解决线程同步问题。NSLock 对象可以通过进入锁时调用 lock 方法,解锁时调用 unlock 方法来保证线程安全。

self.ticketSurplusCount = 50;
self.lock = [[NSLock alloc] init];  // 初始化 NSLock 对象
    
// 售卖火车票(线程安全)
 - (void)saleTicketSafe {
    while (1) {
        // 加锁
        [self.lock lock];
        
        if (self.ticketSurplusCount > 0) {
            //如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }
        // 解锁
        [self.lock unlock];

        if (self.ticketSurplusCount <= 0) {
            NSLog(@"所有火车票均已售完");
            break;
        }
    }
}
NSOperation、NSOperationQueue常用属性和方法

NSOperation

  • BOOL executing; //判断任务是否正在执行
  • BOOL finished; //判断任务是否完成
  • void (^completionBlock)(void); //用来设置完成后需要执行的操作
  • (void)cancel; //取消任务
  • (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕

NSOperationQueue

  • NSUInteger operationCount; //获取队列的任务数
  • (void)cancelAllOperations; //取消队列中所有的任务
  • (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕
  • [queue setSuspended:YES]; // 暂停queue
  • [queue setSuspended:NO]; // 继续queue
  • (id)currentQueue; 获取当前队列
使用KVO观察任务执行状态

NSOperation 有三种状态:isReady -> isExecution -> isFinish。

  • isReady: 返回 YES 表示操作已经准备好被执行。
  • isExecuting: 返回YES表示操作正在执行。
  • isFinished : 返回YES表示操作执行成功或者被取消了。

如果在 Ready 的状态中对 NSOperation 进行取消,NSOperation 会立刻进入 Finish 状态。
但如果操作已经在Executing状态,就会一直运行到结束,我们调用cancle方法系统不会中止线程,这需要我们在任务过程中检测取消事件,并中止线程的执行,还要注意释放内存或资源。

- (IBAction)startOperation:(id)sender {

    self.blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    
        if ([self.blockOperation isCancelled]) {
            NSLog(@"取消了");
            return;
        }
        // 如果检测还没取消
        // 继续请求网络,获取数据..
        if ([self.blockOperation isCancelled]) {
            NSLog(@"取消了");
            return;
        }   
        // 如果检测还没取消
        // 继续将获取到了数据刷新界面...
        }];
    }
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:self.blockOperation];
}

参考资料:
苹果官方指南-Operation Queues
苹果官方文档-NSOperation
关于iOS多线程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值