Моя цель - получить интерфейс настроек, в котором хранится информация о моих настройках приложения. Это может быть информация, которая актуальна только во время работы приложения, и информация, которая должна быть постоянной. Для этого я подумал, что было бы неплохо создать интерфейс приложения и интерфейс постоянных настроек внутри этого интерфейса настроек приложения, чтобы я мог переключаться между, скажем, Ini-файлом, БД, зашифрованным файлом, чем угодно.... потом. Код работает, я думаю. Но при закрытии FastMM оставляет меня с утечкой памяти в TiniFile, и я не знаю, почему. Вот блок интерфейса с классами.
unit UAPPSettings;
interface
uses
IniFiles;
type
IPersistentSettings = Interface
['{11542859-DEA3-4DBB-9A88-2068E407552C}']
function ReadString(Section, Name, Default: String): string;
procedure WriteString(Section, Name, Default: String);
function ReadPassword: string;
procedure WritePassword(Password: String);
end;
type
IAppSettings = Interface
['{559B8219-7D81-44CA-87A0-6D261B4A87E7}']
function GetPersistentSettings: IPersistentSettings;
property PersistentSettings: IPersistentSettings read GetPersistentSettings;
End;
type
TAppSettings = class(TInterfacedObject, IAppSettings)
strict private
FPersistentSettings: IPersistentSettings;
function GetPersistentSettings: IPersistentSettings;
procedure SetPersistentSettings(const Value: IPersistentSettings);
property PersistentSettings: IPersistentSettings read GetPersistentSettings write SetPersistentSettings;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
constructor create(aPersistentSettings: IPersistentSettings);
end;
type
TIniSettings = class(TInterfacedObject, IPersistentSettings)
strict private
FIniFile: TIniFile;
FfilePath: String;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
function ReadString(Section, Name, Default: String): string;
procedure WriteString(Section, Name, Default: String);
function ReadPassword: string;
procedure WritePassword(Password: string);
public
constructor create(FilePath: String);
end;
var
APPSettings: IAppSettings;
implementation
uses
FMX.Types;
{ TIniSettings }
constructor TIniSettings.create(FilePath: String);
begin
FfilePath := FilePath;
end;
function TIniSettings.ReadPassword: string;
begin
Result := ReadString('App', 'Passwort', '');
end;
function TIniSettings.ReadString(Section, Name, Default: String): string;
begin
If FIniFile.ValueExists(Section, Name) = false then
WriteString(Section, Name, Default);
Result := FIniFile.ReadString(Section, Name, Default);
end;
procedure TIniSettings.WritePassword(Password: string);
begin
WriteString('App', 'Passwort', Password);
end;
procedure TIniSettings.WriteString(Section, Name, Default: String);
begin
FIniFile.WriteString(Section, Name, Default);
end;
function TIniSettings._AddRef: Integer;
begin
log.d('IniSettings _AddRef');
FIniFile := TIniFile.create(FfilePath);
Result := inherited _AddRef;
end;
function TIniSettings._Release: Integer;
begin
log.d('IniSettings _Release');
Result := inherited _Release;
FIniFile.Free;
FIniFile := nil;
end;
{ TAppSettings }
constructor TAppSettings.create(aPersistentSettings: IPersistentSettings);
begin
FPersistentSettings := aPersistentSettings;
end;
function TAppSettings.GetPersistentSettings: IPersistentSettings;
begin
Result := FPersistentSettings;
end;
procedure TAppSettings.SetPersistentSettings(const Value: IPersistentSettings);
begin
FPersistentSettings := Value;
end;
function TAppSettings._AddRef: Integer;
begin
log.d('AppSettings _AddRef');
Result := inherited _AddRef;
end;
function TAppSettings._Release: Integer;
begin
log.d('AppSettings _Release');
Result := inherited _Release;
FPersistentSettings := nil;
end;
end.
Вот как я создаю объект:
APPSettings := TAppSettings.create(TIniSettings.create(System.IOUtils.TPath.GetDocumentsPath + System.SysUtils.PathDelim + 'config.ini'));
Любые предложения по улучшению кодирования также очень ценятся
Подумайте о том, что происходит в TIniSettings._AddRef, когда счетчик ссылок становится равным 2. А затем делайте то, что сказал @DelphiCoder.





