Вызывают ли подписки на rx js Subject утечки памяти, если подписка не отменяется, когда тема выходит за рамки? - PullRequest
2 голосов
/ 05 августа 2020

В моем приложении у меня есть некоторые объекты, которые представляют местную валюту, и другие объекты, которые представляют курсы обмена валют.

Мой вопрос в том, подписываются ли мои объекты в местной валюте на один объект в объекте валюты, чтобы получать уведомления чтобы оценить изменения (но объекты денег на самом деле не сохраняют подписку), а затем экземпляр единой валюты определяет, что Subject всех этих подписок имеет значение null, все эти `` подписки '' исчезают, если я не вызвал отмену подписки для каждого из 50000 денежных объектов?

Для конкретного (упрощенного) примера это:

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;
}

Итак, мой вопрос, допустим, у меня есть 50000 денежных объектов в моем приложении, и я затем я устанавливаю c=null как в последней строке здесь. Сохраняются ли где-то в памяти 50 000 слушателей, которые я установил для всех этих денежных объектов, все ли они собираются мусором, когда объект Currency выходит за пределы области видимости?

1 Ответ

8 голосов
/ 05 августа 2020

Я бы сказал, что утечек памяти не будет.

Это основано на моем понимании того, почему действительно происходят утечки памяти. Обычно такого рода проблемы возникают, когда источник бесконечен (например, не завершится / ошибка, как глобальная служба, которая используется компонентами).

Например, в Angular общий шаблон - внедрить сервис в области приложения в компоненты и подписаться на одно из наблюдаемых свойств, предоставляемых службой.

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

Затем вы должны сделать это в своем компоненте:

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

Примечание: это просто для демонстрационных целей, так как вы хотите использовать другие подходы, чтобы вам не приходилось подписываться вручную, например asyn c pipe

When users$ подписан, поскольку users$ происходит от usersSrc, вновь созданный подписчик будет добавлен в список подписчиков Subject. И следующий обратный вызов этого подписчика будет () => {} обратным вызовом.

Теперь, когда компонент уничтожен (например, из-за перехода по другому маршруту), если вы не сделаете что-то вроде this.subscription.unsubscribe(), этот подписчик по-прежнему будет частью этого списка подписчиков. Метод unsubscribe удалит этого подписчика из этого списка.

Таким образом, в следующий раз, когда будет создан компонент и будет создан этот ngOnInit, будет добавлен новый подписчик , но старый остался бы там, если бы вы не использовали this.subscription.unsubscribe().

Я бы сказал, что установки этого источника на нуль будет достаточно.

Если источником окажется Subject, вы также можете использовать Subject.unsubscribe, хотя он может не иметь никакого значения.

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

Вот бы упрощенная версия. Вы можете вставить это в свою консоль.

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
...