一、原理
系统级别的函数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)
http://docwiki.embarcadero.com/RADStudio/Rio/en/Pointers_and_Pointer_Types_(Delphi)#Overview_of_pointers
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.pas4.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;掉了本博客关联: 1、《再谈ARC(Automatic Reference Counting)自动引用计数器与delphi》 https://blog.csdn.net/pulledup/article/details/108333083 2、《ARC自动引用计数与方法function过程procedure的传参的对象类型及其回调值》 https://blog.csdn.net/pulledup/article/details/102646822
喜欢的话,就在下面点个赞、收藏就好了,方便看下次的分享: