![]() |
![]() |
Проблема перекрёстных ссылок. Как её решить? |
> Имею два объекта, каждый из которых ссылается на другого.
> При их уничтожении они остаются в памяти. Имеется ли способ корректного их
удаления?
> Вот простой пример:
*//////////////////////////////////// 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.). Однако, к нему не следует прибегать без крайней необходимости.