How to cancel specific Operation from OperationQueue in swift - ios

There are 3 Operations in my OperationQueue and i'm not able to cancel specific operation from them.
I referred this example but i can't understand it
NSOperationQueue cancel specific operations
This is my code
class myOperation1 : Operation {
override func main() {
print("op1 (🐭) working....")
for i in 1...10 {
print("🐭")
}
}
}
class myOperation2 : Operation {
override func main() {
print("op2 (🐶) working....")
for i in 1...10 {
print("🐶")
}
}
}
class myOperation3 : Operation {
override func main() {
print("op3 (🍉) working....")
for i in 1...10 {
print("🍉")
}
}
}
let op1 = myOperation1()
let op2 = myOperation2()
let op3 = myOperation3()
op1.completionBlock = {
print("op1 (🐭) completed")
}
op2.completionBlock = {
print("op2 (🐶) completed")
}
op3.completionBlock = {
print("op3 (🍉) completed")
}
let opsQue = OperationQueue()
opsQue.addOperations([op1, op2, op3], waitUntilFinished: false)
DispatchQueue.global().asyncAfter(deadline: .now()) {
opsQue.cancelAllOperations()
}
Inshort i want to cancel second operation from operationQueue.
Please guide me.
Thank you

you can call op2.cancel() to cancel the operation, but you need to take additional steps to really stop your operation from running as cancel() only set the isCanceled property to true.
Please check the developer document.
https://developer.apple.com/documentation/foundation/operation/1408418-iscancelled
The default value of this property is false. Calling the cancel() method of this object sets the value of this property to true. Once canceled, an operation must move to the finished state.
Canceling an operation does not actively stop the receiver’s code from executing. An operation object is responsible for calling this method periodically and stopping itself if the method returns true.
You should always check the value of this property before doing any work towards accomplishing the operation’s task, which typically means checking it at the beginning of your custom main() method. It is possible for an operation to be cancelled before it begins executing or at any time while it is executing. Therefore, checking the value at the beginning of your main() method (and periodically throughout that method) lets you exit as quickly as possible when an operation is cancelled.

opsQue.cancelAllOperations() in your code cause removing non-started operations from queue and calls Operation.cancel() for each executing operation, but it only set isCancelled to true. You need to handle it explicitly
class myOperation2 : Operation {
override func main() {
print("op2 (🐶) working....")
for i in 1...10 {
if self.isCancelled { break } // cancelled, so interrupt
print("🐶")
}
}
}

Hope you referred to the documentation for Operation
There are several KVO-Compliant Properties for observe operation.
There is one property isCancelled - read-only
used to check this property before the execution of the operation
like this:
class myOperation2 : Operation {
override func main() {
print("op2 (🐶) working....")
if self.isCancelled {
return
}
for i in 1...10 {
print("🐶")
}
}
}
and for cancelation:
DispatchQueue.global().asyncAfter(deadline: .now()) {
opsQue.operations[1].cancel()
}

Related

NSOperation with a delay - is it async or sync?

