NSOperation

NSOperation

上一篇文章记录了GCD的使用,各种用法都有提到了,现在再写一篇笔记记录一下NSOperation的使用!
首先附上别人制作的一副图片:
iOS四种线程对比
网上的资料很多,细节的东西百度即可得,此文重点以简单的代码作为笔记。

1、子类 BlockOperation

(1)单独使用BlockOperaton

代码:

// MARK: - BlockOperation
func blockOperation() {
    let op = BlockOperation {
        print(Thread.current)
        for i in 0...2 {
            print("\(i) -> **************")
            // 模拟耗时
            Thread.sleep(forTimeInterval: 1)
        }
    }
    op.start()
}

结果:
测试结果

划重点:

  • 仅使用BlockOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。
  • 代码是在主线程中调用的,所以打印结果为主线程。如果在其他线程中执行操作,则打印结果为其他线程。
(2)addExecutionBlock

在(1)的基础上,添加addExecutionBlock操作。
代码:

// MARK: - BlockOperation + addExecutionBlock
func blockOperation() {
    let op = BlockOperation {
        for i in 0...2 {
            print("\(i) -> *************** ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    op.addExecutionBlock {
        for i in 0...2 {
            print("\(i) -> +++++++++++++++ ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    op.addExecutionBlock {
        for i in 0...2 {
            print("\(i) -> --------------- ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    op.addExecutionBlock {
        for i in 0...2 {
            print("\(i) -> >>>>>>>>>>>>>>> ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    op.start()
}

结果:
添加addExecutionBlock打印结果

划重点:

BlockOperation初始化时的block操作和addExecutionBlock的block操作是在不同的线程执行的。虽说前者仍然是在主线程调用,且打印线程信息也为主线程,但是根据其它资料显示,初始化时的block线程并不确定。

一般情况下,如果一个 BlockOperation 对象封装了多个操作。BlockOperation 是否开启新线程,取决于操作的个数。如果添加的操作的个数多,就会自动开启新线程。当然开启的线程数是由系统来决定的。

2、创建队列

  • 获取主队列
// swift获取主队列
let queue: OperationQueue = OperationQueue.main
// OC获取主队了
NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 创建自定义队列
// swift创建自定义队列
let queue: OperationQueue = OperationQueue()
// OC创建自定义队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

3、添加任务到队列

(1)主队列添加任务

代码:

// MARK: - 主队列添加任务
func mainQueue() {
    let queue: OperationQueue = OperationQueue.main
    queue.addOperation {
        for i in 0...2 {
            print("\(i) -> *************** ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    queue.addOperation {
        for i in 0...2 {
            print("\(i) -> >>>>>>>>>>>>>>> ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
}

结果:
主队列添加任务

划重点:

主队列中添加任务,所有任务都会在主线程中执行,且为同步串行执行。

(2)添加任务到自定义队列

代码:

// MARK: - 添加任务到自定义队列
func addTaskToQueue() {
    let queue: OperationQueue = OperationQueue()
    queue.addOperation {
        for i in 0...2 {
            print("\(i) -> *************** ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    queue.addOperation {
        for i in 0...2 {
            print("\(i) -> >>>>>>>>>>>>>>> ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    // 另一种添加任务的写法
    let op = BlockOperation {
        for i in 0...2 {
            print("\(i) -> +++++++++++++++ ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    queue.addOperation(op)
}

结果:
添加任务到自定义队列打印结果

划重点:

  • 添加到自定义队列中的操作,会自动在子线程中执行。

  • 本例默认为并发执行,也可串行执行

4、串行、并发控制

上个例子中我们看到了自定义队列添加任务时的并发执行。那么如何控制串行执行呢?
这时我们就要用到队列OperationQueue的一个属性maxConcurrentOperationCount

提前划重点:

maxConcurrentOperationCount,叫做最大并发操作数。用来控制一个特定队列中可以有多少个操作同时参与并发执行。

  • maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
  • maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
  • maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min(自己设定的值,系统设定的默认最大值)。
  • 任务数量超出设置的最大并发操作数量时,其它任务等待执行,且有其它任务完成后再加入执行。
  • maxConcurrentOperationCount 为负数,且不为-1时,会崩掉。

代码:

// MARK: - 队列任务串行
func serialQueue() {
    let queue: OperationQueue = OperationQueue()
    // 设置为1时,串行
    // 大于1时,并发
    // 默认为 -1,且不可设置为其它负数
    queue.maxConcurrentOperationCount = 1
    // 另一种添加任务的写法
    let op1 = BlockOperation {
        for i in 0...2 {
            print("\(i) -> *************** ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    let op2 = BlockOperation {
        for i in 0...2 {
            print("\(i) -> +++++++++++++++ ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    let op3 = BlockOperation {
        for i in 0...2 {
            print("\(i) -> >>>>>>>>>>>>>>> ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    queue.addOperation(op1)
    queue.addOperation(op2)
    queue.addOperation(op3)
}

执行结果:
最大并发数量设置为1时的打印结果

我们在贴上一个queue.maxConcurrentOperationCount = 2 时的打印结果:
这里写图片描述
该情况是queue.maxConcurrentOperationCount 小于 任务操作数,此时并发执行两个操作,当有空余资源时,再执行第三个。

5、线程依赖

Operation 的封装使用起来很简单,所以我们直接上代码:

// MARK: - 添加任务依赖
func addDependency() {
    let queue: OperationQueue = OperationQueue()
    // 创建操作任务
    let op1 = BlockOperation {
        for i in 0...2 {
            print("\(i) -> *************** ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    let op2 = BlockOperation {
        for i in 0...2 {
            print("\(i) -> +++++++++++++++ ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    let op3 = BlockOperation {
        for i in 0...2 {
            print("\(i) -> >>>>>>>>>>>>>>> ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
    }
    // 添加任务依赖
    op1.addDependency(op2)
    op2.addDependency(op3)
    // 任务添加到队列执行
    queue.addOperation(op1)
    queue.addOperation(op2)
    queue.addOperation(op3)
}

贴一张打印结果:
添加依赖的打印结果

6、操作任务优先级

  • 优先级取值
// 由上到下优先级依次变高
// swift
public enum QueuePriority : Int {
    case veryLow
    case low
    case normal
    case high
    case veryHigh
}
// OC
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

长文本内容需耐心阅读加深理解,下方概念性文本参考该文章
Operation 的 queuePriority属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal。但是我们可以通过setQueuePriority:方法来改变当前操作在同一队列中的执行优先级。

上边我们说过:对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。

那么,什么样的操作才是进入就绪状态的操作呢?

当一个操作的所有依赖都已经完成时,操作对象通常会进入准备就绪状态,等待执行。

划重点:

  • queuePriority 属性决定了进入准备就绪状态下的操作之间的开始执行顺序。并且,优先级不能取代依赖关系。
  • 如果一个队列中既包含高优先级操作,又包含低优先级操作,并且两个操作都已经准备就绪,那么队列先执行高优先级操作。
  • 如果,一个队列中既包含了准备就绪状态的操作,又包含了未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高。那么,虽然准备就绪的操作优先级低,也会优先执行。
  • 优先级不能取代依赖关系。如果要控制操作间的启动顺序,则必须使用依赖关系。

7、异步操作后返回主线程(线程通信)

在GCD中我们经常在异步执行完某个任务后,返回主线程刷新UI操作,如:

// 异步操作后返回主线程
func communication_GCD() {
    DispatchQueue.global().async {
        for i in 0...2 {
            print("\(i) -> *************** ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
        DispatchQueue.main.async {
            print("回到主线程")
        }
    }
}

在Operation中同样也会经常用到:

// 异步操作后返回主线程
func communication() {
    let queue: OperationQueue = OperationQueue()
    queue.addOperation {
        for i in 0...2 {
            print("\(i) -> *************** ", Thread.current)
            Thread.sleep(forTimeInterval: 1)
        }
        OperationQueue.main.addOperation({
            print("回到主线程")
        })
    }
}

附上结果打印图一张:
异步执行返回主线程

8、线程锁

提到线程锁,经常会拿售票举例。那好,我们回顾理解一下。

(1)没有线程锁操作
/// 剩余票数
var surplusTicketCount: Int = 100
// 写三个线程模拟卖票的窗口
func saleTicket() {
    // 第一个售票窗口
    let queue_1 = OperationQueue()
    queue_1.maxConcurrentOperationCount = 1
    queue_1.addOperation {
        self.saleAction()
    }

    // 第二个售票窗口
    let queue_2 = OperationQueue()
    queue_2.maxConcurrentOperationCount = 1
    queue_2.addOperation {
        self.saleAction()
    }

    // 第三个售票窗口
    let queue_3 = OperationQueue()
    queue_3.maxConcurrentOperationCount = 1
    queue_3.addOperation {
        self.saleAction()
    }
}

// 计票操作
func saleAction() {
    while true {
        if surplusTicketCount > 0 {
            //如果还有票,继续售卖
            surplusTicketCount -= 1
            print("剩余票数 ---> \(surplusTicketCount)   线程:\(Thread.current)")
            // 模拟卖一张票的耗时
            Thread.sleep(forTimeInterval: 0.1)
        } else {
            print("火车票卖完了!!!")
            break;
        }
    }
}

售卖结果:
无线程锁的售票
很明显有重复售票的情况

(2)加入线程锁售票
/// 剩余票数
var surplusTicketCount: Int = 100
func saleTicket() {
    // 第一个售票窗口
    let queue_1 = OperationQueue()
    queue_1.maxConcurrentOperationCount = 5
    queue_1.addOperation {
        self.saleAction()
    }

    // 第二个售票窗口
    let queue_2 = OperationQueue()
    queue_2.maxConcurrentOperationCount = 5
    queue_2.addOperation {
        self.saleAction()
    }

    // 第三个售票窗口
    let queue_3 = OperationQueue()
    queue_3.maxConcurrentOperationCount = 5
    queue_3.addOperation {
        self.saleAction()
    }

}

// 线程锁
let lock = NSLock()
func saleAction() {
    while true {
        lock.lock()
        if surplusTicketCount > 0 {
            //如果还有票,继续售卖
            surplusTicketCount -= 1
            print("剩余票数 ---> \(surplusTicketCount)   线程:\(Thread.current)")
            Thread.sleep(forTimeInterval: 0.1)
        }
        lock.unlock()
        if surplusTicketCount <= 0 {
            print("火车票卖完了!!!")
            break;
        }
    }
}

售票结果:
加入线程锁后的售票结果(部分打印结果)
代码中我们将每个队列的最大并发数量设置为了5,即使这样也没有了重复售票的情况。

9、任务取消、继续和状态

(1) NSOperation 常用属性和方法
  • 取消操作方法

    - (void)cancel; 可取消操作,实质是标记 isCancelled 状态。
    判断操作状态方法
    - (BOOL)isFinished; 判断操作是否已经结束。
    - (BOOL)isCancelled; 判断操作是否已经标记为取消。
    - (BOOL)isExecuting; 判断操作是否正在在运行。
    - (BOOL)isReady; 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。

  • 操作同步

    - (void)waitUntilFinished; 阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。
    - (void)setCompletionBlock:(void (^)(void))block; completionBlock 会在当前操作执行完毕时执行 completionBlock。
    - (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
    - (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
    @property (readonly, copy) NSArray

(2) NSOperationQueue 常用属性和方法
  • 取消/暂停/恢复操作

    - (void)cancelAllOperations; 可以取消队列的所有操作。
    - (BOOL)isSuspended; 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。
    - (void)setSuspended:(BOOL)b; 可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。

  • 操作同步

    - (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕。

  • 添加/获取操作`

    - (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象。
    - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
    - (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。
    - (NSUInteger)operationCount; 当前队列中的操作数。
    获取队列
    + (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
    + (id)mainQueue; 获取主队列。
    注意:

    这里的暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
    暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值