Проблема перекрёстных ссылок. Как её решить? |
> Имею два объекта, каждый из которых ссылается на другого.
> При их уничтожении они остаются в памяти. Имеется ли способ корректного их
удаления?
> Вот простой пример:
*//////////////////////////////////// save as any.prg ACTIVATE SCREEN CLEAR PUBLIC goObj1, goObj2 goObj1 = NewObject("MyCls1") goObj2 = NewObject("MyCls2") goObj1.oProp2 = goObj2 goObj2.oProp1 = goObj1 DO cleanup DEFINE CLASS MyCls1 As Session Name = "MyCls1" oProp2 = NULL PROCEDURE Destroy ACTIVATE SCREEN ? "Destroy: " + this.Name this.oProp2 = NULL ENDPROC ENDDEFINE DEFINE CLASS MyCls2 As Session Name = "MyCls2" oProp1 = NULL PROCEDURE Destroy ACTIVATE SCREEN ? "Destroy: " + this.Name this.oProp1 = NULL ENDPROC ENDDEFINE PROCEDURE cleanup ACTIVATE SCREEN ? "Call Cleanup" IF Type('goObj1') = 'O' AND !IsNull(goObj1) RELEASE goObj1 ENDIF IF Type('goObj2') = 'O' AND !IsNull(goObj2) RELEASE goObj2 ENDIF */////////////////////////////// the end of any.prg
> Если проследить работу приведённого выше примера в
отладчике,
> то можно легко увидеть, что события Destroy() у обоих классов не
отрабатывают :-(
Да, это так. Проблема в том, что событие Destroy() не может быть выполнено
потому, что есть свойство-ссылка на существующий объект... и чтобы избавиться,
необходимо выполнить её уничтожение до события Destroy() объекта. Например так:
*//////////////////////////////////// save as any.prg ACTIVATE SCREEN CLEAR PUBLIC goObj1, goObj2 goObj1 = NewObject("MyCls1") goObj2 = NewObject("MyCls2") goObj1.oProp2 = goObj2 goObj2.oProp1 = goObj1 DO cleanup DEFINE CLASS MyCls1 As Session Name = "MyCls1" oProp2 = NULL PROCEDURE RemoveAllRefs this.oProp2 = NULL ENDPROC PROCEDURE Destroy ACTIVATE SCREEN ? "Destroy: " + this.Name this.RemoveAllRefs() && Let's hope that in the future versions it will be was correctly :-) ENDPROC ENDDEFINE DEFINE CLASS MyCls2 As Session Name = "MyCls2" oProp1 = NULL PROCEDURE RemoveAllRefs this.oProp1 = NULL ENDPROC PROCEDURE Destroy ACTIVATE SCREEN ? "Destroy: " + this.Name this.RemoveAllRefs() ENDPROC ENDDEFINE PROCEDURE cleanup ACTIVATE SCREEN ? "Call Cleanup" IF Type('goObj1') = 'O' AND !IsNull(goObj1) goObj1.RemoveAllRefs() RELEASE goObj1 ENDIF IF Type('goObj2') = 'O' AND !IsNull(goObj2) goObj2.RemoveAllRefs() RELEASE goObj2 ENDIF */////////////////////////////// the end of any.prgниже аналогичный пример для классов, производных от Form
*//////////////////////////////////// save as any.prg #DEFINE USE_RELEASE .F. ACTIVATE SCREEN CLEAR PUBLIC goObj1, goObj2 goObj1 = NewObject("MyCls1") goObj2 = NewObject("MyCls2") goObj1.oProp2 = goObj2 goObj2.oProp1 = goObj1 DO cleanup DEFINE CLASS MyCls1 As Form Name = "MyCls1" oProp2 = NULL PROCEDURE Destroy ACTIVATE SCREEN ? "Destroy: " + this.Name this.oProp2 = NULL ENDPROC ENDDEFINE DEFINE CLASS MyCls2 As Form Name = "MyCls2" oProp1 = NULL PROCEDURE Destroy ACTIVATE SCREEN ? "Destroy: " + this.Name this.oProp1 = NULL ENDPROC ENDDEFINE PROCEDURE cleanup ACTIVATE SCREEN ? "Call Cleanup" IF Type('goObj1') = 'O' AND !IsNull(goObj1) #IF USE_RELEASE goObj1.Release() #ENDIF RELEASE goObj1 ENDIF IF Type('goObj2') = 'O' AND !IsNull(goObj2) #IF USE_RELEASE goObj2.Release() #ENDIF RELEASE goObj2 ENDIF */////////////////////////////// the end of any.prg
Обратите внимание, что явный вызов метода Release() у форм (при USE_RELEASE .T.), снимает проблему. В том смысле, что вынуждает выполниться событию Destroy() экземпляра объекта.
Замечено также, что если вы храните ссылки в массиве, то для корректного удаления соответствующих объектов необходимо явное присвоение NULL соответствующему элементу массива, т.е.
gaMyObjects[i] = NULL
А в свете сказанного выше относительно метода Release() у классов производных от Form, луше так:
LOCAL luTemp ... luTemp = gaMyObjects[i] IF TYPE('luTemp') = 'O' AND !ISNULL(luTemp) IF INLIST(luTemp.BaseClass, "Form", "FormSet", "ToolBar") *-- Insert any special-destroy actions here... luTemp.Release() ENDIF ENDIF STORE NULL TO ; luTemp, gaMyObjects[i] ...Однако, явный вызов метода Release() формы может и не привести к её разрушению. Это может случиться например из-за того, что ссылка на один из объектов, принадлежащий разрушаемой форме, где-нибудь используется во вне её.
*//////////////////////////////////// save as any.prg *// Demo example problems *// with cross-refs in forms *// VFP ver: 8.0(SP1VFP8), 7.0(SP1VFP7), 6.0(SP5VS6) *//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *// Author: Michael Drozdov *// My Page: http://vfpdev.narod.ru/ *// Date: 11/12/2003 *//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * *-- If USE_CLEARPROPS is true (& USE_REMOVECHILDOBJS .F.) *-- works correctly, only in case clear all external references *-- by explicit use command: RELEASE goMyAnyFrm or method: goMyAnyFrm.Release() #DEFINE USE_CLEARPROPS .F. * *-- If USE_REMOVECHILDOBJS is true *-- all works correctly, ...and there are problems otherwise #DEFINE USE_REMOVECHILDOBJS .F. * *-- For explicit use command: RELEASE goMyAnyFrm #DEFINE USE_CLEANUP .F. * *-- (if USE_CLEANUP .T.) For explicit use method: goMyAnyFrm.Release() #DEFINE USE_RELEASE .F. PUBLIC goFrm1 ACTIVATE SCREEN CLEAR *-- Create Form1 goFrm1 = NEWOBJECT('Frm1') IF VARTYPE(goFrm1) # 'O' DO cleanup RETURN .F. ENDIF goFrm1.Show() *-- Create Form2 PUBLIC goFrm2 goFrm2 = NEWOBJECT('Frm2') IF VARTYPE(goFrm2) # 'O' DO cleanup RETURN .F. ENDIF goFrm2.Show() *-- Init cross references *-- between child objects of the forms goFrm1.oCmdExit2 = goFrm2.cmdExit goFrm2.oCmdExit1 = goFrm1.cmdExit #IF USE_CLEANUP DO cleanup #ENDIF DEFINE CLASS Frm1 As Form Caption = "Form1" oCmdExit2 = NULL Left = 120 ADD OBJECT cmdExit As CommandButton WITH ; Caption = "Cancel",; Top = 5 PROCEDURE cmdExit.Click() ThisForm.Release() ENDPROC PROCEDURE Destroy() ACTIVATE SCREEN ?"Destroy: "+ThisForm.Caption #IF USE_CLEARPROPS *-- Release property to external ref this.oCmdExit2 = NULL #ENDIF #IF USE_REMOVECHILDOBJS *-- Release object, the reference on which *-- can be used by external objects this.RemoveObject('cmdExit') #ENDIF ENDPROC ENDDEFINE DEFINE CLASS Frm2 As Form Caption = "Form2" AutoCenter = .T. oCmdExit1 = NULL ADD OBJECT cmdExit As CommandButton WITH ; Caption = "Cancel",; Top = 5 PROCEDURE cmdExit.Click() ThisForm.Release() ENDPROC PROCEDURE Destroy() ACTIVATE SCREEN ?"Destroy: "+ThisForm.Caption #IF USE_CLEARPROPS *-- Release property to external ref this.oCmdExit1 = NULL #ENDIF #IF USE_REMOVECHILDOBJS *-- Release object, the reference on which *-- can be used by external objects this.RemoveObject('cmdExit') #ENDIF ENDPROC ENDDEFINE PROCEDURE cleanup ACTIVATE SCREEN ?"Call Cleanup" IF Type('goFrm1') = 'O' AND !IsNull(goFrm1) #IF USE_RELEASE goFrm1.Release() #ENDIF RELEASE goFrm1 ENDIF IF Type('goFrm2') = 'O' AND !IsNull(goFrm2) #IF USE_RELEASE goFrm2.Release() #ENDIF RELEASE goFrm2 ENDIF */////////////////////////////// the end of any.prgСамый радикальный способ заключается в использовании явных удалений встроенных объектов с помощью метода RemoveObject() класса-контейнера (USE_REMOVECHILDOBJS .T.). Однако, к нему не следует прибегать без крайней необходимости.