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?
Related
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)
}
Edit (after discussion):
It's a common practice in SwiftUI to have a View (struct) hold an ObservableObject. Since a struct is not a reference type, it's easy to forget that is holds one.
The questions should be really:
Can an indirect references cause a retain cycle?
Can a retain cycle involve a single pointer
The answer to both is yes.
struct -> class -> closure{struct.class}
class ViewModel: ObservableObject
...
struct MyView: View {
#ObservedObject var viewModel: ViewModel
var body: some View {
Button("Action") {
viewModel?.handler {
// ViewModel is retained here through self:
// (MyView.viewModel)
self.someAction()
}
}
}
}
In SwiftUI, it's not a good practice to 'pass yourself' to your ViewModel
Class holding an instance var pointing to itself
(discussed here How does ARC deal with circular linked lists when the external head reference is removed?)
class A {
var next: A
}
let a = A()
a.next = a // Retain cycle of an object to itself.
(Original question)
Why is holding a reference to a closure cause a memory leak even if the closure does not retain anything?
(This started with a SwiftUI issue, but I'm not sure it's related really)
So here's a simple Manager (ViewModel) that holds a closure.
class Manager: ObservableObject {
private var handler: (() -> Void)?
deinit {
print("Manager deallocated")
}
func shouldDismiss(completion: #escaping () -> Void) {
handler = completion
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.handler?()
}
}
}
Here's a view, using it:
struct LeakingView: View {
#ObservedObject var manager: Manager
let shouldDismiss: () -> Void
var body: some View {
Button("Dismiss") { [weak manager] in
manager?.shouldDismiss {
self.shouldDismiss()
}
}
}
}
Here's a ViewController activating the flow:
class ViewController: UIViewController {
var popup: UIViewController?
#IBAction func openViewAction(_ sender: UIButton) {
let manager = Manager()
let suiView = LeakingView(manager: manager) { [weak self] in
self?.popup?.view.removeFromSuperview()
self?.popup?.dismiss(animated: true)
self?.popup = nil
}
popup = LKHostingView(rootView: suiView)
popup?.view.frame.size = CGSize(width: 300, height: 300)
view.addSubview(popup!.view)
}
}
When running this, as long as the manager is holding the closure, it will leak and the Manager will not get released.
Where is the retain cycle?
Why is holding a reference to a closure cause a memory leak even if the closure does not retain anything?
Because the closure does retain something. It retains self.
For example, when you say
manager?.shouldDismiss {
shouldDismiss()
}
The second shouldDismiss means self.shouldDismiss and retains self. Thus the LeakingView retains the Manager but the Manager, by way of the closure, is now retaining the LeakingView. Retain cycle!
This is why people say [weak self] in at the start of such closures.
I would suggest that you might not want to use [weak self] pattern here. You are defining something that should happen ½ second after the view is dismissed. So you probably want to keep that strong reference until the desired action is complete.
One approach is to make sure that Manager simply removes its strong reference after the handler is called:
class Manager: ObservableObject {
private var handler: (() -> Void)?
func shouldDismiss(completion: #escaping () -> Void) {
handler = completion
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.handler?()
self.handler = nil
}
}
}
Alternatively, if the use of this completion handler is really limited to this method, you can simplify this to:
class Manager: ObservableObject {
func shouldDismiss(completion: #escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completion()
}
}
}
Or
func shouldDismiss(completion: #escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: completion)
}
All of these approaches will make sure that Manager does not keep any strong references beyond the necessary time.
Now, the caller can choose to use [weak self] if it wants (i.e., if it doesn’t want the captured references to even survive ½ second), but if you really want the “do such-and-such after it is dismissed”, it probably wouldn’t use weak references there either. But either way, it should be up to the caller, not the manager. The manager should just ensure that it doesn’t hang on to the closure beyond the point that it is no longer needed.
I'm rewriting parts of an app, and found this code:
fileprivate let defaults = UserDefaults.standard
func storeValue(_ value: AnyObject, forKey key:String) {
defaults.set(value, forKey: key)
defaults.synchronize()
NotificationCenter.default.post(name: Notification.Name(rawValue: "persistanceServiceValueChangedNotification"), object: key)
}
func getValueForKey(_ key:String, defaultValue:AnyObject? = nil) -> AnyObject? {
return defaults.object(forKey: key) as AnyObject? ?? defaultValue
}
When CMD-clicking the line defaults.synchronize() I see that synchronize is planned deprecated. This is written in the code:
/*!
-synchronize is deprecated and will be marked with the NS_DEPRECATED macro in a future release.
-synchronize blocks the calling thread until all in-progress set operations have completed. This is no longer necessary. Replacements for previous uses of -synchronize depend on what the intent of calling synchronize was. If you synchronized...
- ...before reading in order to fetch updated values: remove the synchronize call
- ...after writing in order to notify another program to read: the other program can use KVO to observe the default without needing to notify
- ...before exiting in a non-app (command line tool, agent, or daemon) process: call CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)
- ...for any other reason: remove the synchronize call
*/
As far as I can interpret, the usage in my case fits the second description: synchronizing after writing, in order to notify others.
It suggests using KVO to ovserve, but how? When I search for this, I find a bunch of slightly older Objective-C-examples. What is the best practice for observing UserDefaults?
As of iOS 11 + Swift 4, the recommended way (according to SwiftLint) is using the block-based KVO API.
Example:
Let's say I have an integer value stored in my user defaults and it's called greetingsCount.
First I need to extend UserDefaults with a dynamic var that has the same name as the user defaults key you want to observe:
extension UserDefaults {
#objc dynamic var greetingsCount: Int {
return integer(forKey: "greetingsCount")
}
}
This allows us to later on define the key path for observing, like this:
var observer: NSKeyValueObservation?
init() {
observer = UserDefaults.standard.observe(\.greetingsCount, options: [.initial, .new], changeHandler: { (defaults, change) in
// your change logic here
})
}
And never forget to clean up:
deinit {
observer?.invalidate()
}
From the blog of David Smith
http://dscoder.com/defaults.html
https://twitter.com/catfish_man/status/674727133017587712
If one process sets a shared default, then notifies another process to
read it, then you may be in one of the very few remaining situations
that it's useful to call the -synchronize method in: -synchronize acts
as a "barrier", in that it provides a guarantee that once it has
returned, any other process that reads that default will see the new
value rather than the old value.
For applications running on iOS 9.3
and later / macOS Sierra and later, -synchronize is not needed (or
recommended) even in this situation, since Key-Value Observation of
defaults works between processes now, so the reading process can just
watch directly for the value to change. As a result of that,
applications running on those operating systems should generally never
call synchronize.
So in most likely case you do not need to set to call synchronize. It is automatically handled by KVO.
To do this you need add observer in your classes where you are handling persistanceServiceValueChangedNotification notification. Let say you are setting a key with name "myKey"
Add observer in your class may be viewDidLoad etc
UserDefaults.standard.addObserver(self, forKeyPath: "myKey", options: NSKeyValueObservingOptions.new, context: nil)
Handle the observer
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
//do your changes with for key
}
Also remove your observer in deinit
For anyone who will be looking for the answer in the future, didChangeNotification will be posted only if changes are made on the same process, if you would like to receive all updates regardless of the process use KVO.
Apple doc
This notification isn't posted when changes are made outside the current process, or when ubiquitous defaults change. You can use key-value observing to register observers for specific keys of interest in order to be notified of all updates, regardless of whether changes are made within or outside the current process.
Here is a link to demo Xcode project which shows how to setup block based KVO on UserDefaults.
Swift 4 version made with reusable types:
File: KeyValueObserver.swift - General purpose reusable KVO observer (for cases where pure Swift observables can't be used).
public final class KeyValueObserver<ValueType: Any>: NSObject, Observable {
public typealias ChangeCallback = (KeyValueObserverResult<ValueType>) -> Void
private var context = 0 // Value don't reaaly matter. Only address is important.
private var object: NSObject
private var keyPath: String
private var callback: ChangeCallback
public var isSuspended = false
public init(object: NSObject, keyPath: String, options: NSKeyValueObservingOptions = .new,
callback: #escaping ChangeCallback) {
self.object = object
self.keyPath = keyPath
self.callback = callback
super.init()
object.addObserver(self, forKeyPath: keyPath, options: options, context: &context)
}
deinit {
dispose()
}
public func dispose() {
object.removeObserver(self, forKeyPath: keyPath, context: &context)
}
public static func observeNew<T>(object: NSObject, keyPath: String,
callback: #escaping (T) -> Void) -> Observable {
let observer = KeyValueObserver<T>(object: object, keyPath: keyPath, options: .new) { result in
if let value = result.valueNew {
callback(value)
}
}
return observer
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if context == &self.context && keyPath == self.keyPath {
if !isSuspended, let change = change, let result = KeyValueObserverResult<ValueType>(change: change) {
callback(result)
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
File: KeyValueObserverResult.swift – Helper type to keep KVO observation data.
public struct KeyValueObserverResult<T: Any> {
public private(set) var change: [NSKeyValueChangeKey: Any]
public private(set) var kind: NSKeyValueChange
init?(change: [NSKeyValueChangeKey: Any]) {
self.change = change
guard
let changeKindNumberValue = change[.kindKey] as? NSNumber,
let changeKindEnumValue = NSKeyValueChange(rawValue: changeKindNumberValue.uintValue) else {
return nil
}
kind = changeKindEnumValue
}
// MARK: -
public var valueNew: T? {
return change[.newKey] as? T
}
public var valueOld: T? {
return change[.oldKey] as? T
}
var isPrior: Bool {
return (change[.notificationIsPriorKey] as? NSNumber)?.boolValue ?? false
}
var indexes: NSIndexSet? {
return change[.indexesKey] as? NSIndexSet
}
}
File: Observable.swift - Propocol to suspend/resume and dispose observer.
public protocol Observable {
var isSuspended: Bool { get set }
func dispose()
}
extension Array where Element == Observable {
public func suspend() {
forEach {
var observer = $0
observer.isSuspended = true
}
}
public func resume() {
forEach {
var observer = $0
observer.isSuspended = false
}
}
}
File: UserDefaults.swift - Convenience extension to user defaults.
extension UserDefaults {
public func observe<T: Any>(key: String, callback: #escaping (T) -> Void) -> Observable {
let result = KeyValueObserver<T>.observeNew(object: self, keyPath: key) {
callback($0)
}
return result
}
public func observeString(key: String, callback: #escaping (String) -> Void) -> Observable {
return observe(key: key, callback: callback)
}
}
Usage:
class MyClass {
private var observables: [Observable] = []
// IMPORTANT: DON'T use DOT `.` in key.
// DOT `.` used to define `KeyPath` and this is what we don't need here.
private let key = "app-some:test_key"
func setupHandlers() {
observables.append(UserDefaults.standard.observeString(key: key) {
print($0) // Will print `AAA` and then `BBB`.
})
}
func doSomething() {
UserDefaults.standard.set("AAA", forKey: key)
UserDefaults.standard.set("BBB", forKey: key)
}
}
Updating defaults from Command line:
# Running shell command below while sample code above is running will print `CCC`
defaults write com.my.bundleID app-some:test_key CCC
As of iOS 13, there is now a cooler way to do this, using Combine:
import Foundation
import Combine
extension UserDefaults {
/// Observe UserDefaults for changes at the supplied KeyPath.
///
/// Note: first, extend UserDefaults with an `#objc dynamic` variable
/// to create a KeyPath.
///
/// - Parameters:
/// - keyPath: the KeyPath to observe for changes.
/// - handler: closure to run when/if the value changes.
public func observe<T>(
_ keyPath: KeyPath<UserDefaults, T>,
handler: #escaping (T) -> Void)
{
let subscriber = Subscribers.Sink<T, Never> { _ in }
receiveValue: { newValue in
handler(newValue)
}
self.publisher(for: keyPath, options: [.initial, .new])
.subscribe(subscriber)
}
}
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'd like all publishers to execute unless explicitly cancelled. I don't mind AnyCancellable going out of scope, however based on docs it automatically calls cancel on deinit which is undesired.
I've tried to use a cancellable bag, but AnyCancelable kept piling up even after the publisher fired a completion.
Should I manage the bag manually? I had impression that store(in: inout Set) was meant to be used for convenience of managing the cancellable instances, however all it does is push AnyCancellable into a set.
var cancelableSet = Set<AnyCancellable>()
func work(value: Int) -> AnyCancellable {
return Just(value)
.delay(for: .seconds(1), scheduler: DispatchQueue.global(qos: .default))
.map { $0 + 1 }
.sink(receiveValue: { (value) in
print("Got value: \(value)")
})
}
work(value: 1337).store(in: &cancelableSet)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
print("\(cancelableSet)")
}
What I came up with so far, which works fine but makes me wonder if something is missing in the Combine framework or it was not meant to be used in such fashion:
class DisposeBag {
private let lock = NSLock()
private var cancellableSet = Set<AnyCancellable>()
func store(_ cancellable: AnyCancellable) {
print("Store cancellable: \(cancellable)")
lock.lock()
cancellableSet.insert(cancellable)
lock.unlock()
}
func drop(_ cancellable: AnyCancellable) {
print("Drop cancellable: \(cancellable)")
lock.lock()
cancellableSet.remove(cancellable)
lock.unlock()
}
}
extension Publisher {
#discardableResult func autoDisposableSink(disposeBag: DisposeBag, receiveCompletion: #escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: #escaping ((Self.Output) -> Void)) -> AnyCancellable {
var sharedCancellable: AnyCancellable?
let disposeSubscriber = {
if let sharedCancellable = sharedCancellable {
disposeBag.drop(sharedCancellable)
}
}
let cancellable = handleEvents(receiveCancel: {
disposeSubscriber()
}).sink(receiveCompletion: { (completion) in
receiveCompletion(completion)
disposeSubscriber()
}, receiveValue: receiveValue)
sharedCancellable = cancellable
disposeBag.store(cancellable)
return cancellable
}
}
The subscriptions in Apple Combine are scoped in a RAII compliant fashion. I.e. the event of deinitialization is equivalent to the event of automatic disposal of the observable. That is contrary to RxSwift Disposable where this behavior is sometimes reproduced, but not strictly so.
Even in RxSwift if you lose a DisposeBag your subscriptions will be disposed and this is a feature. If you would like your subscription to live through the scope, it means that it belongs to an outer scope.
And none of these implementations get busy actually tossing out the Disposables out of the retention tree once the subscriptions are done.