PDA

Ver la Versión Completa : Gestion de la memoria


orihuela
15-01-2015, 17:50:28
Hola a todos, tengo una pregunta tecnica que me gustaria resolver para quedarme tranquilo. Estoy desarrollando una aplicacion en delphi para android y estoy utilizando el patrón arquitectónico "modelo-vista-controlador", aparentemente todo funciona, pero si he notado que cuando la app trabaja mucho la memoria ram del dispostivo se llena (aunque no considerablemente), lo que implica que no se esta liberando bien la memoria y particularmente tengo una duda:

tengo una clase modelo generica que crea un componente tsqlquery en tiempo de ejecucion, el controlador llama al modelo y este devuelve un tsqlquery cargado con la consulta pertinente, a continuacion el controlador delvuelve el tsqlquery al formulario(vista). Os pongo un ejemplo generico:

<<vista>>
type
TForm2 = class(TForm)

procedure FormShow(Sender: TObject);


private
sql:tsqlquery;

public
{ Public declarations }
end;

implementation

procedure TForm1.FormShow(Sender: TObject);
var cliente:tcliente;

begin
cliente:= Tcliente.create;

//esta funccion del controlador cliente carga el tsqlquery con la lista de clientes.
cliente.listarcliente(sql);
// una vez cargado la variable sql se realizan mas operaciones;

end;

< controlador cliente, funcion listar cliente >

procedure TCliente.ListarClientes (var SQL: TSQLQUERY);
var modelocliente: TModeloCliente;


begin

// cuando se ejecuta el contructor del modelo cliente, éste crea un tsqlquery y lo inicializa, el destrucctor de la clase libera dicho tsqlquery.
modelocliente:=TModeloCliente.Create;
// esta funcion devuelve el tsqlquery creado en el contructor y cargado con la consulta.
SQL:= modeloCliente.ListarClientes();
end;


Mi pregunta es, ¿ESTA ASIGNACION SE COPIA POR VALOR O POR REFERENCIA?
SQL:= modeloCliente.ListarClientes();

Lo que si puedo decir es que una vez finalizada la funcion del controlador, el destructor del modelo cliente actua y libera el tsqlquery. Aun así la variable sql del formulario esta correctamente cargada y no esta liberada, lo que me hace pensar que es por copia o que no se esta liberado bien. El problema de la ram puede venir por ahí. cada vez que se crea el modelo se crea un nuevo tsqlquery, ¿se habra liberado el espacio reservado para el anterior?

Neftali [Germán.Estévez]
16-01-2015, 10:18:25
Mi pregunta es, ¿ESTA ASIGNACION SE COPIA POR VALOR O POR REFERENCIA?
SQL:= modeloCliente.ListarClientes();

Lo que si puedo decir es que una vez finalizada la funcion del controlador, el destructor del modelo cliente actua y libera el tsqlquery. Aun así la variable sql del formulario esta correctamente cargada y no esta liberada, lo que me hace pensar que es por copia o que no se esta liberado bien. El problema de la ram puede venir por ahí. cada vez que se crea el modelo se crea un nuevo tsqlquery, ¿se habra liberado el espacio reservado para el anterior?

Eso es una asignación de punteros. Al hacerlo SQL debería estar sin crear (apuntando a nil), pues si lo estuviera sí que perderías acceso a la memoria apuntada y por lo tanto tendrías pérdida de memoria.

Al liberar el Query, la variable SQL no se apunta a nil, a no ser que lo hagas manualmente. Si intentas hacer algo con va variable SQL seguramente obtendrás un access Violation, pues esa memoria a la que apunta ya ha sido liberada.

AgustinOrtu
16-01-2015, 19:25:09
Esta medio raro tu modelo de datos.

Lo que se ve a simple vista es que tenes una variable llamada "cliente" de tipo TCliente, es decir la variable "cliente" deberia representar solo UNO de los clientes de tu negocio, este deberia conocer su nombre, apellido, mail, y tener metodos como por ej. DeudaPendiente, UltimaCompraRealizada, ProductoMasComprado, etc. Recordar que es UNA instancia de TCLIENTE, siendo TCliente una estructura con metodos y atributos que tienen todos los clientes.

