與 GCD 不同的是,Operation Queues 不遵循先進先出的順序。以下是 Operation Queues 和 Dispatch Queues 的不同:
1: 不遵循 FIFO(先進先出):在 Operation Queues 中,你可以設置 operation(操作)的執行優先級,並且可以在 operation 之間添加依賴,這意味着你可以定義某些 operation,使得它們可以在另外一些 operation 執行完畢之後再被執行。這就是爲什麼它們不遵循先進先出的順序。
2: 默認情況下 Operation Queues 是併發執行:雖然你不能將其改成串行隊列,但還是有一種方法,通過在 operation 之間添加相依性來讓 Operation Queues 中的任務按序執行。
3: Operation Queues 是 NSOperationQueue 類的實例,任務被封裝在 NSOperation 的實例中。
NSOperation
任務是以 NSOperation 實例的形式被提交到 Operation Queues 中去的。之前說過 GCD 中任務是以 block 的形式被提交。在這裏也是類似,只不過是需要被綁到 NSOperation 實例中。你可以簡單的將 NSOperation 看作是一套工作任務的整體。
NSOperation 是一個抽象的類,不可以被直接拿來用,所以你只能使用 NSOperation 的子類。
NSBlockOperation - 用這個類來初始化包含一個或多個 blocks 的 operation。該 operation 本身可包含的 block 超過一個,當所有的block 執行完畢後這個 operation 就被視爲已完成。
NSInvocationOperation - 用這個類來初始化一個 operation,能用來調用某指定對象的選擇器(selector)。
首先它可以通過 NSOperation 類的 addDependency(op: NSOperation)方法獲得對相依性的支持。如果你有這樣的需求:即某 operation 的啓動需取決於另一個 operation 的執行,那麼就得用 NSOperation
你可以取消掉某特定隊列中的某個 operation,或者是取消隊列中所有的 operation。
通過調用 NSOperation 類的 cancel() 方法來實現對 operation 的取消。你取消任何 operation 的時候,會是下面三種場景之一:
1.你的 operation 已經完成了,這種情況下 cancel 方法沒有任何效果。
2. 你的 operation 正在被執行的過程中,這種情況下系統不會強制停止你的 operation 代碼,而是將 cancelled 屬性置爲 true。
3. 你的 operation 還在隊列中等待被執行,這種情況下你的 operation 就不會被執行。
4. NSOperation 有3個有用的布爾型屬性:finished,cancelled 和 ready。finished 在 operation 執行完畢後被置爲 true。cancelled 在 operation 被取消後被置爲 true。ready 在 operation 即將被執行時被置爲 true。
6. 所有的 NSOperation 在任務被完成後都可以選擇去設置一段 completion block。NSOperation 的 finished 屬性變爲 true 後這段 block 就會被執行。
func testOperation() {
let queue = OperationQueue()
let imageURLs = ["https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]",
"https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]", "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]","https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]"]
let imageView1 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
let imageView2 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
let imageView3 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
let imageView4 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
print("開始走")
queue.addOperation {
OperationQueue.main.addOperation {
print("一個開始走")
for num in 1..<100 {
print("first %@",num)
}
imageView1.loadingImage(withImageUrlStr: imageURLs[0])
print("一個結束 %@",imageView1.image)
}
}
queue.addOperation {
OperationQueue.main.addOperation {
print("二個開始走")
for num in 1..<100 {
print("second %@",num)
}
imageView2.loadingImage(withImageUrlStr: imageURLs[1])
print("二個結束 %@",imageView2.image)
}
}
queue.addOperation {
OperationQueue.main.addOperation {
print("三個開始走")
for num in 1..<100 {
print("third %@",num)
}
imageView3.loadingImage(withImageUrlStr: imageURLs[2])
print("三個結束 %@",imageView3.image)
}
}
queue.addOperation {
OperationQueue.main.addOperation {
print("四個開始走")
for num in 1..<100 {
print("forth %@",num)
}
imageView4.loadingImage(withImageUrlStr: imageURLs[3])
print("四個結束 %@",imageView4.image)
}
}
print("走完了所以流程")
print("取消上面四個任務")
queue.cancelAllOperations()
}
/*
打印結果:
開始走
走完了所以流程
取消上面四個任務
如果我將 print("取消上面四個任務")
queue.cancelAllOperations()放到一個結束的後面發現,全部都會打印,因爲這時已經把所有的operation加入到了operationQueue中了
*/
如上所見,你使用了 addOperationWithBlock 方法來創建一個新的、帶有某給定 block(或者它在 Swift 中的名字:閉包)的 operation。很簡單,對吧?爲在主隊列中完成某個任務,與使用 GCD 時調用 dispatch_async() 不同,我們用 NSOperationQueue(NSOperationQueue.mainQueue())也可以達到相同結果,將你想要在主隊列中執行的 operation 提交過去。
可以運行一下這個 App 做個快速測試。如果代碼輸入正確的話, App 應該能夠在後臺下載圖片,不會阻塞UI。
之前的例子中我們使用了 addOperationWithBlock 方法把 operation 添加到隊列中。再讓我們來看看如何使用 NSBlockOperation:在達到相同的效果的同時,還會給我們提供更多的功能和選項,比如設置 completion handler。
func testOperation() {
let queue = OperationQueue()
let imageURLs = ["https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]",
"https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]", "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]","https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]"]
let imageView1 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
let imageView2 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
print("開始走")
let operation1 = BlockOperation {
print("一個開始走")
let url = URL(string: imageURLs[0])
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
imageView1.image = UIImage(data: data!)
}
print("一個結束")
}
let operation2 = BlockOperation {
print("二個開始走")
let url = URL(string: imageURLs[1])
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
imageView2.image = UIImage(data: data!)
}
print("二個結束")
}
operation1.addDependency(operation2)
operation1.completionBlock = {
print("operation 1 completed")
}
operation2.completionBlock = {
print("operation 2 completed")
}
queue.addOperation(operation1)
queue.addOperation(operation2)
print("走完了所以流程")
}
/*打印結果
開始走
走完了所以流程
二個開始走
二個結束
一個開始走
operation 2 completed
一個結束
operation 1 completed
*/
不添加依賴正常打印
func testOperation() {
let queue = OperationQueue()
let imageURLs = ["https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]",
"https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]", "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]","https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]"]
let imageView1 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
let imageView2 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
print("開始走")
let operation1 = BlockOperation {
print("一個開始走")
let url = URL(string: imageURLs[0])
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
imageView1.image = UIImage(data: data!)
}
print("一個結束")
}
let operation2 = BlockOperation {
print("二個開始走")
let url = URL(string: imageURLs[1])
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
imageView2.image = UIImage(data: data!)
}
print("二個結束")
}
operation1.completionBlock = {
print("operation 1 completed")
}
operation2.completionBlock = {
print("operation 2 completed")
}
queue.addOperation(operation1)
queue.addOperation(operation2)
print("走完了所以流程")
}
/*
開始走
走完了所以流程
一個開始走
二個開始走
一個結束
operation 1 completed
二個結束
operation 2 completed
*/
由於 operation #1 已經開始執行,取消對它沒有任何效果。這就是爲什麼 cancelled 會被記錄成 false,並且 App 還是會顯示第一張圖片。
operation #2 會被取消。對 cancelAllOperations() 的調用會停止對該 operation 的執行,所以第二張圖片沒有被下載。
operation #3 已經排在隊列中,等待 operation #2 的完成。因爲 operation #3 是否開始取決於 operation #2 的完成與否,而 operation #2 已經被取消,operation #3 就不會被執行,從隊列中被立即踢出了,同理operation#4也不會被執行。
func testOperation() {
let queue = OperationQueue()
let imageURLs = ["https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]",
"https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]", "https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]","https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/topnav/[email protected]"]
let imageView1 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
let imageView2 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
let imageView3 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
let imageView4 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
print("開始走")
let operation1 = BlockOperation {
print("一個開始走")
let url = URL(string: imageURLs[0])
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
imageView1.image = UIImage(data: data!)
}
print("一個結束")
}
let operation2 = BlockOperation {
print("二個開始走")
let url = URL(string: imageURLs[1])
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
imageView2.image = UIImage(data: data!)
}
print("二個結束")
}
let operation3 = BlockOperation {
print("三個開始走")
let url = URL(string: imageURLs[2])
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
imageView3.image = UIImage(data: data!)
}
print("三個結束")
}
let operation4 = BlockOperation {
print("四個開始走")
let url = URL(string: imageURLs[3])
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
imageView3.image = UIImage(data: data!)
}
print("四個結束")
}
peration2.addDependency(operation1)
operation3.addDependency(operation2)
operation4.addDependency(operation3)
operation1.completionBlock = {
queue.cancelAllOperations()
print("Operation 1 completed, cancelled:\(operation1.isCancelled) ")
}
operation2.completionBlock = {
print("operation 2 completed")
}
operation3.completionBlock = {
print("operation 3 completed")
}
operation4.completionBlock = {
print("operation 4 completed")
}
queue.addOperation(operation1)
queue.addOperation(operation2)
queue.addOperation(operation3)
queue.addOperation(operation4)
print("走完了所以流程")
}
GCD和NSOperation的區別:
GCD原理:系統有個線程池,用於分配和回收線程,程序員不需要對線程管理
GCD的高級功能:延遲執行after,一次性執行once(單例),調度組(實現批量處理)
GCD存在四種組合,NSOperation只有全局隊列、異步操作,如果想同步,必須用依賴關係
NSOperation的高級:最大併發數,控制線程個數,優化了線程的暫停、繼續、取消功能(GCD實現起來太難),依賴關係,可以讓異步任務同步執行
- GCD是底層的C語言構成的API,而NSOperationQueue及相關對象是Objc的對象。在GCD中,在隊列中執行的是由block構成的任務,這是一個輕量級的數據結構;而Operation作爲一個對象,爲我們提供了更多的選擇;
- 在NSOperationQueue中,我們可以隨時取消已經設定要準備執行的任務(當然,已經開始的任務就無法阻止了),而GCD沒法停止已經加入queue的block(其實是有的,但需要許多複雜的代碼);
- NSOperation能夠方便地設置依賴關係,我們可以讓一個Operation依賴於另一個Operation,這樣的話儘管兩個Operation處於同一個並行隊列中,但前者會直到後者執行完畢後再執行;
- 我們能將KVO應用在NSOperation中,可以監聽一個Operation是否完成或取消,這樣子能比GCD更加有效地掌控我們執行的後臺任務;
- 在NSOperation中,我們能夠設置NSOperation的priority優先級,能夠使同一個並行隊列中的任務區分先後地執行,而在GCD中,我們只能區分不同任務隊列的優先級,如果要區分block任務的優先級,也需要大量的複雜代碼;
- 我們能夠對NSOperation進行繼承,在這之上添加成員變量與成員方法,提高整個代碼的複用度,這比簡單地將block任務排入執行隊列更有自由度,能夠在其之上添加更多自定製的功能。
- GCD 是嚴格的隊列,先進先出 FIFO;NSOperation可以改動 優先級(或者說服務質量)改變執行順序
NSOperation的高級:最大併發數,控制線程個數,優化了線程的暫停、繼續、取消功能(GCD實現起來太難,可以用 KVO ),依賴關係,可以讓異步任務同步執行
NSOperationQueue暫停&繼續&取消 可以控制操作的執行
NSOperationQueue 暫停和恢復隊列
// YES代表暫停隊列,NO代表恢復隊列
@property (getter=isSuspended) BOOL suspended;
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
_queue.maxConcurrentOperationCount = 2;
}
/**
* 創建多個操作 開始執行
* @param sender
*/
- (IBAction)start:(id)sender {
int count = 15;
for (int i = 0; i < count; i++) {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"i = %d, thread = %@", i, [NSThread currentThread]);
}];
[_queue addOperation:operation];
}
}
/**
* 將隊列掛起,暫停執行隊列中還沒有執行的 操作
* @param sender
*/
- (IBAction)suspended:(id)sender {
if (!_queue.suspended) {
// operationCount 包含正在執行的 操作
NSLog(@"掛起隊列, 當前隊列中還有%zd個任務", _queue.operationCount);
// 掛起任務,沒有執行的操作纔會暫停,已經在執行的操作會繼續執行
_queue.suspended = YES;
}
}
- (IBAction)resume:(id)sender {
if (_queue.suspended) {
NSLog(@"當前隊列中還有%zd個任務", _queue.operationCount);
_queue.suspended = NO;
}
}
注意: 向 掛起 的 隊列 添加 操作, 添加的 操作 是 不會執行 的