I'm creating an NSOperation that executes a closure with a delay. The operations are added into a queue, every time before a new operation is added I cancel all existing ones in the queue:
let myOp = SomeOperation { [weak self] in /* do something */ }
queue.cancelAllOperations()
queue.addOperation(myOp)
Operation Code 1
final class SomeOperation: Operation {
private let closure: () -> Void
init(closure: #escaping () -> Void) {
self.closure = closure
}
override func main() {
if isCancelled {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: doSomething)
}
private func doSomething() {
guard isCancelled == false else {
return
}
closure()
}
}
While the above code works, the code below doesn't. In the DispatchQueue closure, self is nil:
Operation Code 2
final class SomeOperation: Operation {
private let closure: () -> Void
init(closure: #escaping () -> Void) {
self.closure = closure
}
override func main() {
if isCancelled {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
guard let self = self else { return }
guard isCancelled == false else { return }
self.closure()
}
}
}
So I'm trying to learn a bit deeper:
In Code 2, self is nil because as soon as DispatchQueue.main.asyncAfter… is called, the main method finishes and the operation is thus released.
Code 1 works because execute: doSomething implicitly captures/retains a self, so even after asyncAfter, self is still there.
So my questions are:
In Apple's doc it says for concurrent operations I should rather be using start, asynchronous, executing, finished, etc. In my case I just need to have a delay, not actually doing anything asynchronous, should I do it in main only or should I do it as an async operation by implementing those methods Apple suggested?
Is my thinking correct that in Code 1 there's a self retained implicitly, which doesn't sound correct and can create retain cycle?
Thanks!
You asked:
In Apple's doc it says for concurrent operations I should rather be using start, asynchronous, executing, finished, etc. In my case I just need to have a delay, not actually doing anything asynchronous, should I do it in main only or should I do it as an async operation by implementing those methods Apple suggested?
First, you are doing something asynchronous. I.e., asyncAfter is asynchronous.
Second, the motivating reason behind Apple’s concurrent operation discussion is that the operation should not finish until the asynchronous task it launched also finishes. You talk about canceling the operation, but that only makes sense if the operation is still running by the time you go to cancel it. This feature, the wrapping the asynchronous task in an object while never blocking a thread, is one of the key reasons we use operations rather than just GCD. It opens the door for all sorts of elegant dependencies between asynchronous tasks (dependencies, cancelation, etc.).
Is my thinking correct that in Code 1 there's a self retained implicitly, which doesn't sound correct and can create retain cycle?
Regarding the strong reference cycle issues, let’s look at your first example. While it is prudent for the creator of the operation to use [weak self] capture list, it should not be required. Good design of the operation (or anything using asynchronously called closures) is to have it release the closure when it is no longer needed:
class SomeOperation2: Operation {
private var closure: (() -> Void)?
init(closure: #escaping () -> Void) {
self.closure = closure
}
override func main() {
if isCancelled {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: doSomething)
}
override func cancel() {
closure = nil
super.cancel()
}
private func doSomething() {
guard !isCancelled else {
return
}
closure?()
closure = nil
}
}
It doesn’t mean that the caller shouldn’t use [weak self] capture list, only that the operation no longer requires it, and will resolve any strong reference cycles when it is done with the closure.
[Note, in the above, I omitted synchronization of the variable, to keep it simple. But you need to synchronize your access to it to ensure thread-safe design.]
But this design begs the question as to why would you would want to keep the asyncAfter scheduled, still firing even after you canceled the operation. It would be better to cancel it, by wrapping the closure in a DispatchWorkItem, which can be canceled, e.g.:
class SomeOperation: Operation {
private var item: DispatchWorkItem!
init(closure: #escaping () -> Void) {
super.init()
item = DispatchWorkItem { [weak self] in
closure()
self?.item = nil
}
}
override func main() {
if isCancelled { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: item)
}
override func cancel() {
item?.cancel()
item = nil
super.cancel()
}
}
Having outlined the memory issues, we should note that this is all probably moot, as you probably should just make this a concurrent operation (with all that custom KVO) as you identified in the documentation. Besides, all this care we’ve put into cancelation logic only applies if the operation is alive until the asynchronous process finishes. So, we will make a concurrent operation. E.g.,
class SomeOperation: AsynchronousOperation {
private var item: DispatchWorkItem!
init(closure: #escaping () -> Void) {
super.init()
item = DispatchWorkItem { [weak self] in
closure()
self?.item = nil
self?.complete()
}
}
override func main() {
if isCancelled { return }
synchronized {
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: item)
}
}
override func cancel() {
super.cancel()
synchronized {
item?.cancel()
item = nil
}
}
}
The above uses an asynchronous operation base class that (a) performs the necessary KVO notifications; and (b) is thread-safe. Here is one random example of how that could be implemented:
/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer
/// a concurrent NSOperation subclass, you instead subclass this class which:
///
/// - must override `main()` with the tasks that initiate the asynchronous task;
///
/// - must call `complete()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
/// necessary and then ensuring that `complete()` is called; or
/// override `cancel` method, calling `super.cancel()` and then cleaning-up
/// and ensuring `complete()` is called.
public class AsynchronousOperation: Operation {
private let lock = NSLock()
private var _executing: Bool = false
override private(set) public var isExecuting: Bool {
get {
synchronized { _executing }
}
set {
willChangeValue(forKey: #keyPath(isExecuting))
synchronized { _executing = newValue }
didChangeValue(forKey: #keyPath(isExecuting))
}
}
private var _finished: Bool = false
override private(set) public var isFinished: Bool {
get {
synchronized { _finished }
}
set {
willChangeValue(forKey: #keyPath(isFinished))
synchronized { _finished = newValue }
didChangeValue(forKey: #keyPath(isFinished))
}
}
override public var isAsynchronous: Bool { return true }
/// Complete the operation
///
/// This will result in the appropriate KVN of isFinished and isExecuting
public func complete() {
if isExecuting {
isExecuting = false
isFinished = true
}
}
public override func cancel() {
super.cancel()
complete()
}
override public func start() {
if isCancelled {
isFinished = true
return
}
isExecuting = true
main()
}
override public func main() {
fatalError("subclasses must override `main`")
}
public func synchronized<T>(block: () throws -> T) rethrows -> T {
try lock.synchronized { try block() }
}
}
extension NSLocking {
public func synchronized<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}

Interoperating Async/await, #MainActor and DispatchQueue.main.async

Say I have this code:
class Presenter {
var viewToUpdate: UIView!
func updateUI() {
viewToUpdate.backgroundColor = .red
}
}
class ShinyNewAsyncAwaitClass {
func doAsyncAwaitThing() async {
// make network call or something
}
}
class OtherClassThatICantUpdateToAsyncAwaitYet {
func doOldClosureBasedThing(completion: #escaping () -> Void) {
// make network call or something
completion()
}
}
class TheClassThatUsesAllThisStuff {
var newClass: ShinyNewAsyncAwaitClass!
var oldClass: OtherClassThatICantUpdateToAsyncAwaitYet!
var presenter: Presenter!
func doSomethingWithNewClass() {
Task {
await self.newClass.doAsyncAwaitThing()
// ---->>> What do I do here? <<<<----
await self.presenter.updateUI()
}
}
func doSomethingWithOldClass() {
oldClass.doOldClosureBasedThing {
DispatchQueue.main.async {
self.presenter.updateUI()
}
}
}
func justUpdateTheView() {
self.presenter.updateUI()
}
}
In short, I have three classes. One I can update to async/await, the other I can't, and one that uses both. Both need access to a function that updates UI, both will need to access that function on the main thread.
I saw somewhere I can add #MainActor to the updateUI function, but in cases where I'm already on the main thread and I just want to call updateUI, like in justUpdateTheView I get this error:
Call to main actor-isolated instance method 'updateUI()' in a synchronous nonisolated context
Add '#MainActor' to make instance method 'justUpdateTheView()' part of global actor 'MainActor'
I can't define justUpdateTheView as #MainActor, because we're trying to update our project to the new concurrency stuff slowly and this would cause a chain reaction of changes that need to be made.
What to do for the best? Can I do something like this:
func doSomethingWithNewClass() {
Task {
await self.newClass.doAsyncAwaitThing()
DispatchQueue.main.async {
self.presenter.updateUI()
}
}
}
It compiles, but are there any gotchas to be aware of?
You can do something like this to run the UI code on the MainActor:
func doSomethingWithNewClass() {
Task {
await self.newClass.doAsyncAwaitThing()
await MainActor.run {
self.presenter.updateUI()
}
}
}

Swift. How to return a function from a closure

I have a question regarding closure
function AA(){
localPlayer.authenticateHandler{
//…
if trigger {
retrun
}
}
dosomething()
}
In the above code,
I want to write a code that wants to return to the AA() function when the trigger is satisfied in the authenticateHandler closure that is called by an asynchronous callback.
The result of the code above is
When trigger occurs, only the closure is returned and the dosomething() method is executed below.
Is there any way
I have short English skills, but thank you for your help.
Closure does not support return statement. Instead use completion block inside main thread to perform the task:
i.e
function AA( completion: #escaping (Result<[Return Type], Error>) -> Void) {
localPlayer.authenticateHandler{
//…
if trigger {
DispatchQueue.main.async {
completion(//Whatever Return Value)
}
}
dosomething()
}
}
Closure does not support return statement.
Do it this way.
here i used string comparison to show how completion block will work.
func call() {
compareString(str1: "hey", str2: "hey") { isMatch in
if isMatch! { //Trigger
self.doSomething()
}
}
}
func compareString(str1:String,str2:String,completion:#escaping ((Bool?) -> Void)) {
if str1 == str2 {
completion(true)
}else {
completion(false)
}
}
func doSomething() {
}
It doesn’t work that way. A synchronous function can’t wait for the result of an asynchronous function. Give AA a callback closure and call it when authenticateHandler returns.

Operation State Not Thread Safe Swift

I have subclassed Operation to support async operations.
The new class called AsyncOperation and added new field called state
which is an enum to help manage the operation state.
class AsyncOperation: Operation {
// DONE: State enum with keyPath property
enum State: String {
case Ready, Executing, Finished
fileprivate var keyPath: String {
return "is" + rawValue
}
}
// DONE: state property
var state = State.Ready {
willSet {
willChangeValue(forKey: newValue.keyPath)
willChangeValue(forKey: state.keyPath)
}
didSet {
didChangeValue(forKey: oldValue.keyPath)
didChangeValue(forKey: state.keyPath)
}
}
}
extension AsyncOperation {
// DONE: Operation Overrides
override var isReady: Bool {
return super.isReady && state == .Ready
}
override var isExecuting: Bool {
return state == .Executing
}
override var isFinished: Bool {
return state == .Finished
}
override var isAsynchronous: Bool {
return true
}
override func start() {
if isCancelled {
state = .Finished
return
}
main()
state = .Executing
}
override func cancel() {
state = .Finished
}
}
in general this subclass operates great and im very happy with it.
Im experiencing some odd behaviour tough...
In some cases im adding an operation to the queue like so:
//this code happens in mainViewController
//op is an operation that belong to mainViewController and could dispatched to the queue from many places, its init called once in view did load.
op = SomeAsyncOperation()
if(op.state == .Executing){
queue.addOperatiom(op)
}
and the app crashes because the operation somehow already dispatched to the queue, when i check with breakpoint the state property that i have created is Ready and the isExecuting field of the raw operation is true. what happen is my state property and the operation state fields are not synced. if i check the state field in different implementation it does get to Executing and Finished how can i be sure those will always be synced?
You should use a NSLock to guard reads and writes to the state property.
Take a look a the sample code of the session Advanced NSOperation from WWDC 2015
The important part is :
/// Private storage for the `state` property that will be KVO observed.
private var _state = State.Initialized
/// A lock to guard reads and writes to the `_state` property
private let stateLock = NSLock()
private var state: State {
get {
return stateLock.withCriticalScope {
_state
}
}
set(newState) {
/*
It's important to note that the KVO notifications are NOT called from inside
the lock. If they were, the app would deadlock, because in the middle of
calling the `didChangeValueForKey()` method, the observers try to access
properties like "isReady" or "isFinished". Since those methods also
acquire the lock, then we'd be stuck waiting on our own lock. It's the
classic definition of deadlock.
*/
willChangeValueForKey("state")
stateLock.withCriticalScope { Void -> Void in
guard _state != .Finished else {
return
}
assert(_state.canTransitionToState(newState), "Performing invalid state transition.")
_state = newState
}
didChangeValueForKey("state")
}
}

Function that executes after multiple completion block has finished

I have a funciton that I would like to only execute IF the two completion block has completed (And no way to tell which one to finish first). Below is my attempt that works. However, it is very messy and if I there are three or more completion block that I want to wait for, I would have flags everywhere. I was wondering if there is a prettier way of doing it.
class TestClass: UIViewController {
var blockOneComplete = false
var blockTwoComplete = false
func blockOneDownloadImageDescription(completion:()->Void) {
downloadAsyncWithCompletion {
blockOneComplete = true
if self.blockTwoComplete == true {
self.allDataDownloadCompleted()
} else {
// Do nothing and wait for block Two to complete
}
}
}
func blockTwoDownloadImageData(completion:()->Void) {
downloadAsyncWithCompletion {
blockTwoComplete = true
if self.blockOneComplete == true {
self.allDataDownloadCompleted()
} else {
// Do nothing and wait for block One to complete
}
}
}
func allDataDownloadComplete() {
// Execute this funciton after all Async Download has complete
}
}
-- Update with final result --
Turns out that what was outlined in this website was exactly what I needed
Using dispatch groups to wait for multiple web services
I believe this was not a duplicate of the SO question mentioned in the comment because the final solution included dispatch_group_enter and dispatch_group_leave
The best option is by using dispatch_group
class TestClass: UIViewController {
var group : dispatch_group_t = dispatch_group_create()
override func viewDidLoad() {
super.viewDidLoad()
dispatch_group_notify(group, dispatch_get_main_queue()) {
allDataDownloadComplete()
}
}
func blockOneDownloadImageDescription(completion:()->Void) {
dispatch_group_enter(group)
downloadAsyncWithCompletion {
dispatch_group_leave(group)
}
}
func blockTwoDownloadImageData(completion:()->Void) {
dispatch_group_enter(group)
downloadAsyncWithCompletion {
dispatch_group_leave(group)
}
}
func allDataDownloadComplete() {
// Execute this funciton after all Async Download has complete
}
}
You will need either use dispatch_group or use functional reactive programming library like RxSwift to achieve it if you does not want to manage flags.
However, you can just use one counter flag and just make a function call or use NSNotification if is for another ViewController.
In one of my project, I need to ensure that at least 3 of the 4 completion block is completed before calling some function. I do it something like this:
class TestClass: UIViewController {
var numberOfBlockCompleted = 0
func blockOneDownloadImageDescription(completion:()->Void) {
downloadAsyncWithCompletion {
numberOfBlockCompleted += 1
self.allDataDownloadCompleted()
}
}
func blockTwoDownloadImageData(completion:()->Void) {
downloadAsyncWithCompletion {
numberOfBlockCompleted += 1
self.allDataDownloadCompleted()
}
}
func blockThreeDownloadImageDesc(completion:()->Void) {
downloadAsyncWithCompletion {
numberOfBlockCompleted += 1
self.allDataDownloadCompleted()
}
}
func allDataDownloadComplete() {
if numberOfBlockCompleted == 3 {
//do something
}
}
}
In my opinion, it depend largely on how complex is the app. If is just for one or two part, a flag is good enough. However, if the app depending largely on chaining network calls and fetching from different server that need to wait for one or another to be completed like a live stocks app then a strong knowledge of GCD or using functional reactive programming will make your job easier in the long run.

Resources