Ahora para modelar comportamiento de varios clientes yo lo que hago es tener otra clase encargada de eso, por ej una TClientMgr o TClientHandler. Esta clase es la que se encarga de "pedirle" a la capa de acceso datos que me traiga la o las instancias de TCliente que necesito. Esta clase por ejmplo conoce metodos como ObtenerCliente(Id: integer): TCliente, ObtenerClientes(<parametros de busqueda>): TList<TCliente> y demases

Es solo una sugerencia para tu codigo, tu pregunta ya la respondio Neftali

orihuela
20-01-2015, 16:37:36
Eso es una asignación de punteros. Al hacerlo SQL debería estar sin crear (apuntando a nil), pues si lo estuviera sí que perderías acceso a la memoria apuntada y por lo tanto tendrías pérdida de memoria.

Al liberar el Query, la variable SQL no se apunta a nil, a no ser que lo hagas manualmente. Si intentas hacer algo con va variable SQL seguramente obtendrás un access Violation, pues esa memoria a la que apunta ya ha sido liberada.

Eso es lo que en primera instancia yo creia, que es una asigancion de punteros, pero entonces no entiendo algunos comportamientos: sea el siguiente ejemplo:


procedure TCliente.ListarClientes (var SQL: TSQLQUERY; filtro: TDictionary<String,String>;orden: TDictionary<String,String>);
var modelocliente: TModeloCliente;
sqlaux:tsqlquery;


begin
modelocliente:=TModeloCliente.Create;
freeandnil(sql);
SQL:= modeloCliente.ListarClientes(filtro,orden);
sqlaux:=SQL;
showmessage(sqlaux.Text);
freeandnil(sqlaux);
Showmessage(SQL.Text);
end;


Si realmente es una asigancion de punteros, el ultimo showmessage deberia lanzarme un acess violation puesto que se ha liberado el espacio al que apunta, y para mi sorpresa me muestra el showmessage.
¿Porque?,¿Tal vez sea por como se gestiona la memoria en android?.


De todas maneras así ha quedado el código final, que creo que es lo que te entendí. (Se han añadido las variables reales de la funcion):

<Controlador cliente, Procedimiento ListarClientes >



procedure TCliente.ListarClientes (var SQL: TSQLQUERY; filtro: TDictionary<String,String>;orden: TDictionary<String,String>);
var modelocliente: TModeloCliente;

begin


modelocliente:=TModeloCliente.Create;
// Libero el espacio al que apuntaba anteriomente SQL.
freeandnil(sql);
// Se asigna SQL el nuevo Tsqlquery.
SQL:= modeloCliente.ListarClientes(filtro,orden);

end;


<Modelo cliente >

type
TModeloCliente = class(TModelo)
private
{}
protected
{ Declaraciones protegidas de la clase }
public
function ListarClientes( filtro:TDictionary<String,String>;orden:TDictionary<String,String> ):TSQLQuery;
function ConsultarCliente( codigo: string ): TSQLQuery;
function ObtenerRecargo(codigo:string):string;
function ConsultarClientesCompletable(codigo:string): TSQLQuery;
function Obtenerrutas():TSQLQuery;
published
{ Declaraciones publicadas de la clase }
end;

<Modelo Padre>
var cdatos: TSQLConnection;
QueryGlobal: integer;




type
TModelo = class(TObject)
private
//esta variable es la que se manda cargada con la consulta pertimente, en listarclientes
SQL: TSQLQuery;
protected
function DevolverBD():TSQLQuery;
public
constructor Create;overload;
Destructor Destroy; override;

procedure EjecutarInstruccion( instruccion:String );
procedure EjecutarModificacion( instruccion:String );
function Genera_Sentencia_Insert(SQL:TDataSet;nombre:String;Campos:Array of String):TStrings;
function Genera_Sentencia_Update(SQL:TDataSet;nombre,Condicion:String;Campos:Array of String):TStrings;
procedure EjecutarInsert(nombre:string;Campos:array of string;Valores: Array of variant);
procedure EjecutarUpdate(nombre,condicion:string;Campos:array of string;Valores: Array of variant);


published
{ Declaraciones publicadas de la clase }
end;


< os pongo el codigo del constructor y del destructor, por sí aclara mas la situación >


constructor TModelo.Create;
begin
inherited;

