OnNext not called when disposing the result of Observable.subscribe - swift

I'm subscribing to an observable, but if I'm adding the disposable to the DisposeBag in my class, the onNext block is never called.
Here is my code :
#objc class AppleMusicPlaylistManager: NSObject {
let disposeBag = DisposeBag()
let playlists: [MPMediaPlaylist] = []
func importAppleMusicPlaylist() {
playlists.forEach { applePlaylist in
applePlaylist.getItunesStoreTracks().subscribe(onNext: { tracks in
// Doing things here
}).addDisposableTo(disposeBag)
}
}
}
where getItunesStoreTracks return a RxSwift.Observable<[SoundsMusicITunesStore]> and the whole thing is used like that AppleMusicPlaylistManager().importAppleMusicPlaylist()

It all works as expected.
Current logic with disposeBag points out, that the observables will not be disposed of until the disposeBag is alive.
In your case - AppleMusicPlaylistManager().importAppleMusicPlaylist(), you create a manager and then you call the async requests, while the manager is deallocating. Thus all observables are deallocating as well.
In order for this to work correctly, you either have to set this manager as shared and use this method: AppleMusicPlaylistManager.shared.importAppleMusicPlaylist() or save this manager to some property in order to not deallocate immediately.

Related

Weakly captured self won't let the view model deallocate until the Task finishes

I am trying to learn ARC and I'm having a hard time with a weakly captured self. My project is using MVVM with SwiftUI. I'm presenting a sheet (AuthenticationLoginView) that has a #StateObject var viewModel = AuthenticationLoginViewModel() property. On dismiss of the presented sheet, I expect that the viewModel will have it's deinit called and so it does until I run an asynchronous function within a Task block.
class AuthenticationLoginViewModel: ObservableObject {
#Published var isLoggingIn: Bool = false
private var authenticationService: AuthenticationService
private var cancellables: Set<AnyCancellable> = Set()
private var onLoginTask: Task<Void, Never>?
init(authenticationService: AuthenticationService) {
self.authenticationService = authenticationService
}
deinit {
onLoginTask?.cancel()
LoggerService.log("deallocated")
}
public func onLogin() {
guard !isLoggingIn else { return }
isLoggingIn = true
onLoginTask = Task { [weak self] in
await self?.login()
}
}
private func login() async {
LoggerService.log("Logging in...")
sleep(2)
//
// self is still allocated here <<<---- ???
//
let authResponse = try? await self.authenticationService.authenticate(username: username, password: password)
LoggerService.log(self.isLoggingIn) // <<--- prints `true`
handleLoginResponse(authResponse: authResponse)
}
}
So I have my two cases here:
Case #1
I present the sheet.
I dismiss the sheet.
The deinit function is getting called (app logs: "deallocated")
Case #2
I present the sheet.
I press the login button so the onLogin function is getting called.
I dismiss the sheet before the sleep(2) ends.
---- I EXPECT the "deallocated" message to be printed from deinit and the logging at LoggerService.log(self.isLoggingIn) to print nil and the self.authenticationService.authenticate(... to never be called as self doesn't exist anymore.
Not expected but happening: the app prints "Logging in", sleeps for 2 seconds, calls the service, prints true, and then deallocates the view model (the view was dismissed 2 seconds ago)
What am I doing wrong?
I'm still learning and I'm pretty much unsure if this is normal or I miss something. Anyway, I expect the view model to be deallocated as the view referencing it was dismissed.
At the time you call onLogin the reference to self is valid and so the Task commences.
After that, the reference to self in login keeps self alive. The Task has a life of its own, and you did not cancel it.
Moreover the use of sleep is wrong, as it is not cancellable in any case, so neither is your Task. Use Task.sleep.

Swift Combine how Set<AnyCancellable> works?

I have ViewModel with disposable Set defined this way
class ViewModel {
private var disposables = Set<AnyCancellable>()
func sync() {
repo.syncObjects()
.handleEvents(receiveCancel: {
print("Synced objects: CANCELED!")
})
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print("Synced objects: \(error)")
case .finished:
print("Synced objects: finished")
}
}) { objects in
print("Synced objects: \(objects)")
}.store(in: &disposables)
}
deinit { print("ViewModel deinit") }
}
I am calling sync() in onAppear in SwiftUI view. Then I am fast switching screens and ViewModel referenced from SwiftUI view is deallocated by ARC like deinit is called but subscriptions seems to remain alive and disposable reference does not cancel subscription it fetches data from Network and saved them in Core Data and prints Synced objects: objects, Synced objects: finished. And keeps being alive even when I stop switching screens for several seconds to complete old requests.
Should I manually cancel AnyCancellable? shouldn't it be cancelled automagically?
No, you dont need to cancel any cancellable because this kind of object calls the method cancel when deinitialized. So your code is correct.
Apple's documentation of AnyCancellable:
An AnyCancellable instance automatically calls cancel() when deinitialized
https://developer.apple.com/documentation/combine/anycancellable

Retaining a Signal or SignalProducer?