Проблема в вашем коде заключается в том, что вы используете методы _AddRef и _Release для целей, отличных от управления памятью экземпляра, для которого вызываются эти два метода.
Следующий код является причиной проблемы:
function TIniSettings._AddRef: Integer;
begin
log.d('IniSettings _AddRef');
FIniFile := TIniFile.create(FfilePath);
Result := inherited _AddRef;
end;
function TIniSettings._Release: Integer;
begin
log.d('IniSettings _Release');
Result := inherited _Release;
FIniFile.Free;
FIniFile := nil;
end;
Вы создаете экземпляр FIniFile в методе _AddRef, и это не то место для этого, а _Release — неправильное место для освобождения FIniFile.
Методы _AddRef и _Release можно вызывать несколько раз в течение жизни экземпляра, и каждый вызов _AddRef приведет к созданию нового экземпляра TIniFile.
Для правильного управления памятью эти два метода будут вызываться одинаковое количество раз, но возможно, что _AddRef будет вызываться несколько раз подряд, а затем несколько вызовов _Release последуют в какой-то момент в будущем в зависимости от кода.
Другими словами, также возможны следующие последовательности:
_AddRef
_AddRef
_Release
_Release
или
_AddRef
_AddRef
_Release
_AddRef
_Release
_Release
Когда вы вызываете APPSettings := TAppSettings.create(TIniSettings.create(..., сначала будет вызываться _AddRef при передаче экземпляра в качестве параметра aPersistentSettings, потому что он не объявлен как константа. Затем будет вызван следующий _AddRef при назначении aPersistentSettings на FPersistentSettings, и этот второй вызов создаст утечку экземпляра TIniFile.
В конце вызова конструктора будет соответствующий _Release вызов для aPersistentSettings вызова параметра _AddRef. Это также освободит ini-файл, и FIniFile будет nil и непригодным для использования, пока вы снова не активируете подсчет ссылок на экземпляр FPersistentSettings.
Вы могли бы создать FIniFile либо в конструкторе TIniSettings и уничтожить его в его деструкторе, либо ввести дополнительные методы (Open/Close) в TIniSettings, которые будут служить этой цели, если вы не хотите, чтобы этот ini-файл создавался все время, и вы бы вызывайте их вокруг любых операций чтения или записи.
И когда вы удаляете код, связанный с ini-файлом, из _AddRef и _Release, вам больше не нужно реализовывать их в своих классах, поскольку реализация по умолчанию сделает свою работу.
Существует дополнительный способ исправить код, создав TIniFile только в том случае, если он не назначен в _AddRef, но методы подсчета ссылок по-прежнему не подходят для таких вещей, поскольку вам нужно убедиться, что вы случайно не запускаете дополнительный подсчет ссылок в коде. неправильное место, которое может оставить вас с нулевой переменной FIniFile, хотя она вам все еще может понадобиться.
function TIniSettings._AddRef: Integer;
begin
log.d('IniSettings _AddRef');
if not Assigned(FIniFile) then
FIniFile := TIniFile.create(FfilePath);
Result := inherited _AddRef;
end;
Спасибо за этот подробный ответ. Я не знал о разнице между подсчетом ссылок и управлением памятью. Спасибо!
Подсчет ссылок — это метод управления памятью. Когда счетчик ссылок падает до нуля, экземпляр освобождается. И фактический подсчет ссылок происходит в методах _AddRef и _Release, которые компилятор вставляет в соответствующие места. Если вы проверите реализацию в TInterfacedObject, вы увидите, как она работает. Другой вариант с отключенным управлением памятью можно увидеть в TNoRefCountObject или TComponent. Если подсчет ссылок отключен, экземпляры такого класса необходимо освобождать вручную.
Почему бы вам не поместить обработку памяти
TIniFileв классыconstructorиdestructor?