if ( cdatos = nil ) then
begin
cdatos:=TSQLConnection.Create(Application);
cdatos.DriverName:='SQLite';
cdatos.ConnectionName:='cdatos';
cdatos.Name:='cdatos';
cdatos.KeepConnection:=True;
cdatos.LoginPrompt:=False;

cdatos.Params.Add('DriverName=SQLite');
cdatos.Params.Add('DriverUnit=Data.DbxSqlite');
cdatos.Params.Add('DriverPackageLoader=TDBXSqliteDriverLoader,DBXSqliteDriver170.bpl');
cdatos.Params.Add('MetaDataPackageLoader=TDBXSqliteMetaDataCommandFactory,DbxSqliteDriver170.bpl');
cdatos.Params.Add('FailIfMissing=True');
cdatos.Params.Add('Database='+TPath.Combine(TPath.GetSharedDocumentsPath, 'Cdatos.s3db'));

cdatos.Connected:=True;
QueryGlobal:=0;
end;

SQL:=TSQLQuery.Create(nil);
SQL.SQLConnection:=cdatos;
SQL.Name:='name'+ IntToStr(QueryGlobal);
QueryGlobal := QueryGlobal + 1;

end;


destructor TModelo.Destroy;
begin


//freeandnil(sql);

// actualmente la sentecia freeandnill(sql) esta comentada. por lo que dijistes de que
//si liberamos esta variable, cuando se destrulla el modelo, las demas asignaciones que se han
// hecho de esta variable deberian fallar, puesto que a lo que apuntan estaria liberado. aunque la practica no lo confirme.


inherited;
end;


No se si me habre explicado bien puesto que el tema no es facil de explicar, pero si necesitas de alguna función mas para su compresión por favor solicitamelo. Un saludo y muchisimas gracias

orihuela
20-01-2015, 17:21:56
Esta medio raro tu modelo de datos.

Lo que se ve a simple vista es que tenes una variable llamada "cliente" de tipo TCliente, es decir la variable "cliente" deberia representar solo UNO de los clientes de tu negocio, este deberia conocer su nombre, apellido, mail, y tener metodos como por ej. DeudaPendiente, UltimaCompraRealizada, ProductoMasComprado, etc. Recordar que es UNA instancia de TCLIENTE, siendo TCliente una estructura con metodos y atributos que tienen todos los clientes.

Ahora para modelar comportamiento de varios clientes yo lo que hago es tener otra clase encargada de eso, por ej una TClientMgr o TClientHandler. Esta clase es la que se encarga de "pedirle" a la capa de acceso datos que me traiga la o las instancias de TCliente que necesito. Esta clase por ejmplo conoce metodos como ObtenerCliente(Id: integer): TCliente, ObtenerClientes(<parametros de busqueda>): TList<TCliente> y demases

Es solo una sugerencia para tu codigo, tu pregunta ya la respondio Neftali

Si, estuve apunto de obtar por el modelo que me dices para la realización del proyecto, pero al final me decidi a trabajar con este modelo, donde el modelo cliente por ejemplo, trata todos los acesos a la bd que tengan que ver con clientes, por tanto una instancia de la tabla cliente de la base de datos representa a un objeto cliente ( que se podria almacenar en una estructura como comentas). Este modelo responde al controlador que le realiza la peticion y este controlador hace de intemediario entre la vista (form) y la capa de datos (modelo);
Lo tendré en cuenta para futuros proyectos, gracias

Neftali [Germán.Estévez]
20-01-2015, 18:08:25
Por favor, utiliza TAG's cuando escribas código Delphi. (http://tinyurl.com/bvzsyhb)

Neftali [Germán.Estévez]
20-01-2015, 18:13:24
Si realmente es una asigancion de punteros, el ultimo showmessage deberia lanzarme un acess violation puesto que se ha liberado el espacio al que apunta, y para mi sorpresa me muestra el showmessage.
¿Porque?,¿Tal vez sea por como se gestiona la memoria en android?.

Es posible que sea algo diferente en la gestión o liberación de memoria, porque si creas una estructura similar en Windows, verás que al realizar el último ShowMessage obtienes un "Access Violation"

orihuela
20-01-2015, 18:36:35
Gracias por su atención de momento se va a quedar así, seguire investigando de como puedo bajar la ram. gracias por todo