Created
April 26, 2020 14:02
-
-
Save andreashanft/53590d074ec54860deb51e5e168d5cc2 to your computer and use it in GitHub Desktop.
Method Reference Issue
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CaptureClass { | |
var action: ((Int) -> Void)? | |
init() { | |
// Passing a method reference to an escaping closure implicitly captures self | |
// and in this case causes a refernce cycle! | |
addAction(takesInt) | |
// To avoid memory leak: | |
addAction { [weak self] in self?.takesInt($0) } | |
} | |
func addAction(_ f: @escaping (Int) -> Void) { | |
// store a strong ref to f, which might include a strong ref to a captured self | |
action = f | |
} | |
func takesInt(_ i: Int) {} | |
deinit { | |
print("👍") | |
} | |
} | |
var test = CaptureClass() | |
test = CaptureClass() | |
Hi Nils,
Thank you very much for your thoughts and great workarounds. Unfortunately I am not sure if they actually can help in the specific case I am facing using Combine.
I have attached a pseudo-code example and also added an idea for another workaround which unfortunately is not working:
import UIKit
import Combine
struct Presentation {
let title: String
}
final class ViewModel {
@Published private (set) var presentation = Presentation(title: "Some Title")
}
final class Proxy<T: AnyObject> {
unowned let o: T
init(original: T) {
self.o = original
}
}
final class NotReallyAViewController {
let viewModel = ViewModel()
var subscription: AnyCancellable?
lazy var proxy = Proxy(original: self)
func start() {
// Causes retain cycle
//subscription = viewModel.$presentation.sink(receiveValue: presentationDidChange)
// All good but not so nice syntax
subscription = viewModel.$presentation.sink(receiveValue: { [unowned self] in self.presentationDidChange(presentation: $0)})
// Still a retain cycle
//subscription = viewModel.$presentation.sink(receiveValue: proxy.o.presentationDidChange)
}
func presentationDidChange(presentation: Presentation) {
print("presentationDidChange: \(presentation.title)")
}
deinit {
print("☠️")
}
}
var test: NotReallyAViewController? = .init()
test?.start()
test = nil
The proxy workaround was inspired by behaviour I observed in my code: I have a publisher on a view that is connected to a method on the view model, the subscription is stored in the view controller. This does not seem to cause a retain cycle. :thinking_face:
view.button.onTapPublisher.sink(receiveValue: viewModel.backAction)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi Andreas,
in this specific case there is a workaround by currying the method to be captured.
If the function is not a member, you'll have to end up with two
arguments when setting the action. Here I do that in the initializer. It's not that great at the call site though.
I don't have any better idea, sorry...