Prevent Retain Cycle in Swift Function Pointers

prevent retain cycle in Swift function pointers

Rather than

datasource.decorator = decorateThatThing

You can use

datasource.decorator = { [unowned self] cell in
self.decorateThatThing(cell)
}

Avoid retain cycles for function pointers in Swift

To fix it, you have to call the func1 through weak self.

internal init() {
self.funcPointer = { [weak self] in
self?.func1()
}
print("TestClass1 init is called!")
}

This way you can prevent retain cycles.

What happens at the moment is that you are assigning an instance function to your instance property who now has a strong reference to your function.

Hope this helps.

What is the proper way to avoid Retain Cycle while using blocks

Try a combination of the 2nd and 3rd.

__weak id weakSelf = self;
[tapBlockView setTapBlock:^(UIImage* image) {
[weakSelf.myImageArray addObject:image];
}

How to avoid a retain cycle when using an array of delegates in Swift

The problem is that weakDelegates is a strong reference and its reference to its elements of type WeakDelegateContainer is a strong reference.

Your situation is why the class NSHashTable exists. Initialize using weakObjects(). This will give you a set of ARC-weak references, each of which will be nilified and removed when the referenced object goes out of existence (with no need for any extra bookkeeping on your part, and no need for your WeakDelegateContainer type).

Your set will have to be typed as holding AnyObject, but you can easily mediate to ensure that you are supplying and retrieving SomeDelegate-conformant objects:

let list = NSHashTable<AnyObject>.weakObjects()
func addToList(_ obj:SomeDelegate) {
list.add(obj)
}
func retrieveFromList(_ obj:SomeDelegate) -> SomeDelegate? {
if let result = list.member(obj) as? SomeDelegate {
return result
}
return nil
}
func retrieveAllFromList() -> [SomeDelegate] {
return list.allObjects as! [SomeDelegate]
}

The function retrieveAllFromList() lists only objects that still exist. Any object that has gone out existence has been changed to nil in the NSHashTable and is not included in allObjects. That is what I mean by "no extra bookkeeping"; the NSHashTable has already done the bookkeeping.

Here is code that tests it:

func test() {
let c = SomeClass() // adopter of SomeDelegate
self.addToList(c)
if let cc = self.retrieveFromList(c) {
cc.someFunction()
}
print(self.retrieveAllFromList()) // one SomeClass object
delay(1) {
print(self.retrieveAllFromList()) // empty
}
}

Alternatively, you can use NSPointerArray. Its elements are pointer-to-void, which can be a little verbose to use in Swift, but you only have to write your accessor functions once (credit to https://stackoverflow.com/a/33310021/341994):

let parr = NSPointerArray.weakObjects()
func addToArray(_ obj:SomeDelegate) {
let ptr = Unmanaged<AnyObject>.passUnretained(obj).toOpaque()
self.parr.addPointer(ptr)
}
func fetchFromArray(at ix:Int) -> SomeDelegate? {
if let ptr = self.parr.pointer(at:ix) {
let obj = Unmanaged<AnyObject>.fromOpaque(ptr).takeUnretainedValue()
if let del = obj as? SomeDelegate {
return del
}
}
return nil
}

Here is code to test it:

    let c = SomeClass()
self.addToArray(c)
for ix in 0..<self.parr.count {
if let del = self.fetchFromArray(at:ix) {
del.someFunction() // called
}
}
delay(1) {
print(self.parr.count) // 1
for ix in 0..<self.parr.count {
if let del = self.fetchFromArray(at:ix) {
del.someFunction() // not called
}
}
}

Interestingly, after our SomeClass goes out of existence, our array's count remains at 1 — but cycling through it to call someFunction, there is no call to someFunction. That is because the SomeClass pointer in the array has been replaced by nil. Unlike NSHashTable, the array is not automatically purged of its nil elements. They do no harm, because our accessor code has guarded against error, but if you would like to compact the array, here's a trick for doing it (https://stackoverflow.com/a/40274426/341994):

    self.parr.addPointer(nil)
self.parr.compact()

Closure recursion and retain cycles

You have an unusual setup where your closure retains itself. Note that Swift doesn't allow you to create a weak reference to a closure.

To break the retain cycle, set closure to { } in the base case of the recursion. Here's a test macOS command-line program:

func test() {
var closure: ((Int) -> ()) = { _ in }
closure = { i in
if i < 10 {
closure(i + 1)
} else {
// Comment out this line for unbounded memory consumption.
closure = { _ in }
}
}
closure(0)
}

while true {
test()
}

If you run this, its memory consumption is flat.

If you comment out the line in the base case that resets closure, its memory consumption grows without bound.

Calling function in block retain cycle

Potentially.

A retain cycle is created by having a cycle of strong references, apart from the qualifier (i.e. weak, strong) on the variable the actual variables where those references come from is irrelevant. So strongSelf referenced by your block is a strong reference to self and you have the same potential for a retain cycle as if you'd used self itself.

Re: comment

Having your block maintain a weak reference is the standard approach to addressing this issue. If you use weakSelf in your block then there is no strong reference, if by the time the block is called weakSelf is nil then the call [weakSelf secondFunction] will do nothing - you are allowed to message nil in Objective-C. You won't create a cycle, during the call of the block a strong copy of the reference may be created but this will go after the call to the block returns.

Weird weak self and retain cycle behaviour

First of all, all printers are creating and retaining their own Delayer. The delayer takes a closure and, in turn, retains that closure.

Let's try to walk through them one by one.

Printer1

As you stated yourself, it's pretty clear why it's creating a retain cycle. You are passing the self.action instance method as the closure to the Delayer, and since all closures are reference types, passing self.action will retain its surrounding scope (which is Printer1).

Printer2

Again, pretty obvious here. You're explicitly capturing a weak reference to self inside the closure you're passing to Delayer, hence not creating a retain cycle.

Printer3

Here, a retain cycle is not created, because the self.weakAction property is called immediately, and its result (a closure which holds a weak reference to self) is passed on to Delayer. This, in effect, is the exact same thing as what's happening in Printer2.

Printer4

First, you're capturing a weak reference to self, and then fetching welf?.action and passing the result into Delayer. Again, welf?.action is called immediately, and the result (a pointer to an instance method) is passed on to Delayer. The weak reference to self is only kept for the duration of the surrounding scope (the lazy var creation scope), and passing the action instance method will retain self. This is identical to Printer1.

Printer5

Here, you're first creating a weak reference to self, and then you're capturing that weak reference inside a new closure that is passed to Delayer. Since self is never directly referenced in the passed closure, it will not capture self in that scope, only the welf weak reference. This is pretty much identical to Printer2, but with a slightly different syntax.

Personally, I would opt for the Printer2 way (creating a new closure, retaining a weak reference to self and using that to call self?.action). It makes for the easiest code to follow (as opposed to retaining a variable with a closure that weakly captures self). But, depending on what you're actual use case is, it might of course make sense.

Understanding retain cycle in depth

Unless there is some other reference to the parent or child, they both become orphaned. But the retain cycle between the parent and child prevent either from being released and they become wasted memory.

A child should never retain a parent. If anything, use a weak reference in the child to maintain a reference to the parent.



Related Topics



Leave a reply



Submit