Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

typescript - Do subscriptions to rxjs Subjects cause memory leaks if not unsubscribed when the subject goes out of scope?

In my application I have some objects which represent local currency, and other objects which represent currency exchange rates.

My question is if my local currency objects subscribe to a single subject on the currency object to be alerted to rate changes (but the money objects don't actually save the subscription) and then the single currency instance defines the Subject of all those subscriptions is set to null, does all those 'subscriptions' disappear if I haven't called unsubscribe on each of the 50,000 money objects?

For a concrete (simplified) example, this:

import { Subject } from 'rxjs'
interface MyChangeEvent {
  oldValue : number;
  newValue : number;
}
export class Currency {
  rateSubject : Subject<MyChangeEvent>;
  private _rate : number;
  private _name : string;
  constructor(name : string, rate : number) {
    this.rateSubject = new Subject();
    this._rate= rate;
    this._name = name;
  }
  get rate() : number {
    return this._rate;
  }
  set rate(v : number) {
    let oldrate = this.rate;
    this._rate = v;
    let ce : MyChangeEvent
    ce = {} as MyChangeEvent;
    ce.newValue = v;
    ce.oldValue = oldrate;
    this.rateSubject.next(ce);
  }
}


export class Money {
  private _rate : number = 1;
  private _localCurr : number = 0;
 
  get dollarValue() {
    return this._localCurr * this._rate;
  }

  constructor(localCurr : number, curr : Currency) {
    this._localCurr = localCurr;
    this._rate = curr.rate;
    curr.rateSubject.subscribe((change)=>{
        this._rate = change.newValue;
    })
  }
}

const test = function() {
    let c = new Currency("USD", 1);
    let m = new Money(500, c);
    c.rate = .5;
    c=null;
}

So my question is, lets say i have 50,000 money objects, in my application, and I then I set c=null as in the last line here. Do the 50,000 listeners I've set up for all those money objects persist somewhere in memory, are are they all garbage collected when the Currency object goes out of scope?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

EDIT

You could also check out RxJS: Why memory leaks occur when using a Subject.


I'd say that there will be no memory leaks.

This is based on my understanding as to why memory leaks actually occur. Usually this sort of problems take place when the source is infinite(e.g will not complete/error, like a global service that is used by components).

For example, in Angular, a common pattern is to inject a app-scoped service into components and subscribe to one of the observable properties exposed by the service.

class Service {
  private usersSrc = new Subject();
  users$ = this.usersSrc.asObservable();
}

Then you'd do this in your component:

class FooComponent {
  ngOnInit () {
    this.subscription = this.service.users$.subscribe(() => {} /* callback */)
  }
}

Note: this is just for demonstration purposes, as you'd want to use other approaches so that you won't have to manually subscribe, e.g async pipe

When users$ is subscribed, because users$ comes from usersSrc, the newly created subscriber will be added to the Subject's list of subscribers. And that subscriber's next callback will be the () => {} callback.

Now when the component is destroyed(e.g due to navigating to another route), if you don't do something like this.subscription.unsubscribe(), that subscriber will still be part of that subscribers list. The unsubscribe method would remove that subscriber from that list.

So, the next time the component is created and that ngOnInit is created, a new subscriber will be added, but the old one would still be there if you didn't use this.subscription.unsubscribe().


I'd say that setting that source to null would be enough.

If the source happens to be a Subject, you could also use Subject.unsubscribe, although it may not make any difference.

unsubscribe() {
  this.isStopped = true;
  this.closed = true;
  this.observers = null!;
}

Here would be a simplified version. You could paste this in your console.

src = {
 subscribers: [],
 addSubscriber(cb) {
  this.subscribers.push(cb);
  return this.subscribers.length - 1
 },
 removeSubscriber(idx) {
  this.subscribers.splice(idx, 1)
 },
 next (data) {
  this.subscribers.forEach(cb => cb(data));
 }
}

// the component
class Foo {
 
constructor () {
   this.subIdx = src.addSubscriber(() => { console.log('foo') })
 }

 onDestroy () {
  src.removeSubscriber(this.subIdx);
 }
}

// usage

// creating a new component
foo = new Foo() // Foo?{subIdx: 0}

// sending data to subscribers
src.next('test')

// destroying the component - without calling `onDestroy`
foo = null


src.next('test') // the subscribers is still there
VM506:18 foo

foo = new Foo() // registering a new instance - Foo?{subIdx: 1}

src.next('test2')
foo
foo

foo.onDestroy()
src.next('test2')
foo

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...