RAD Studio 10.4 for delphi XE Assigned和Nil的联系与区别
一、原理
系统级别的函数System.pas : System.Assigned
function Assigned(var P): Boolean;
1、用于测试值为nil(未赋值的unassigned)的指针Pointer或方法变量procedural variable。
2、使用Assigned来确定指针Pointer或被Assigned(var P)中的方法变量function:P或procedure(var P)引用的方法是否为空。P必须是变量类型(var)的指针或方法类型(function或procedure)的引用。
3、Assigned(P)相当于测试var P:Pointer指针变量P<>nil,@P<>nil相当于测试function:P或procedure(var P)的P<>nil。
4、如果P=Nil,Assigned返回False,否则返回True。
5、特别注意:
5.1、当测试对象事件object events不能测试其是否nil,此时使用Assigned是正确的方法。
5.2、关于方法指针与过程指针:(1)、无论方法指针还是过程指针,均能有效测试@P=nil或@P<>nil;(2)、方法指针既能有效测试Assigned(@P)或not Assigned(@P),也能有效测试@P=nil或@P<>nil;(3)、过程指针不能有效测试Assigned(@P)或not Assigned(@P),但能有效测试@P=nil或@P<>nil。
二、使用方法案例
1、对象事件object event、方法指针与过程指针
1.1、对象事件object events
var
LNotifyEvent: TNotifyEvent; //:事件对象
begin
if LNotifyEvent <> nil then {这种写法是错误的:会产生编译错误 }
Writeln('这种写法是错误的:会产生编译错误');
if Assigned(LNotifyEvent) then {这种写法是才是正确的 }
Writeln('这种写法是才是正确的');
end;
对象的通知事件的赋值:
type
TNotifyEvent = procedure(Sender: TObject) of object;
TMainForm = class(TForm)
procedure ButtonClick(Sender: TObject);
//...
end;
var
MainForm: TMainForm;
OnClick: TNotifyEvent;
OnClick := MainForm.ButtonClick; //:通知事件的赋值
1.2、方法指针与过程指针
概念:
type
TProcedureMyProc = procedure; //:过程类型
TMethodMyMethod = procedure of object; //:方法类型
var
FmyProc:TProcedureMyProc; //:过程指针变量
FmyMethod:TMethodMyMethod; //:方法指针变量
var
FmyFunc_Pointer:Pointer; //:普通指针变量
关于方法指针与过程指针:(1)、无论方法指针还是过程指针,均能有效测试@P=nil或@P<>nil;(2)、方法指针既能有效测试Assigned(@P)或not Assigned(@P),也能有效测试@P=nil或@P<>nil;(3)、过程指针不能有效测试Assigned(@P)或not Assigned(@P),但能有效测试@P=nil或@P<>nil。官方解释原文:
http://docwiki.embarcadero.com/RADStudio/Rio/en/Procedural_Types_(Delphi)#Method_Pointers
试验后确实如此:
type
TProcedureMyProc = procedure; //:过程类型
TMethodMyMethod = procedure of object; //:方法类型
var
FmyProc:TProcedureMyProc; //:过程指针变量
FmyMethod:TMethodMyMethod; //:方法指针变量
var
FmyFunc_Pointer:Pointer; //:普通指针变量
function myFunc(var APointer:Pointer):Pointer; //:函数:可供方法指针或过程指针调用,当然也可供方法或过程调用
//var LAPointer:^Integer;
begin //LAPointer:=Pointer(1);
if APointer=nil then Result:=nil
else Result:=APointer;
end;
procedure myProc; //:过程
var LRef:Integer;
begin
LRef:=1;
@FmyProc:=@myProc; //:@myProc等价于:Pointer(myProc);
FmyProc:=myProc;
FmyFunc_Pointer:=myFunc(@FmyProc); //FmyProc:=nil;
end;
function myMethod :string; //:函数:可供方法指针或过程指针调用,当然也可供方法或过程调用
var LRef:Integer;
LStr:string;
begin
LRef:=1; LStr:='AaBb方法指针。,.,';
Result:=LStr;
end;
procedure TForm2.FormShow(Sender: TObject);
var LmyProcPointerValue:Integer;
begin
Memo_Test.Lines.Add('一、方法指针:');
Memo_Test.Lines.Add(' 比如1、:方法:procedure myProc;');
if Assigned(@myProc) then
Memo_Test.Lines.Add(' 返回Assigned(@myProc)=true,方法指针能识别Assigned(@myProc),内存中的内容: '+PChar(@myProc) );
if @myProc<>nil then
Memo_Test.Lines.Add(' 返回(@myProc<>nil)=true,方法指针能识别@myProc<>nil,内存中的内容: '+PChar(@myProc) );
@FmyMethod:=Pointer(myMethod);
if ( (@myMethod<>nil) and (@FmyMethod<>nil) )
and ( Assigned(@myMethod) and Assigned(@FmyMethod) ) then
Memo_Test.Lines.Add(' 比如2、:方法类型:type TMethodMyMethod = procedure of object;'+sLineBreak
+' 方法指针变量:var FmyMethod:TMethodMyMethod;'+sLineBreak
+' 赋值方法指针:@FmyMethod:=Pointer(myMethod);'+sLineBreak
+' 方法指针变量:var FmyMethod:TMethodMyMethod;'+sLineBreak
+' 返回方法指针所指的字符串的数值:'+PChar(@FmyMethod)
) else Memo_Test.Lines.Add(' 不返回任何结果');
Memo_Test.Lines.Add('二、过程指针,比如:');
Memo_Test.Lines.Add(
' TProcedureMyProc = procedure;'+sLineBreak
+' var FmyProc:TProcedureMyProc;'+sLineBreak
+' Procedure pointer types are always incompatible with method pointer types.'+sLineBreak
+' The value nil can be assigned to any procedural type:'+sLineBreak
+' 过程指针类型始终与方法指针类型不兼容。值nil可以分配给任何过程类型。比如: '+sLineBreak
+' if Assigned(FmyProc) then //: 过程不能识别Assigned(FmyProc)'+sLineBreak
+' if Assigned(@FmyProc) then //: 过程指针也不能识别Assigned(@FmyProc)'+sLineBreak
+' if FmyProc=nil then //: 过程不能识别FmyProc=nil且编译器会报错'+sLineBreak
+' if @FmyProc=nil then //: 但过程指针能识别@FmyProc=nil'+sLineBreak
);
if @FmyProc=nil then //: 但过程指针能识别@FmyProc=nil
begin
Memo_Test.Lines.Add(' @FmyProc=nil');
myProc;
end else Memo_Test.Lines.Add(' 过程FmyProc及过程指针@FmyProc均不能识别Assigned');
if FmyFunc_Pointer<>nil then //: 过程指针变量也能识别@FmyProc=nil
Memo_Test.Lines.Add(' FmyFunc_Pointer<>nil,过程指针变量的内存值: '+PChar(@FmyFunc_Pointer) );
if (@myProc<>nil) and (@FmyFunc_Pointer<>nil) then
begin
Memo_Test.Lines.Add(' @myProc<>nil,过程指针内存值: '+PChar(@myProc) );
Memo_Test.Lines.Add(' @myProc<>nil,过程指针变量内存值: '+PChar(@FmyFunc_Pointer) );
end;
Memo_Test.Lines.Add('三、结论:');
Memo_Test.Lines.Add(
' 1、无论方法指针还是过程指针,均能有效测试@P=nil或@P<>nil'+sLineBreak
+' 2、方法指针既能有效测试Assigned(@P)或not Assigned(@P),也能有效测试@P=nil或@P<>nil'+sLineBreak
+' 3、过程指针不能有效测试Assigned(@P)或not Assigned(@P),但能有效测试@P=nil或@P<>nil'
);
end;
2、方法或过程的指针变量的回调procedural variable : function myFuc(var APointer: Pointer):Pointer;
function myFuc(var APointer: Pointer):Pointer;
begin
if APointer=nil then Result:=nil
else Result:=APointer;
end;
//:procedure AProc(var AItem: Pointer);类似
var LPointer: Pointer; //:声明:普通的指针变量
begin
LPointer:=nil;
if myFuc(LPointer)=nil then
Label_1.Text:=('myFuc(LPointer)=nil'); //:返回'myFuc(LPointer)=nil'
//赋值普通的指针变量:注意:
GetMem(LPointer, 1024); //LPointer有效且赋值
FreeMem(LPointer, 1024); //LPointer不再有效,但其仍然<>nil
if myFuc(LPointer)=nil then
Label_2.Text:=('myFuc(LPointer)=nil')
else Label_2.Text:=('myFuc(LPointer)<>nil'); //:返回'myFuc(LPointer)<>nil'
LPointer:=nil;
end;
3、普通类型的变量
不能针对普通类型的变量使用Assigned或nil,比如:
var LVar1 :String; LVar2 :Double;
begin
if Assigned(LVar1 ) then Writeln('这种写法是错误的:会产生编译错误');
if Assigned(LVar2 ) then Writeln('这种写法是错误的:会产生编译错误');
if (LVar1 =nil) then Writeln('这种写法是错误的:会产生编译错误');
if (LVar2 =nil) then Writeln('这种写法是错误的:会产生编译错误');
end;
4、普通的指针变量
var LPointer: Pointer; //:声明:普通的指针变量
begin
if (LPointer = nil) then
Label1.Text:=('这种写法是正确的:因为LPointer已被声明为普通的指针变量;但if返回false:因为虽然LPointer还没有赋值,但LPointer已经申明了指针变量var LPointer: Pointer;');
if (LPointer <> nil) then
Label2.Text:=('这种写法是正确的:因为LPointer已被声明为普通的指针变量;且if返回true:因为虽然LPointer还没有赋值,但LPointer已经申明了指针变量var LPointer: Pointer;');
if Assigned( LPointer ) then
Label3.Text:=('这种写法是正确的:因为LPointer已被声明为普通的指针变量,且返回true:因为LPointer已经申明了指针变量var LPointer: Pointer;');
if not Assigned( LPointer ) then
Label4.Text:=('这种写法是正确的:因为LPointer已被声明为普通的指针变量,但返回false:因为LPointer已经申明了指针变量var LPointer: Pointer;');
//赋值普通的指针变量:注意:
LPointer:=nil;
//:普通指针变量赋值:=nil相当于对象TObject.DisposeOf从内存中FreeAndNil,其声明也抹掉啦
//:但不能FreeAndNil(LPointer);运行时因为ARC自动引用计数会报内存地址错误,只能再次赋值LPointer:=nil;
if (LPointer = nil) then
Label5.Text:=('这种写法是正确的:因为LPointer为普通的指针变量,且已经被赋值为nil');
if Assigned( LPointer ) then
Label6.Text:=('这种写法是正确的:因为LPointer为普通的指针变量,但返回false:普通指针变量:=nil相当于对象TObject.DisposeOf从内存中FreeAndNil,其声明也抹掉啦');
//赋值普通的指针变量:注意:
GetMem(LPointer, 1024); //LPointer有效且赋值
FreeMem(LPointer, 1024); //LPointer不再有效,但其仍然<>nil
if Assigned( LPointer ) then
Label7.Text:=('这种写法是正确的:因为LPointer已被声明为普通的指针变量,且返回true:因为LPointer已经申明了指针变量var LPointer: Pointer;且已经被赋值,内存中既有指针起始位置又有内存空间GetMem(LPointer, 1024);');
if ( LPointer <>nil ) then
Label8.Text:=('这种写法是正确的:因为LPointer已被声明为普通的指针变量,且返回true:因为LPointer已经申明了指针变量var LPointer: Pointer;且已经被赋值,内存中既有指针起始位置又有内存空间GetMem(LPointer, 1024);');
LPointer:=nil;//:FreeAndNil(LPointer);是错误的,只能再次赋值LPointer:=nil;
end;
5、记录(结构体)化的指针
案例System.TypInfo.pas
PPropInfo = ^TPropInfo; //:记录(结构体)化的指针变量
TPropInfo = packed record //:记录(结构体)
PropType: PPTypeInfo;
GetProc: Pointer; //:普通指针变量
SetProc: Pointer; //:普通指针变量
StoredProc: Pointer; //:普通指针变量
Index: Integer;
Default: Integer;
NameIndex: SmallInt;
Name: TSymbolName;
function NameFld: TTypeInfoFieldAccessor; inline;
function Tail: PPropInfo; inline; //:返回记录(结构体)化的指针变量的函数
end;
procedure FreeAndNilProperties(AObject: TObject);
var //:变量定义
I, Count: Integer;
PropInfo: PPropInfo; //:记录(结构体)化的指针变量
TempList: PPropList;
LObject: TObject; //:对象类型的变量
begin
Count := GetPropList(AObject, TempList);
if Count > 0 then
try
for I := 0 to Count - 1 do
begin
PropInfo := TempList^[I];
if (PropInfo^.PropType^.Kind = tkClass)
and Assigned(PropInfo^.GetProc)
//:正确的Assigned写法:因为PropInfo^.GetProc返回GetProc:Pointer;普通指针变量
and Assigned(PropInfo^.SetProc)
//:正确的Assigned写法:因为PropInfo^.SetProc返回SetProc:Pointer;普通指针变量
then
begin
LObject := GetObjectProp(AObject, PropInfo);
if LObject <> nil then
//:正确的nil写法:因为LObject:TObject;是对象类型的变量而非普通变量
begin
SetObjectProp(AObject, PropInfo, nil);
LObject.Free;
end;
end;
end;
finally
FreeMem(TempList);
end;
end;
三、官方英文参考:
http://docwiki.embarcadero.com/Libraries/Rio/en/System.Assigned
http://docwiki.embarcadero.com/CodeExamples/Rio/en/SystemAssigned_(Delphi)
http://docwiki.embarcadero.com/RADStudio/Rio/en/Delphi_Intrinsic_Routines
四、关于窗体组件对象测试Assigned和Nil、更安全的Create和更安全的DisposeOf释放:
官方英文参考:
http://docwiki.embarcadero.com/RADStudio/Rio/en/Procedural_Types_(Delphi)#Method_Pointers
http://docwiki.embarcadero.com/RADStudio/Rio/en/Program_Control_(Delphi)
4.1、窗体的对象继承关系
TCommonCustomForm = class(TFmxObject) //FMX.Forms.pas
TFmxObject = class(TComponent) //FMX.Types.pas
TComponent = class(TPersistent) //System.Classes.pas
TPersistent = class(TObject) //System.pas
TObject = class //System.pas
4.2、窗体对象将其父类的继承解构BeforeDestruction
procedure TCommonCustomForm.BeforeDestruction;
procedure SaveStateNotifyCheck;
var
SaveStateService: IFMXSaveStateService;
begin
if not TPlatformServices.Current.SupportsPlatformService(IFMXSaveStateService, SaveStateService) or
not SaveStateService.Notifications then
SaveStateHandler(Self, nil);
end;
var
I: Integer;
begin
SaveStateNotifyCheck;
if Assigned(FOnDestroy) then
FOnDestroy(Self);
for I := 0 to ChildrenCount - 1 do
if Children[I] = Owner then
begin
Children[I].Parent := nil;
Break;
end;
inherited;
end;
TCommonCustomForm及其父类解构前,均对其子组件及其自身做:=nil处理,同时对其状态通知、状态保存句柄做:=nil做:=nil处理、以及对“观察者”订阅与发布事件及其监听等等做:=nil处理。
//TCommonCustomForm的父类TFmxObject:
procedure TFmxObject.BeforeDestruction;
var
I: Integer;
L2: TList<Pointer>;
begin
{ NotifyList }
if FNotifyList <> nil then
begin
L2 := TList<Pointer>.Create;
try
L2.AddRange(FNotifyList);
for I := L2.Count - 1 downto 0 do
if FNotifyList.Contains(L2[I]) then
IFreeNotification(L2[I]).FreeNotification(Self);
{ IFreeNotification = interface
['{FEB50EAF-A3B9-4b37-8EDB-1EF9EE2F22D4}']
procedure FreeNotification(AObject: TObject);
end; } //释放通知事件做:=nil处理
finally
L2.Free;
FreeAndNil(FNotifyList); //释放通知事件列表FreeAndNil做:=nil处理
end;
end;
inherited;
end;
TCommonCustomForm->TFmxObject的父类TComponent:
procedure TComponent.BeforeDestruction;
begin
if not (csDestroying in ComponentState) then
Destroying;
end;
procedure TComponent.Destroying;
var
I: Integer;
begin
if not (csDestroying in FComponentState) then
begin
Include(FComponentState, csDestroying);
if FComponents <> nil then //:测试组件做=nil处理
for I := 0 to FComponents.Count - 1 do
TComponent(FComponents[I]).Destroying; //:测试子组件做=nil处理
end;
end;
procedure TComponent.DestroyComponents;
var
Instance: TComponent;
begin
FreeAndNil(FSortedComponents);
while FComponents <> nil do //:正式移除组件:测试组件做=nil处理
begin
Instance := FComponents.Last;
if (csFreeNotification in Instance.FComponentState)
or (FComponentState * [csDesigning, csInline] = [csDesigning, csInline]) then
RemoveComponent(Instance) //:正式移除组件:测试组件做=nil处理
else
Remove(Instance); //:正式移除组件:测试组件做=nil处理
Instance.DisposeOf; //:组件实例做=nil处理
end;
end;
procedure TComponent.Remove(AComponent: TComponent);
var
Count: Integer;
begin
AComponent.FOwner := nil; //:正式移除组件:测试组件做=nil处理
Count := FComponents.Count;
if Count > 0 then
begin
{ On destruction usually the last item is deleted first }
if FComponents[Count - 1] = AComponent then
FComponents.Delete(Count - 1)
else
FComponents.Remove(AComponent);
if FSortedComponents <> nil then
FSortedComponents.Remove(AComponent);
end;
if FComponents.Count = 0 then
begin
FreeAndNil(FSortedComponents); //:正式移除组件:测试组件做=nil处理
FreeAndNil(FComponents); //:正式移除组件:测试组件做=nil处理
end;
end;
//System.SysUtils.pas //System.SysUtils.FreeAndNil
procedure FreeAndNil(var Obj); //:无论是否使用引用计数均做=nil处理
{$IF not Defined(AUTOREFCOUNT)}
var
Temp: TObject;
begin
Temp := TObject(Obj);
Pointer(Obj) := nil; //做=nil处理
Temp.Free;
end;
{$ELSE}
begin
TObject(Obj) := nil; //做=nil处理
end;
{$ENDIF}
//System.pas
procedure TObject.DisposeOf;
type
TDestructorProc = procedure (Instance: Pointer; OuterMost: ShortInt);
begin
{$IFDEF AUTOREFCOUNT}
if Self <> nil then
begin
Self.__ObjAddRef; // Ensure the instance remains alive throughout the disposal process
try
if __SetDisposed(Self) then
begin
_BeforeDestruction(Self, 1);
TDestructorProc(PPointer(PByte(PPointer(Self)^) + vmtDestroy)^)(Self, 0);
end;
finally
Self.__ObjRelease; // This will deallocate the instance if the above process cleared all other references.
end;
end;
{$ELSE}
Free;
{$ENDIF}
end;
4.3、安全的条件测试Create和Close窗体
function TfmxTestGYListview1.AppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;
var LAppEventStatus :string;
begin
Result := true;
//(*
case AAppEvent of
TApplicationEvent.FinishedLaunching:
begin
//Memo1.Lines.Add('应用完成系统载入:');
end;
TApplicationEvent.WillTerminate:
begin
//Memo1.Lines.Add('应用将要中断');
{$IFDEF MSWINDOWS} //Windows下防止用户强行中断正在Rest响应数据的应用,否则内存泄漏:
if IFutureCtL00001<>nil then
if IFutureCtL00001.Status<>TTaskStatus.Completed then IFutureCtL00001.Wait(500);
if FITask<>nil then
if FITask.Status<>TTaskStatus.Completed then FITask.Wait( 2000 );
{$ENDIF}
end;
TApplicationEvent.EnteredBackground:
begin
//:也许10.4 for Android10越来越严格约束:
//:按了返回键若程序不控制则立即进入后台:
//:而进入后台:主程序就被:=nil掉了
//:(nil就相当于对象 TObject.disposeOf = FreeAndNil(TObject) )
//:FreeAndNil是10.4与10.3.3的一个变化
TimerGlobal.Enabled := false;
TimerGuangGao.Enabled := false;
///////////已进入后台千千万不与UI有任何交互,否则异常:////////////////////////////////////////////////////////
//Memo1.Lines.Add('应用已进入后台');
//Text_0201.Text:=TInterlocked(FRestSearchFrequency).ToString;
end;
TApplicationEvent.WillBecomeInactive:
begin
TimerGlobal.Enabled := false;
TimerGuangGao.Enabled := false;
{Memo1.Lines.Add(
'App切入后台再重新调入过程:'+sLineBreak
+'1、应用将变为非活动状态:'+sLineBreak
+string('').PadLeft(8,#32)+'此时就需提前关闭任何'+sLineBreak
+string('').PadLeft(8,#32)+'与UI相关的线程与计时,'+sLineBreak
+string('').PadLeft(8,#32)+'否则手机重新切到前台'+sLineBreak
+string('').PadLeft(8,#32)+'会内部异常!');
Memo1.Lines.Add(
'*注意:已进入后台事件中:'
+string('').PadLeft(8,#32)+'千万不能有与UI相关的'+sLineBreak
+string('').PadLeft(8,#32)+'任何交互、线程与计时'+sLineBreak
+string('').PadLeft(8,#32)+'否则手机重新切到前台'+sLineBreak
+string('').PadLeft(8,#32)+'亦会内部异常!'+sLineBreak
);}
end;
TApplicationEvent.WillBecomeForeground:
begin
TimerGlobal.Enabled := true;
if (FDMemTable1.Active=false) then LAppEventStatus:='但内存表不活动'
else LAppEventStatus:='内存表活动';
//Memo1.Lines.Add('2、应用将进入前台:'+LAppEventStatus+',单号'+FGetBillNo_Currt.Trim);
TimerGuangGao.Enabled := true;
end;
TApplicationEvent.BecameActive:
begin
TimerGlobal.Enabled := true;
if (FDMemTable1.Active=false) then LAppEventStatus:='但内存表不活动'
else LAppEventStatus:='内存表活动';
//Memo1.Lines.Add('3、应用已变为活动状态:'+LAppEventStatus+',单号'+FGetBillNo_Currt.Trim);
TimerGuangGao.Enabled := true;
//TranslucentStatusAndNavBar(self, Rectangle_02);
// 将状态栏与导航栏透明显示,顺便做一些每个窗口必须要做的处理
//Rectangle_02.Height := Rectangle_02.Height - 25; // 因为上面多加了25
end;
TApplicationEvent.TimeChange:
begin
//Memo1.Lines.Add('应用计时改变:');
end;
TApplicationEvent.LowMemory:
begin
//Memo1.Lines.Add('系统报应用高内存耗用:');
end;
TApplicationEvent.OpenURL:
begin
//Memo1.Lines.Add('App打开一个URL:');
end;
end; //*)
end;
所以,更科学和安全的写法:
if DataModuleSqlLiteCommon=nil then DataModuleSqlLiteCommon
:=TDataModuleSqlLiteCommon.Create(Application);
//:if DataModuleSqlLiteCommon=nil比if not Assigned(DataModuleSqlLiteCommon)更安全
//另外:
//:最好由于Application来管理一切窗体://:=TDataModuleSqlLiteCommon.Create(AFormOrFrame);
//:DataModuleSqlLiteCommon的Owner:AFormOrFrame万一已在某个事件被:=nil;掉了
喜欢的话,就在下面点个赞、收藏就好了,方便看下次的分享: