Storing weak reference sometimes causes leak
Asked Answered
E

1

13

I have a class A that stores a optional weak variable to other object, that is a subclass of A. Sometimes when I store something in this variable I get a leak - this happens rarely, but it does. I'm assigning this variable in forEach loop, but I also found similar leak in other place of application once when using weak var, so I don't think that loop has anything to do with this. Responsible library is libswiftCore.dylib and responsible frame is swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1> >::formWeakReference(). Did someone else have had similar issue? Is there some way I can fix this?

enter image description here

enter image description here

enter image description here

Electroform answered 14/7, 2018 at 14:55 Comment(3)
Never seen this, but for leaks in Apple frameworks you should file a bug report.Aziza
I have this problem. I think it is a Swift issue and I don't know why it happens in some particular situations. I was able to fix it by using Unmanaged<T> but decided to revert the fix in favor of more readable code. FYI, in my case, the actual object that I was storing in a weak property wasn't being retained so not fixing the issue was OK for me.Perrault
Can you provide a minimal viable snippet of code reproducing the issue stripped of anything not relevant for the leak to occur?Depute
B
5

Try setting the variable instead of being weak set it to be unowned. This is a Swift bug sort of as there is no warning for the developer that he is capturing a strong reference of the nested closure, however, setting it to unowned should do as a workaround for now.

EDIT1: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001197.html

Mentioned as an improvement from here here:

class ViewControllerBase: UIViewController {
let timer:DispatchSourceTimer = DispatchSource.makeTimerSource(flags: [], queue:  DispatchQueue(label: "q.q"))

deinit {
    NSLog("deinit of \(NSStringFromClass(type(of: self)))")
}

override func viewDidLoad() {
    super.viewDidLoad()

    timer.scheduleRepeating(deadline: .now(), interval: .seconds(1))

    timer.setEventHandler {
        UIView.animate(withDuration: 0.2) { [weak self] in
            self?.view.backgroundColor = UIColor.green
        }
    }
}

This leaks memory in a subtle way...there is a retain loop even though self is only used in a "weak" manner. This is because the nested closure captures a strong reference for use in the closure that follows.

EDIT 2: I might be wrong but the OP is using changes.forEach closure and then another closure with changes.added.forEach I could be wrong but this might be the cause of the whole issue perhaps declaring it there [weak self] (cluster, change) in ... could possibly remove the issue. It is swifts built in closure but still a closure, that could technically cause them to be nested.

Try changing your code to:

changes.forEach{[weak self] (cluster, change) in

see if that helps

or

changes.forEach{[unowned self] (cluster, change) in

Also any chance that you could paste your code instead of doing a screenshot as it is easier to recreate your code instead of retyping it.

Bothersome answered 4/9, 2018 at 10:51 Comment(9)
unowned and weak have different meaning - weak is by definition optional and must be unwrapped (safely or not) before use, we can safely check it for nil (since it is optional). unowned can't be optional, thus it is inapplicable in cases where I need to use weak (and check for nil). As for "this is bug" - could you cite any authoritative source?Hezekiah
even though information you provided is very useful (imo) and gave me a lot of things to think about it is about one particular case involving two nested closures. I don't have such code, I just have weak var (like question author) and it still produces leak.Hezekiah
Could you share your code somewhere so I could have a look??Bothersome
Making minimal reproducible example from my existing code requires some time and probably another question on SO. I hoped to see a quick solution to this problem. If we don't see other answers within few days, I do it.Hezekiah
I might be wrong but the OP is using changes.forEach closure and then another closure with changes.added.forEach I could be wrong but this might be the cause of the whole issue perhaps declaring it there [weak self] (cluster, change) in ... could possibly remove the issue. It is swifts built in closure but still a closure, that could technically cause them to be nested.Bothersome
My bad, judging from code provided by question author, it might be the case. Let's wait for his feedback.Hezekiah
Could you meanwhile post the section of your code with comments where you are having the memory cycle??Bothersome
I would really like to, but it involves a few complicated files and classes (but there are no two nested closures along call stack), and I don't waste your or anybody else's time trying to figure out how it works.Hezekiah
After a few trials I failed to create reproducible example but I found a few minor issues and one "normal" reference cycle in my code. By fixing it I removed the major memory leak (though this one still remains unfixed, but causes almost no harm). Although question author doesn't respond to answer and we don't know if it works or not for them, I find answer very informative and believe it deserves bounty.Hezekiah

© 2022 - 2024 — McMap. All rights reserved.