GCD & NSOperation

  • GCD:用來管理隊列,及創造隊列,執行隊列會在另外開一個單獨的線程中執行,而不是在原本創造隊列的線程(如主線程),依需求執行時可選擇Sync同步(會阻塞當下的線程),Async非同步(不會阻塞),另外系統裡面default時會有一個串行,四個並行的隊列可供使用,但注意這些也會被蘋果內部的API做使用,所以隊列中不僅僅只有你的任務而已,當然可以創建自己的隊列。
  • Dispatch 對列可分為兩種:

1.Serial (串行隊列):就是一個會等上一個結束,下一個才進去,有順序(FIFO)先進先出,但較慢,好處就是避免競速發生,也可用串行達到並行的效果,就是開好幾條串行下去跑。

2.Concurrency (並行隊列):就是一次好幾個同時進去,無順序,順序都是系統決定,較快

串行 - dispatch_get_main_queue
主要用來更新畫面中的UI,真的要顯示才用,不然畫面會卡頓
並行 - dispatch_get_global_queue
裡面又有四種優先狀態,high(最高),low,background(最低),default

以上是用內部default的,當然也可以自己創建自己的隊列,如

串行

dispatch_queue_create("com.jojojo.abc", DISPATCH_QUEUE_SERIAL)

<上面的別名是自己取的unique name,debug時就可以看stack去trace那一個是你的序列。>

//補充,若隊列的欄位沒填,默認為串行隊列。
並行
dispatch_queue_create("com.jojojo.abc",DISPATCH_QUEUE_CONCURRENT)
  • DispatchSemaphore 這個沒用過

 

  • DispatchWorkItem

GCD可以用這個來取消隊列的請求,不用用NSOperation的方式了

ref:

https://heisenbean.me/2017/06/A-deep-dive-into-Grand-Central-Dispatch-in-Swift/

https://www.jianshu.com/p/65c333777571

  • DispatchGroup :

前往 Medium.com 檢視

GCD group可以控制非同步的request全部回來後,再處理別的事件,在shareExtension時有用過,主要是用在傳多個檔案的時候,一個傳完才傳下一個,最後再執行關掉shareExtension的畫面。

補充:
可用以下來看當下的thread為什麼
[NSThread currentThread]

NSOperation

  • 介紹:

是一個抽象的類別,但他提供兩種方法可供使用,此外也可繼承他,然後創造新的類別overwrite他原本的方法來做使用。

NSOperation class有兩個subclass: NSBlockOperation 與 NSInvocationOperation。NSBlockOperation可以讓你把一個 block 封裝成 NSOperation,至於 NSInvocationOperation 則是用來封裝 NSInvocation,還有一個NSOperationQueue他是繼承NSObject

 

  • 至於怎麼使用?
 創建實體
 NSBlockOperation *operation = [NSBlockOperation alloc]init];

 之後就可以使用他的實體方法
- (void)addExecutionBlock:(void (^)(void))block; [operation start];

若不創建實體,他也有提供類別方法可供使用

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

不過以上其實都在當前的線程中執行,會佔用到當前的線程,所以需要用到隊列 NSOperationQueue,以下就是把任務加入隊列的方式,就不用特定再寫start了,它內部會自動呼叫。

[NSOperationQueue addOperation:operation];

獲得主序列來更新UI的方式

NSOperationQueue *queue = [NSOperationQueue mainQueue];

或者直接繼承NSOperation類,然後overwrite main,start,cancel等方法,來建立自己的operation。

  • 依賴:

default執行時他屬於並行的模式,但若要達到串行的方式,則可用相互依賴的方法來達成,得到A的結果之後才呼叫B,可用A依賴B的方式,不過不能A依賴B,B又依賴A,會造成死鎖的現象。

增加相依 - (void)addDependency:(NSOperation *)op;
移除相依 - (void)removeDependency:(NSOperation *)op;

 

  • maxConcurrentOperationCount

maxConcurrentOperationCount 這個 property,設定 NSOperationQueue 可以同時平行執行幾件工作,如果超過 1,就代表允許平行執行,如果剛好是 1 的話,就代表在這個 queue 當中的所有工作都會依次執行。預設值是 -1(NSOperationQueueDefaultMaxConcurrentOperationCount),意思是讓系統自己決定最多可以同時建立多少 thread。

 

  • 取消隊列:

可以依照需求來取消某個特定的任務或是全部取消,取消會遇到的三種情況:

  1. 你的 Operation 已經完成。這種情形下, cancel 方法什麼也不做。
  2. 你的 Operation 正在執行。在這種情形下,系統不會強行終止 Operation 中的代碼,但會將 cancelled 屬性設置為 true。
  3. Operation 已經位於隊列中,處於等待執行狀態。在這種情形下,這個 Operation 不會被執行。

