Operation (P3)

by Hoang Anh Tuan
448 views

Đến với phần cuối của loạt bài viết về Operation nhưng cũng không kém phần quan trọng, mình sẽ nói về Async Operation và Cancel Operation.

Nội dung bài viết:

  • Cancel Operation
  • Async Operation
  • Demo

Cancel Operation

  • Cách dùng khá đơn giản, chỉ cần gọi cancel() để cancel 1 operation. Tuy nhiên, 1 operation chỉ có thể bị cancel trước khi nó được bắt đầu được thực hiện.
  • Bản chất của việc gọi cancel là sẽ set state của operation thành isCancelled = true.
operation.cancel()

Cancel toàn bộ operation:

Để cancel toàn bộ các operations trong 1 operation queue, chỉ cần gọi:

operationQueue.cancelAllOperations()

Note: Chỉ cancel những task chưa được thực hiện

Vậy làm thế nào có thể cancel 1 operation đang thực hiện? Câu trả lời là sẽ kiểm tra state isCancelled trong thân hàm.
Hãy xem ví dụ ở cuối bài để hiểu thêm.

Async operation

1 Operation nếu được khởi tạo default thì sẽ hoạt động theo kiểu synchronous. 1 vòng đời của operation khi đó theo các state là: isReady -> isExecuting -> isFinished.

Nhưng nếu bạn muốn các operation của bạn hoạt động theo kiểu asynchoronous bởi diều đó chắc chắn sẽ khiến app của bạn họat động nhanh hơn?
Điều đó là hoàn toàn có thể, tuy nhiên nếu operation hoạt động kiểu async, nó sẽ trả về quyền điều khiển ngay lập tức (xem lại bài GCD part 1). Vì vậy, state của operation đó sẽ trở thành isFinished ngay lập tức. Do đó, bạn sẽ phải làm thêm 1 vài việc để custom lại state của operation nếu bạn muốn operation đó chạy theo kiểu async.

Cách làm ở đây về cơ bản là sẽ viết 1 async operation subclass để quản lí state, và giao tiếp với lớp cha Operation của nó thông qua KVO.
Nghe có vẻ khá dài, nhưng đừng lo lắng, chỉ cần làm 1 lần thôi, lần sau bạn sẽ chỉ cần gọi và dùng.

class AsyncOperation: Operation {
    // State enumaration
    enum State: String {
        case ready, excuting, finished

        // 1
        fileprivate var keyPath: String {
            return "is" + rawValue.capitalized
        }
    }
    // State property
    // 2
    var state: State = .ready {
        // 3
        willSet {
            debugPrint("New value \(newValue)")
            willChangeValue(forKey: newValue.keyPath)
            debugPrint("Will change value 1 for key \(newValue.keyPath)")
            willChangeValue(forKey: state.keyPath)
            debugPrint("Will change value 2 for key \(state.keyPath)")
        }
        didSet {
            debugPrint("old value \(oldValue)")
            didChangeValue(forKey: oldValue.keyPath)
            debugPrint("Did change value 1 for key \(oldValue.keyPath)")
            didChangeValue(forKey: state.keyPath)
            debugPrint("Did change value 2 for key \(state.keyPath)")
        }
    }
}
  1. State của operation default sẽ là ready
  2. keyPath là 1 computed property sẽ giúp bạn lấy state hiện tại của operation.
  3. Bởi vì bạn cần gửi các notification khi bạn thay đổi state, bạn sẽ dùng didSet và willSet để hứng notification. -> Trong nhiều trường hợp có thể bạn sẽ không cần dùng đến, nhưng nên khai báo để dễ dàng quan sát, debug.

Base Properties

extension AsyncOperation {
    // 1
    open override var isReady: Bool {
        return super.isReady && state == .ready
    }

    open override var isExecuting: Bool {
        return state == .excuting
    }


    open override var isFinished: Bool {
        return state == .finished
    }
    // 2
    open override var isAsynchronous: Bool {
        return true
    }
    // 3
    open override func start() {
        // 4
        if isCancelled {
            state = .finished
            return
        }

        main()
    }

    open override func cancel() {
        super.cancel()
        state = .finished
    }
}
  1. override lại các state vì bạn muốn tự quản lí state cho riêng mình.
  2. set thuộc tính isAsynchronous thành true để operation chạy async.
  3. override lại func start(). Khi đưa 1 operation vào queue, queue sẽ gọi start để thực hiện chạy 1 operation.
  4. Kiểm tra trước khi thực hiện xem operation này đã bị cancel chưa, nếu chưa thì sẽ bắt đầu thực hiện.

Note: Không bao giờ gọi super.start() trong hàm start().
Refer: https://developer.apple.com/documentation/foundation/operation/1416837-start

Custom 1 Operation

Mình sẽ custom 1 opeartion dùng để download ảnh, và check state isCancelled trong thân hàm để có thể dừng 1 operation đang chạy.

  1. Override lại hàm main cho operation. Đây là hàm operation sẽ chạy vào khi thực được gọi để thực hiện.
  2. set state thành .executing khi bắt đầu thực hiện operation.
  3. Luôn nhớ set state thành .finished khi operation được thực hiện xong
  1. check nếu operation đã bị cancel thì sẽ không download image.
  2. check nếu opeartion đã bị cancel thì sẽ không convert data thành image.
  3. Ở đây bạn có thể check nếu operation đã bị cancel thì không hiển thị image cũng được, tuy nhiên mình nghĩ download xong image rồi thì hiển thị cũng ok.

Kết luận:

  • Operation thích hợp để dùng hơn GCD khi những task của bạn đòi hỏi sự kiểm soát state, hoặc những task có tính reuseable cao. Còn với những task đơn giản, hoặc không cần reuse nhiều thì có thể dùng GCD.
  • Operation là API bậc cao hơn GCD, nên cách dùng sẽ khó hiểu hơn, nên hãy cẩn trọng khi kiểm soát state của các custom Operation.

Tham khảo: Ray wenderlich
End

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.