Перейти к содержанию
Fire Monkey от А до Я
  • 0

Утечка при использовании анонимного метода или анонимные методы, циклические ссылки и ARC


ENERGY

Вопрос

Это касается ARC компиляторов, Android, iOS и будущего Linux.

Если контейнер владелец, содержит классы, которые используют анонимный метод для общения с ним (классом владельцем), то такая конструкция порождает утечку памяти из-за появления циклической ссылки. Т.к. при присваивании анонимного метода инкрементируется счетчик ссылок и не меняется. Причем это не указано в хелпе.


А дело было так - при вызове MyCore.Free класс не уничтожался - не вызывался деструктор из за того, что после вызова Free, счетчик ссылок (reference Count) был равен 1. Приходилось пользоваться  DisposeOf. Решил разобраться.

Для этого перекрыл виртуальные методы TObject отвечающие за изменения счетчика объекта (см. также полный пример ниже).

function __ObjAddRef: Integer; override;
function __ObjRelease: Integer; override;

 

Итак TCore содержит класс TTestClass - у которого есть событие OnMyEvent. 
Прототип описан как анонимная процедура - TAnonymProc = reference to procedure;


При указании анонимной процедуры, т.е. : 

procedure TCore.SetEvent;
begin
  fTest.OnMyEvent := procedure ()
  begin
    fSetFlag := true;
  end;
end;

счетчик ссылок TCore увеличивается на 1 и не изменяется при выходе из SetEvent.

Теперь при вызове Free TCore - не будет вызван деструктор, который должен уничтожит классы TCore и TTestClass и произойдет утечка памяти.


Решение : 
1. Использовать слабые ссылки - weak, в нашем примере добавить атрибут [weak]:

TTestClass = class
strict private
  [weak]fOnMyEvent:TAnonymProc;
public
  property OnMyEvent: TAnonymProc read fOnMyEvent write fOnMyEvent;
end; 

При присваивании объекта в переменную со слабой ссылкой не происходит увеличение счётчика ссылок объекта на единицу. Аналогично, при очистке слабой ссылки не происходит уменьшение счётчика объекта на единицу.

2. Не использовать анонимные методы, а использовать обычные указатели на метод: 
Вместо TAnonymProc = reference to procedure; используем классический 
       TAnonymProc = procedure of object;


Демо пример, где можно отследить утечку прикрепил.

Полный код: 

Спойлер

 


unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls;

type
  TAnonymProc = reference to procedure;
  TTestClass = class
  strict private
    fOnMyEvent:TAnonymProc;
  public
    property OnMyEvent: TAnonymProc read fOnMyEvent write fOnMyEvent;
  end;

  TCore = class (TObject)
  strict private
    fTest: TTestClass;
    fSetFlag: boolean;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SetEvent;
    function __ObjAddRef: Integer; override;
    function __ObjRelease: Integer; override;
  end;


  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    fCore: TCore;
  public
    { Public declarations }
  end;


var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.Button2Click(Sender: TObject);
begin
  fCore := TCore.Create;
  fCore.SetEvent;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Tag := fCore.RefCount;
  fCore.Free;
end;

{ TCore }

constructor TCore.Create;
begin
  fTest := TTestClass.Create;
end;

destructor TCore.Destroy;
begin
  fTest.Free;
  inherited;
end;

procedure TCore.SetEvent;
begin
  fTest.OnMyEvent := procedure ()
  begin
    fSetFlag := true;
  end;

end;

// test it on ARC compilers like Android, iOS
function TCore.__ObjAddRef: Integer;
begin
  Result := inherited __ObjAddRef;
end;

function TCore.__ObjRelease: Integer;
begin
  Result := inherited __ObjRelease;
end;

end.

 

 

 

AnonMethodsCycle.zip

Изменено пользователем ENRGY
Ссылка на комментарий

Рекомендуемые сообщения

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...