所以只有在隊列中,尚未執行的任務,才有機會被取消。

 

  • NSOperation 有三個有用的布爾屬性,分別是 finished、cancelled 和 ready。當 Operation 被執行完,finished 就會設置為 true。當 Operation 被取消,cancelled 就被設置為 true。當 Operation 即將要被執行時,ready 就被設置為 true。

我們可以對 NSOperationQueue 呼叫 addOperation: 加入 operation,用 cancelAllOperations 取消所有排程中的作業。至於已經在執行中的作業,我們就得對特定的 operation 呼叫 cancel

 

  • 任何 NSOperation 對象都可設置一個completeBlock,當任務完成時會調用這個completeBlock。當 finished 屬性一設置為 true 后,立即調用該Block。
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);

 

  • 一些NSOperationQueue小功能
- (void)addOperation:(NSOperation *)op;
NSUInteger operationCount; //獲取隊列的任務數
- (void)cancelAllOperations; //取消隊列中所有的任務
- (void)waitUntilAllOperationsAreFinished; //阻塞當前線程直到此隊列中的所有任務執行完畢
[queue setSuspended:YES]; // 暫停queue
[queue setSuspended:NO]; // 繼續queue

 

 


如何選擇要用NSOperation or GCD ?

取決你的控制要多深入,譬如你是需要網路請求時是可以被取消的>這個現在GCD也可做到了,或是可以控管網路請求的大小數量,用NSOperation也許會比較適合,其餘用GCD就可。

 


專案遇到的問題:

1.競速條件 or 線程安全

大量的cell滑動同時去存取同一個API,因為裡面當時是有一些順序的,先解壓縮後,才能拿資料,目前還未解,要用互斥鎖來解決,同時只准一個tread進來,但好像只會有一個tread,只是會call很多次,所以?

 NSLock

@synchronized

dispatch_semaphore

NSRecursiveLock

NSCondition

NSConditionLock

pthread_mutex

OSSpinLock -> os_unfair_lock (iOS10~)

DispatchQueue.async(flags: .barrier)

ref: https://medium.com/@crafttang/ios-swift%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%8B-%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E6%95%B0%E6%8D%AE%E7%AB%9E%E4%BA%89-data-race-ff085bebac92

ref: http://shoshino21.logdown.com/posts/7818726

 

2.Memory warning

用NSCache 去解決。

3.畫面卡頓

a.因為API裡面是一連串的非同步無限callback 地獄,所以拿掉不必要的mainQueue update,API是一層一層傳下去,資料回來也是一層一層傳回來,當時把每一層回傳結果都寫在主執行緒,造成畫面上的不流暢,只需在最後一個回傳點寫就好。(無效,但不是造成卡頓主原因,但這樣寫才是對的)

   dispatch_async(dispatch_get_main_queue(), ^(){

                completeBlock(data,err);

            });

b.用了NSOperation把任務並行的加入。(無效,只是限制他進來的數量,因為會用到取消cacelAllOperations)

c.最後發現[Lxxxservice shareInstance]someAPI 回來的callback result底下,原來都是在main queue,因為他底層是用AFNetworking寫的,回來的result可能就直接拉到main queue ,所以造成只要用到的地方,都會佔用到主執行緒,這也是會斷斷續續卡頓UI的原因,解法把callback回來的區塊再開一個非同步的queue去串行或並行處理,目前是用快取解決。

      d.[userDefault synchronize]; 同一個cell十筆二十筆同時進來存,會造成卡頓,         目前是先把它block掉,同class存取可以存,不同class則會找不到。

所以一般如果沒有新創立一個序列,而在地進行block的執行,雖說他當下是非同步,但主要的callback還是會在main queue的序列中,所以若callback中處理太複雜UI渲染或運算…等,還是有機率會卡頓。

概念:

https://www.appcoda.com.tw/ios-concurrency/

http://www.jianshu.com/p/0b0d9b1f1f19

https://zonble.gitbooks.io/kkbox-ios-dev/threading/gcd.html

https://objccn.io/issue-2-1/

 

尚未看文章:

GCD 深入理解(一)

并发编程之Operation Queue和GCD

底層並發API

iOS 中幾種常用的鎖總結

semaphore

semaphore

實作:

https://read01.com/QKMm.html

http://www.jianshu.com/p/32dd2c605d95

https://nghiatran.me/advanced-issues-the-right-way-to-load-content-in-backgrounds-thread-with-tableview/

發表留言