3

When passing a method to a function that takes a closure, I can use either someFunc(closure: someMethod) orsomeFunc() { [unowned self] in self.someMethod() }`.

The first one is shorter but makes a strong reference. How can I use it while avoiding this strong reference?

Here is a demo with both the leaking one and the good one: https://swiftlang.ng.bluemix.net/#/repl/581ccd3a0bdc661a6c566347

import Foundation

private var instanceCounter = 0

class Leak : NSObject {

    override init() {
        super.init()
        instanceCounter += 1
    }

    deinit {
        instanceCounter -= 1
    }
}

class OnFunctionLeak : Leak {

    override init() {
        super.init()
        _ = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "OnFunctionLeak"),
                                               object: nil,
                                               queue: nil,
                                               usingBlock: doNothing)
    }

    func doNothing(_ notif: Notification) { }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

class OnClosureLeak : Leak {

    override init() {
        super.init()
        _ = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "OnFunctionLeak"),
                                               object: nil,
                                               queue: nil) { [unowned self] notif in
            self.doNothing(notif)
        }
    }

    func doNothing(_ notif: Notification) { }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

var onFunctionLeak: OnFunctionLeak? = OnFunctionLeak()
onFunctionLeak = nil

//XCTAssertEqual(instanceCounter, 0)
print("instanceCounter: \(instanceCounter) == 0")

instanceCounter = 0
var onClosureLeak: OnClosureLeak? = OnClosureLeak()
onClosureLeak = nil

//XCTAssertEqual(instanceCounter, 0)
print("instanceCounter: \(instanceCounter) == 0")

The shorter choice is on line 26 and if I replace doNothing by { [unowned self] notif in self.doNothing(notif) }, the strong reference is gone.

Any ideas?

jscs
  • 63,694
  • 13
  • 151
  • 195
Dam
  • 579
  • 6
  • 22
  • Related: [How to remove strong reference cycle from closure from method?](http://stackoverflow.com/q/39899051/2976878) I don't believe there's a better way of avoiding a strong reference to `self` without using a closure, such as in your second example. – Hamish Nov 04 '16 at 18:29
  • I read it and I ask to be sure. But if it is confirmed, my thinking will why allow to write directly the question if it will always create an hiding strong reference, so easy to make the mistake. – Dam Nov 04 '16 at 18:37
  • BTW, it’s generally considered bad practice to call "NotificationCenter.default.removeObserver(self)” — this can mess up parent classes, so Apple encourages you to remove your observations one by one. – Wil Shipley Jul 21 '17 at 07:13

2 Answers2

3

How can I use it while avoiding this strong reference?

You can't.

Only an anonymous function defined inline (at the point of usage) can have a capture list (such as [unowned self]). Thus, only an anonymous function can provide the functionality you are asking for. A function defined with func simply cannot do it.

That's just a fact about Swift.

(There are probably underlying reasons for it; I suspect that the reasons have to do with storage. A func function is stored statically in some way. But an anonymous function defined inline is not; it comes into being at exactly the moment it is passed to the callee. But that's just a guess, and a rather vague guess at that.)

matt
  • 515,959
  • 87
  • 875
  • 1,141
0

Matt is right and I can't find a way to use function without strong reference.

I just found you can use var to make it cleaner writing the closure directly inside your function is not really clean.

class OnVarLeak : Leak {

    var value = 0

    override init() {
        super.init()
        NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "OnVarLeak"),
                                               object: nil,
                                               queue: nil,
                                               using: doNothing)
    }

    var doNothing: (Notification) -> Void {
        return { [unowned self] notif in
            self.value += 1
        }
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

Like that you do not have strong reference and you can do "using: doSomething)".

I still think it is not safe that Swift compilation let you use function instead of closure as it will always leave a memory leak in your project.

Dam
  • 579
  • 6
  • 22