Cũng giống như GCD, Operations giúp chúng ta có thể thực hiện các task đa luồng. Vì Operations là API bậc cao hơn GCD, do đó, Operations cung cấp nhiều tính năng hơn so với GCD như cung cấp các state để quản lí task, dependency giữa các task,… nhưng cũng vì thế mà để khơi tạo và sử dụng operation sẽ khó hơn so với GCD.
Nếu bạn chưa hiểu về GCD, hãy đọc bài về GCD của mình tại:
Ở phần 1 của bài viết Operation, mình sẽ giới thiệu về tính chất và các thuộc tính của Operation và Operation queue.
Nội dung bài viết
- Operation State
- Block Operation
- Subclass Operation
- Operation Queue
Operation States
- Operation cũng có công dụng như là 1 DispatchWorkItem vậy. Chúng đều là 1 task, bạn khai báo thân hàm cho chúng và đưa chúng vào queue để thực hiện.
- Tuy nhiên, điểm vượt trội của Operation so với DispatchWorkItem là mỗi Operation đều có State của riêng mình, còn workItem thì không.
- isReady: sẵn sàng để được thực hiện
- isExecuting: đang được thực hiện
- isCancelled: Nếu bạn gọi phương thức cancel(), operation sẽ chuyển qua state isCancelled trước khi chuyển sang state isFinished.
- isFinished: Nếu operation k bị cancel, nó sẽ chuyển từ state isExecuting sang isFinished khi hoàn thành.
Note:
- Các state của Operation là các thuộc tính read-only.
- Bạn có thể gọi cancel() để hủy 1 operation, nhưng tương tự như workItem, chúng chỉ có thể bị cancel trước khi được thực hiện. Tuy nhiên, do Operation có state, nên bạn có thể kiểm tra state trong thân hàm để cancel hàm. Mình sẽ làm rõ hơn ở phần sau.
Block Operation
Có thể tạo 1 operation 1 cách nhanh chóng bằng cách sử dụng 1 block operation:
Bạn cũng có thể add thêm nhiều closures vào block operation:
Block Operation hoạt động như một DispatchGroup. Nếu bạn cung cấp 1 completionBlock closure cho blockOperation, thì nó sẽ được thực hiên khi tất cả các closure được add vào block operation đã được thực hiện hết (tương tự hàm notify của DispatchGroup).
Kết quả thu được trên màn hình console:
Note: Từ kết quả thu được ở trên, ta có thể dễ dàng kết luận:
- Task trong block operation chạy concurrent.
- Operation Blocks chạy default trên global queue.
Subclass Operation
BlockOperation rất dễ sử dụng và thích hợp cho các task đơn giản. Tuy nhiên, đối với những task phức tạp cần kiểm soát state, hoặc những task sử dụng nhiều lần, thì bạn nên tự tạo 1 Operation để sử dụng.
- Bạn phải override lại hàm main(), đây là hàm được thực hiện khi operation đó bắt đầu thực hiện.
- Gọi start() để chạy. Tuy nhiên, lưu í rằng, khi gọi start() 1 cách trực tiếp trên 1 operation, Operation đó sẽ chạy theo kiểu sync trên current thread. -> Không nên gọi start trên main thread.
Note: Không chỉ việc gọi start sẽ block current thread, mà nó còn dễ dẫn tới 1 exception rằng task đó chưa sẵn sàng để thực hiện. Vì vậy bạn không nên gọi start ở 1 operation.
Vậy làm cách nào để có thể start 1 operation ? Câu trả lời cho việc này là tương tự như DispatchWorkItem, chúng ta sẽ đưa operation vào Operation Queue để chạy.
Operation Queue:
Bạn đưa các task vào trong operation queue, operation queue sẽ tự động thực hiện task khi thích hợp mà bạn không cần phải gọi start.
- Operation Queue chỉ thực hiện task khi task đó đang ở state isReady.
- Khi bạn add 1 operation vào 1 queue, task đó sẽ chạy cho đến khi hoàn thành hoặc bị hủy (cancel).
- Không thể add 1 operation vào nhiều operation queues khác nhau.
Block queue:
- Operation queue có 1 thuộc tính là waitUntilAllOperationsAreFinished, có tác dụng block queue hiện tại, vì vậy bạn sẽ không nên gọi trên main thread.
- Trong trường hợp bạn không muốn block cả 1 queue chỉ để đợi 1 task thực hiện xong mà bạn chỉ muốn block 1 vài task, bạn có thể gọi addOperations(_:waitUntilFinished:) method on OperationQueue.
Tạm dừng queue:
Bạn có thể tạm dừng operation queue bằng cách set isSuspend = true.
-> Những task đang được thực hiện vẫn sẽ dc tiếp tục thực hiện, nhưng những task chưa dc thực hiện thì sẽ đợi cho đến khi isSuspend được set lại thành false.
Set số lượng operations tối đa
Còn nếu như bạn muốn giới hạn số lượng tối đa operations chạy trong 1 Operation Queue tại 1 thời điểm, chẳng hạn như việc chỉ load những ảnh của những visible cell của collectionView chẳng hạn?
Khi đó, bạn chỉ cần set thuộc tính maxConcurrentOperationCount của Operation Queue thành 1 số bạn muốn.
class DownloadImage: Operation {
override func main() {
// Download image task
print("Start downloading... at time: \(Date().timeIntervalSince1970)")
sleep(5)
print("Finish downloading... at time: \(Date().timeIntervalSince1970)")
}
}
let operation = DownloadImage()
let operation2 = DownloadImage()
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.addOperations([operation, operation2], waitUntilFinished: true)
Nếu set maxConcurrentOperationCount = 1, thì task 1 chạy xong, sau đó task 2 mới chạy. Đây là kết quả trên màn hình console:
Nếu set maxConcurrentOperationCount = 2, thì 2 task sẽ chạy song song. Đây là kết quả trên màn hình console:
Nội dung phần tiếp:
- Dependency Operation
Nguồn tham khảo: Ray Wenderlich