Created
April 1, 2020 14:42
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
*/ |
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
The problem isn’t the container class,
Foo
, but rather theDispatchWorkItem
.I ran the
, and I can see that the
DispatchWorkItemCanceled
and when it was done and I received thedeallocated
message, I tapped the “debug memory graph” button,DispatchWorkItem
leaked:To fix this “cancel” scenario, I had to
nil
theitem
after Icancel
it:In the
DispatchWorkItemExecuted
example, while you commented out theitem = nil
inside the block, you are settingitem
tonil
where you cancel the block, which is resolves the issue. But if you commented out that seconditem = nil
, too, you again would see a leak:This is again resolved by setting
item
tonil
.