I don't understand the correct way of using self inside Combine closure
class ViewModel {
var requestPublisher = PassthroughSubject<Void, Never>()
var nextPageAvailabe = true
private var subscriptions = Set<AnyCancellable>()
init() {
setupSubscriptions()
}
setupSubscriptions() {
requestPublisher
.filter { _ in self.nextPageAvailabe }
.sink(receiveValue: {
print("Received request")
}).store(in: &subscriptions)
}
}
requestPublisher.send() executed from parent class.
Using .filter { _ in self.nextPageAvailabe } will lead to memory leak. So I need to use [weak self] or [unowned self] inside filter {}. And both solve the memory leak issue, but I don't understand the correct way. I have read several articles but couldn't find the answer. So what is the correct way of using self inside Combine closure?
weak is definitely the way to go : you will just have an optional self. When a weak variable is released, it just becomes nil.
unowned should be avoided as much as possible, as it is not runtime-safe : it is like weak, but the result is not optional. So if you try to use an unowned variable after it has been released, the app will simply crash.
setupSubscriptions() {
requestPublisher
.filter { [weak self] _ in self?.nextPageAvailabe ?? false }
// I have put `?? false` because I suppose that if self is nil, then page is not available...
.sink(receiveValue: {
print("Received request")
}).store(in: &subscriptions)
}
Related
I'm plaing with concurrency features of Swift. I've created helper function which returns AsyncStream with values published by NSOBject implementations. Sort of code below.
func asyncStreamFor<Root: NSObject, Value> (_ root: Root, keyPath: KeyPath<Root, Value>) -> AsyncStream<Value> {
AsyncStream { continuation in
let cancellable = root.publisher(for: keyPath, options: [.initial, .new])
.sink {
continuation.yield($0)
}
continuation.onTermination = { #Sendable _ in
cancellable.cancel()
}
}
}
I'm trying to use it (and previously used it directly using publisher) for such purposes as AVPlayer properties (rate, status) observation. Usage scheme below:
class Player {
let avPlayer = AVPlayer()
var cancellable = Set<AnyCancellable>()
init() {
avPlayer.publisher(for: \.status)
.sink {
print($0)
}.store(in: &cancellable)
}
}
Release of Player's instance works properly (no reference cycle problem).
For AsyncStream, I've tried to make it simple and used scheme as below:
class Player {
let avPlayer = AVPlayer()
init() {
Task {
for await status in asyncStreamFor(avPlayer, keyPath: \.status) {
print(status)
}
}
}
}
Here, it seems like there's a reference cycle: AsyncStream instance hold reference to cancellable (in onTermination clousure), which in turn hold reference to avPlayer. Player instance is not deinitialised when dropping last reference.
Any idea how to solve the problem without explicitly cancelling Task before dropping reference to Player?
Let's consider following code:
// Just for easier testing
protocol Printer {
var delayer: Delayer { get }
}
// Retain cycle
class Printer1: Printer {
private func action() {
print("action")
}
private(set) lazy var delayer: Delayer = {
return Delayer(action)
}()
deinit {
print("deinit")
}
}
// Works fine, but weak mess
class Printer2: Printer {
private func action() {
print("action")
}
private(set) lazy var delayer: Delayer = {
return Delayer { [weak self] in self?.action() }
}()
deinit {
print("deinit")
}
}
// Questionable hack, but works fine
class Printer3: Printer {
private func action() {
print("action")
}
private(set) lazy var delayer: Delayer = {
return Delayer(weakAction)
}()
// computed property or function is also fine here
private lazy var weakAction: () -> Void = {
return { [weak self] in
self?.action()
}
}()
deinit {
print("deinit")
}
}
// Retain cycle
class Printer4: Printer {
private func action() {
print("action")
}
private(set) lazy var delayer: Delayer = {
weak var welf: Printer4? = self
return Delayer(welf?.action ?? {})
}()
deinit {
print("deinit")
}
}
// Works fine
class Printer5: Printer {
private func action() {
print("action")
}
private(set) lazy var delayer: Delayer = {
weak var welf: Printer5? = self
return Delayer { welf?.action() }
}()
deinit {
print("deinit")
}
}
class Delayer {
private var action: () -> Void
init(_ action: #escaping () -> Void) {
self.action = action
}
func run() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
self?.action()
}
}
}
So we have a Printer class which contains a Delayer class that takes the action on Printer and performs it delayed.
We call it something like this:
var printer: Printer? = PrinterX()
printer?.delayer.run()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
printer = nil
}
It is clear why Printer1 creates retain cycle. Action is passed into delayer with implicit strong self which cannot be released because Delayer is owned by Printer.
Printer2 is the intended way in my opinion. Obviously doesn't create retain cycle, but it is kind of mess to write all the time. Thats why I started experimenting with other solution.
I don't understand why Printer3 doesn't create retain cycle. Because weakAction is property on self, so passing it like that into Delayer should create strong reference like in Printer1.
I also don't understand why Priner4 does create retain cycle. welf is local weak reference to self, so it should not increase the reference count when passing it into the Delayer.
Strangely enough using the welf inside closure in Printer5 doesn't create retain cycle.
Questions
Can anyone please explain to me this weird behavior on Printer3, Printer4 and Printer5
I am tempted to use the Printer3 solution. Is it safe to use? As it seems almost like a bug, can I use it without worrying about it being fixed in future versions and therefore creating retain cycle in my app?
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.
I'm using RxSwift in a project and I found that when I bind directly to a selector it captures a strong reference from self and deinit wasn't called.
I was wondering how to make selector/func to deal with only a weak reference of self.
viewModel.title
.drive(onNext: updateTitle)
.disposed(by: disposeBag)
func updateTitle(_ title: String) {
navigationItem.title = title
}
What I've tried is
func updateTitle(_ title: String) {
weak var weakSelf = self
weakSelf?.navigationItem.title = title
}
But still deinit is not getting called.
Of course one solution would be to remove the function entirely
viewModel.title
.drive(onNext: { [weak self] title in
self?.updateTitle(title)
)
.disposed(by: disposeBag)
but I don't to lose the concise binding code.
Playing around with it, I found that you can get a syntax like:
viewModel.title
.drive(onNext: weakCapture(self, method: YourViewController.updateTitle))
.disposed(by: disposeBag)
The medicine is worse than the disease, but it's a bit of fun. Here's the definition of the helper method:
func weakCapture<T: AnyObject, A1>(
_ target: T,
method: #escaping (T) -> (A1) -> Void
) -> (A1) -> Void {
return { [weak target] arg in
guard let strongTarget = target else { return }
method(strongTarget)(arg)
}
}
Here's an example usage:
var c: C? = C()
let weaklyCapturedFooMethod = weakCapture(c!, method: C.foo)
weaklyCapturedFooMethod(123) // Runs foo(i: 123)
print(c as Any)
c = nil
weaklyCapturedFooMethod(123) // does nothing
It's not very nice. :P
I would recommend just using: { [weak self] self?.updateTitle($0) }
After some looking, apparently you can't make a func weak because it is not saved in the memory, on the other hand closures capture the environment around it.
So my solution was to create a closure as a var:
viewModel.title
.drive(onNext: updateTitle)
.disposed(by: disposeBag)
lazy var updateTitle: ((String) -> Void)? = { [weak self] _ in
self?.navigationItem.title = $0
}
It is a solution but still curious if we can make the selector captures weak reference.
Is there a good way to handle an array of AnyCancellable to remove a stored AnyCancellable when it's finished/cancelled?
Say I have this
import Combine
import Foundation
class Foo {
private var cancellables = [AnyCancellable]()
func startSomeTask() -> Future<Void, Never> {
Future<Void, Never> { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
promise(.success(()))
}
}
}
func taskCaller() {
startSomeTask()
.sink { print("Do your stuff") }
.store(in: &cancellables)
}
}
Every time taskCaller is called, a AnyCancellable is created and stored in the array.
I'd like to remove that instance from the array when it finishes in order to avoid memory waste.
I know I can do something like this, instead of the array
var taskCancellable: AnyCancellable?
And store the cancellable by doing:
taskCancellable = startSomeTask().sink { print("Do your stuff") }
But this will end to create several single cancellable and can pollute the code. I don't want a class like
class Bar {
private var task1: AnyCancellable?
private var task2: AnyCancellable?
private var task3: AnyCancellable?
private var task4: AnyCancellable?
private var task5: AnyCancellable?
private var task6: AnyCancellable?
}
I asked myself the same question, while working on an app that generates a large amount of cancellables that end up stored in the same array. And for long-lived apps the array size can become huge.
Even if the memory footprint is small, those are still objects, which consume heap, which can lead to heap fragmentation in time.
The solution I found is to remove the cancellable when the publisher finishes:
func consumePublisher() {
var cancellable: AnyCancellable!
cancellable = makePublisher()
.sink(receiveCompletion: { [weak self] _ in self?.cancellables.remove(cancellable) },
receiveValue: { doSomeWork() })
cancellable.store(in: &cancellables)
}
Indeed, the code is not that pretty, but at least there is no memory waste :)
Some high order functions can be used to make this pattern reusable in other places of the same class:
func cleanupCompletion<T>(_ cancellable: AnyCancellable) -> (Subscribers.Completion<T>) -> Void {
return { [weak self] _ in self?.cancellables.remove(cancellable) }
}
func consumePublisher() {
var cancellable: AnyCancellable!
cancellable = makePublisher()
.sink(receiveCompletion: cleanupCompletion(cancellable),
receiveValue: { doSomeWork() })
cancellable.store(in: &cancellables)
}
Or, if you need support to also do work on completion:
func cleanupCompletion<T>(_ cancellable: AnyCancellable) -> (Subscribers.Completion<T>) -> Void {
return { [weak self] _ in self?.cancellables.remove(cancellable) }
}
func cleanupCompletion<T>(_ cancellable: AnyCancellable, completionWorker: #escaping (Subscribers.Completion<T>) -> Void) -> (Subscribers.Completion<T>) -> Void {
return { [weak self] in
self?.cancellables.remove(cancellable)
completionWorker($0)
}
}
func consumePublisher() {
var cancellable: AnyCancellable!
cancellable = makePublisher()
.sink(receiveCompletion: cleanupCompletion(cancellable) { doCompletionWork() },
receiveValue: { doSomeWork() })
cancellable.store(in: &cancellables)
}
It's a nice idea, but there is really nothing to remove. When the completion (finish or cancel) comes down the pipeline, everything up the pipeline is unsubscribed in good order, all classes (the Subscription objects) are deallocated, and so on. So the only thing that is still meaningfully "alive" after your Future has emitted a value or failure is the Sink at the end of the pipeline, and it is tiny.
To see this, run this code
for _ in 1...100 {
self.taskCaller()
}
and use Instruments to track your allocations. Sure enough, afterwards there are 100 AnyCancellable objects, for a grand total of 3KB. There are no Futures; none of the other objects malloced in startSomeTask still exist, and they are so tiny (48 bytes) that it wouldn't matter if they did.
I just want to use function parameter inside swift closure without memory leak, so I just want to confirm if I do in following way will there be any memory related issues? Kindly let me know
func someMethod(someValue: String) {
weak var weakSelf = self
var copyOfSomeValue: String? = someValue.copy() as? String
self.someOtherMethodWithCompletion(completionHandler: { () -> Void in
if let strongSelf = weakSelf, let originalValue = copyOfSomeValue {
strongSelf.updateMyViewWithText(originalValue)
}
})
}
String is a value type. Even though closures capture them by reference, there is no need to make a copy unless you are going to change someValue after the capture. Even then you're better off using capture lists [someValue], which are also used when you need to declare [weak self].
Using weak or unowned is situational, read about them here.
func someMethod(someValue: String) {
someOtherMethodWithCompletion { [weak self] in
self?.updateMyViewWithText(someValue)
}
}
With swift you should use [unowned self] and just use swift as normal, for example:
func someMethod(someValue: String) {
var copyOfSomeValue: String? = someValue.copy() as? String
self.someOtherMethodWithCompletion(completionHandler: { () ->
[unowned self]
in
if let originalValue = copyOfSomeValue {
self.updateMyViewWithText(originalValue)
}
})
}
You should use weak or unowned just to variables which can cause reference cycle. The difference between those is:
Use a weak reference whenever it is valid for that reference to become
nil at some point during its lifetime. Conversely, use an unowned
reference when you know that the reference will never be nil once it
has been set during initialization