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
Passing Values Between Viewcontrollers Based on List Selection in Swift
Continuous Rotation of Nsimageview (So It Appears to Be Animated)
How to Add UIpickerview in UIalertcontroller
How to Navigate from Initial UIviewcontroller to UIsplitviewcontroller in Swift
Changing a Label in Prepareforsegue
Open View Controller When Remote Notification Pressed
Trouble Calling a Method in an Init
Core Data with Swiftui Mvvm Feedback
Swift: Get The Compile Time Name of Variable (Referencing to a Class)
Swift Preserve UIswitch State on UIlongpress
Prevent Retain Cycle in Swift Function Pointers
How to Make a Function Operate on a Sequence of Optional Values
Why Is Swift Giving Me Inaccurate Floating Point Arithmetic Results