Is it my responsibility to maintain a reference to a Signal or a SignalProducer, e.g., using an instance variable?
In other words, will they automatically disappear and stop sending events when they get deallocated?
FYI, not necessary, the Signal will be disposed and stop forwarding events.
Signalis a class type, if no one have a reference to it, it should be deinit.
However, Signal implementation introduces a tricky way to retain itself, see state property, so that there exists some memory leaks in temporary. As seen in source code, if there have some observers subscribe on the Signal, it's state does retain it in turn until all observers unsubscribed or the Signal received completed/error/interrupted event.
Here some marked code snippets.
// definition of SignalState
private struct SignalState<Value, Error: Swift.Error> {
var observers: Bag<Signal<Value, Error>.Observer> = Bag()
var retainedSignal: Signal<Value, Error>? // here is the key
}
public func observe(_ observer: Observer) -> Disposable? {
var token: RemovalToken?
state.modify {
$0?.retainedSignal = self // retain self when one observer on
token = $0?.observers.insert(observer)
}
if let token = token {
return ActionDisposable { [weak self] in
if let strongSelf = self {
strongSelf.state.modify { state in
state?.observers.remove(using: token)
if state?.observers.isEmpty ?? false {
// break retain cycle when disposed
state!.retainedSignal = nil
}
}
}
}
} else {
observer.sendInterrupted()
return nil
}
}
How about SignalProducer?
It is really intuitive, SignalProducer is just struct type, and you should not consider its lifetime.

Inter-thread inter-object communication in Swift 3 with Cocoa

My program consists of three relatively-distinct areas: listening on a network for new state, performing network actions, and updating the UI. So respectively I want three classes: StateListener, ActionSender, and ViewController, each chugging along on separate threads.
Would that it were so simple -- the three need to interact. Some states discovered by the StateListener require Actions to be sent by the ActionSender or the UI to be updated by the ViewController. Some responses to Actions require the UI to be updated by the ViewController. Some UI actions require Actions to be performed by the ActionSender.
Currently I do something like this (pseudocode):
/* ViewController.swift */
class ViewController : blah
{
//...
func buttonPressed()
{
// ?! Need to do an action here but I can't
// because actionSender is initialised below...
}
func viewDidLoad()
{
let actionSender = ActionSender(m_view: self)
let actionQueue = OperationQueue()
let stateListener = StateListener(m_view: self,
m_actionSender: actionSender,
m_actionQueue: actionQueue)
let stateQueue = OperationQueue()
stateQueue.addOperation(stateQueue.listen())
}
}
/* StateListener.swift */
class StateListener
{
// ...
func listen()
{
while true
{
var state = waitForNewState()
if shouldActOn(state)
{
m_actionQueue.addOperation(m_actionSender.act())
}
}
}
}
/* ActionSender.swift */
class ActionSender
{
// ...
func act()
{
var reply = sendAction()
OperationQueue.main.addOperation(m_view.m_textBox.append(reply))
}
}
This is fairly hellish and doesn't even do what I want it to do, because I can't have the ViewController perform actions (ActionSender's require a ViewController reference to update the view after the action, but I tried initialising the ActionSender within ViewController.init and I got bizarre errors to do with a Code.init that I hadn't implemented...). I want to get above ViewController and initialise all these OperationQueues and objects wherever ViewController gets initialised, but I can't find where that is...
What I've done above is basically object-reference injection of each object and OperationQueue. I know there are other ways of doing this (a hierarchy of callbacks, NSNotifications) but I'm unsure of which is best.
My question is in two parts:
What is the best (i.e., fastest, easiest to implement and maintain, most idiomatic in Swift) way to get the inter-object and inter-thread communication I desire?
I currently get things going from ViewController's viewDidLoad function, which seems awful (and means I can't get a 'higher-up' perspective of the ViewController. Where should this stuff go? AppDelegate advertises itself as the 'program startup' area, but I can't access the ViewController from there... XCode seems to have hidden the startup of my app from me!
I really appreciate your responses!
This post greatly helped with question 2 and the problem of passing self as a parameter to a data member in an initialiser : http://blog.scottlogic.com/2014/11/20/swift-initialisation.html
Specifically, I can use this pattern:
class Foo : blah
{
var m_bar : Bar!
init() {
// notice I get away with not initialising m_bar
}
func viewDidLoad {
m_bar = Bar(m_foo: self)
m_bar.doYourThing()
}
}
The blog prefers the following, which I feel I should add here out of gratitude to the author, though I prefer the above.
class Foo : blah
{
lazy var m_bar : Bar = {
return Bar(m_foo: self) // notice I can pass in self
}
init() {
// notice I get away with not initialising m_bar
}
func viewDidLoad {
m_bar.doYourThing()
}
}

Swift: possible removeObserver cyclic reference in addObserverForName's usingBlock

I'm toying around with a small Swift application. In it the user can create as many MainWindow instances as he wants by clicking on "New" in the application's menu.
The application delegate holds an array typed to MainWindowController. The windows are watched for the NSWindowWillCloseNotification in order to remove the controller from the MainWindowController array.
The question now is, if the removal of the observer is done correctly – I fear there might be a cyclic reference to observer, but I don't know how to test for that:
class ApplicationDelegate: NSObject, NSApplicationDelegate {
private let notificationCenter = NSNotificationCenter.defaultCenter()
private var mainWindowControllers = [MainWindowController]()
func newWindow() {
let mainWindowController = MainWindowController()
let window = mainWindowController.window
var observer: AnyObject?
observer = notificationCenter.addObserverForName(NSWindowWillCloseNotification,
object: window,
queue: nil) { (_) in
// remove the controller from self.mainWindowControllers
self.mainWindowControllers = self.mainWindowControllers.filter() {
$0 !== mainWindowController
}
// remove the observer for this mainWindowController.window
self.notificationCenter.removeObserver(observer!)
}
mainWindowControllers.append(mainWindowController)
}
}
In general you should always specify that self is unowned in blocks registered with NSNotificationCenter. This will keep the block from having a strong reference to self. You would do this with a capture list in front of the parameter list of your closure:
{ [unowned self] (_) in
// Block content
}