1

I just went memory-leak hunting in the app I am working on, and noticed that the following produces a memory leak:

class SubClass {

    var didCloseHandler: (() -> Void)?

}

class MainClass {

    var subClass = SubClass()

    func setup {
        subClass.didCloseHandler = self.didCloseSubClass
    }

    func didCloseSubClass() {
        //
    }

}

This produces a retain cycle, and for good reason - didCloseHandler captures MainClass strongly, and MainClass captures SubClass strongly.

My Question: Is there a way in Swift that allows me to assign a class method to a handler without a retain cycle?

And yes, I am aware that I can do this using subClass.didCloseHandler = { [weak self] self?.didCloseSubClass() }. I'm wondering, though, if it can be done without introducing a new closure.

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
BlackWolf
  • 5,239
  • 5
  • 33
  • 60
  • 4
    Hi! Did you tried weak var subClass...? – wm.p1us Mar 05 '20 at 09:31
  • make it `weak var subClass = SubClass()` – Jawad Ali Mar 05 '20 at 09:37
  • Why don't you want to introduce a new closure? – Sweeper Mar 05 '20 at 10:28
  • @Sweeper The main reason is to keep the code simpler and more maintainable. The example I gave is very simplistic, but say my handler has 3 parameters - in that case I have to pass along 3 parameters in the closure, if I refactor the handler I have to make sure my parameters are appropriately renamed everywhere, etc. – BlackWolf Mar 05 '20 at 10:45

2 Answers2

1

make a weak reference of subClass in MainClass

M Ohamed Zead
  • 197
  • 10
  • weak var subClass = subClass() – M Ohamed Zead Mar 05 '20 at 10:18
  • I'll wait for more answers, but it seems this is the only possible way, so I'll accept this if no other clever answers come in. But to be honest, for most use cases it would probably be preferrable to introduce a closure. – BlackWolf Mar 05 '20 at 10:48
1

If you don't have strong reference to SubClass instance somewhere else - you may try wrapper like this:

func WeakPointer<T: AnyObject>(_ object: T, _ method: @escaping (T) -> () -> Void) -> (() -> Void) {
    return { [weak object] in
        method(object!)()
    }
}

Then use it like this:

func setup() {
    subClass.didCloseHandler = WeakPointer(self, MainClass.didCloseSubClass)
}

If you don't need properties from MainClass instance in didCloseSubClass implementation - you can make this method static, which will also solve your problem.

If you have strong reference to SubClass instance somewhere else and it won't be deallocated immediately - weak var subClass will do, as was already mentioned.

EDIT: I've come up with another idea. It may look a bit more complicated, but maybe it would help.

import Foundation

class SubClass {

    @objc dynamic func didCloseHandler() {
        print(#function)
    }

    deinit {
        print(" \(self) deinit")
    }
}

class MainClass {

    var subClass = SubClass()

    func setup() {
        if let implementation = class_getMethodImplementation(MainClass.self, #selector(didCloseSubClass)),
            let method = class_getInstanceMethod(SubClass.self, #selector(SubClass.didCloseHandler)) {
            method_setImplementation(method, implementation)
        }
    }

    @objc func didCloseSubClass() {
        print(#function)
    }

    deinit {
        print(" \(self) deinit")
    }
}

You change closure for @objc dynamic method and set it's implementation to the one from MainClass in setup().

Lieksu
  • 71
  • 4
  • Very clever solution, but I wouldn't necessarily recommend this to anyone :D – BlackWolf Mar 05 '20 at 10:46
  • using [weak self] in closure is probably the best option, as it makes a bit easier to understand what's going on in code, but problem is interesting to tackle around, so I've added another solution :) – Lieksu Mar 05 '20 at 11:12