Skip to content

Instantly share code, notes, and snippets.

@mfaani
Created April 1, 2020 14:42
Show Gist options
  • Save mfaani/639c8ebd8b613c45cb07070f6f7c8b29 to your computer and use it in GitHub Desktop.
Save mfaani/639c8ebd8b613c45cb07070f6f7c8b29 to your computer and use it in GitHub Desktop.
Related to https://stackoverflow.com/a/38372384/5175709 Trying to show setting `item` to `nil` doesn't seem to be necessary
import Foundation
class Foo {
func testDispatchItems() {
let queue = DispatchQueue.global()
var item: DispatchWorkItem?
item = DispatchWorkItem { [weak self] in
for i in 0 ... 10 {
if item?.isCancelled ?? true { break }
print(i)
self?.heavyWork(i)
}
// item = nil // resolve strong reference cycle
}
item?.cancel()
}
func heavyWork(_ num: Int) {
print(num)
}
deinit {
print("deallocated")
}
}
var c: Foo? = Foo()
c?.testDispatchItems()
c = nil
// no need to set item to `nil` when execution finishes...
/*
Output:
deallocated
*/
import Foundation
class Foo {
func testDispatchItems() {
let queue = DispatchQueue.global()
var item: DispatchWorkItem?
item = DispatchWorkItem { [weak self] in
for i in 0 ... 10 {
if item?.isCancelled ?? true { break }
print(i)
self?.heavyWork(i)
}
// item = nil // resolve strong reference cycle
}
queue.async(execute: item!)
queue.asyncAfter(deadline: .now() + 5) {
item?.cancel()
item = nil
}
}
func heavyWork(_ num: Int) {
print(num)
}
deinit {
print("deallocated")
}
}
var c: Foo? = Foo()
c?.testDispatchItems()
c = nil
// no need to set item to `nil` when execution finishes...
/*
Output:
0
0
1
1
deallocated
2
3
4
5
6
7
8
9
10
*/
@winkelsdorf
Copy link

winkelsdorf commented Dec 10, 2020

What about the following example, I'm trying to cancel previously running work items (if there had been any). I'm unable to avoid a leak when not running the operation at all (i.e. Cancel immediately). Likely I'm too focussed on this now.

Edit: Looks like it's the Work Item inside testDispatchItems which leaks. That one must be nilled. Still leaking at least once.

class ViewController: UIViewController {

    lazy var foo = Foo()

    override func viewDidLoad() {
        super.viewDidLoad()

        foo.testDispatchItems()

        // fire a 2nd run
        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { [weak self] in
            self?.foo.testDispatchItems()
        }
    }

}

class Foo {
    
    var item: DispatchWorkItem?
    
    func testDispatchItems() {
        // cancel previously running work items, if any
        self.item?.cancel()
        self.item = nil
        
        var workItem: DispatchWorkItem?

        workItem = DispatchWorkItem { [weak self] in
            defer {
                debugPrint("defer")
                // workItem = nil // does not help
            }
            
            for i in 0 ... 50 {
                if workItem?.isCancelled ?? false {
                    debugPrint("Cancelled")
                    break
                }
                debugPrint("for", i)
                self?.heavyWork(i)
            }
            
            self?.item = nil // resolve strong reference cycle
        }
        
        guard let assignedWorkItem = workItem else {
            assertionFailure()
            self.item = nil
            return
        }
        
        self.item = assignedWorkItem
          
        self.item?.cancel()
        self.item = nil

//        DispatchQueue.global()
//            .async(execute: assignedWorkItem)
    }

    func heavyWork(_ num: Int) {
        debugPrint("heavyWork", num)
        Thread.sleep(forTimeInterval: 0.1)
    }

    deinit {
        print("deallocated")
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment