《Mastering Delphi 6》學習筆記之八 (轉)

worldblog發表於2007-12-09
《Mastering Delphi 6》學習筆記之八 (轉)[@more@]

構件自動清除的內幕

 

我們已經知道,TComponent在釋放自身的時候會自動刪除它Owns的所有Component,這是的一個極大特點。否則的話,任何動態生成的比如Menu Items都要自己維護和刪除,那將是多麼痛苦。VCL是如何做到這一點的呢?可以想象到,每個Component內部應該保持一個列表,記錄它所擁有的Components,在自身的析構中將列表中的所有物件同時刪除即可。基本上這個想法是正確的,不過應該考慮到兩種可能的情況:(1)構件刪除自身的時候,將它所擁有的子構件全部清除,這種情況下子構件是被動的;(2)某個子構件主動地了Free或者Destroy,這個時候,父構件應該有辦法得到通知,並且維持列表的同步。否則的話,就可能出現某個子構件被析構兩次以上的情況,其後果如何大概也可以想到了。:namespace prefix = o ns = "urn:schemas--com::office" />

要保持列表的同步,子構件在建立的時候就必須向父構件報告,退出的時候也必須給父構件打個招呼才行。

原理不復雜,現在來看看VCL是如何實現的。TComponent的構造方法叫做Create,帶有一個AOwner引數:

constructor TComponent.Create(AOwner: TComponent);

begin

  FComponentStyle := [csInheritable];

  if AOwner <> nil then AOwner.InsertComponent(Self);

end;

InsertComponent正是我們關心的地方。找找看它的具體實現:

procedure TComponent.InsertComponent(AComponent: TComponent);

begin

  AComponent.ValidateContainer(Self);

  ValidateRename(AComponent, ', AComponent.FName);

  Insert(AComponent);

  AComponent.SetReference(True);

  if csDesigning in ComponentState then

  AComponent.SetDesigning(True);

  Notification(AComponent, opInsert);

end;

雖然有些地方還不大明白,不過關鍵的部分看來是Insert(AComponent)一句。再找到Insert:

procedure TComponent.Insert(AComponent: TComponent);

begin

  if FComponents = nil then FComponents := TList.Create;

  FComponents.Add(AComponent);

  AComponent.FOwner := Self;

end;

我們所猜想的內部列表終於現身,它是一個通用的,可以容納任何T或者Pointer的TList型別。

另外,從上面的程式碼段也可以看到VCL中比較典型的一種處理方法,那就是物件“只有在需要的時候才建立”,比如上面建立FComponents就是屬於這種情況。當然,在每次建立一個構件時都檢查一遍FComponent的有效性將會稍稍影響程式的執行速度,但是考慮到程式所佔用的(Form和Application一般有應該有Components列表,而一般的構件則完全沒有必要,如果每個構件都建立一個List的話,那麼佔用的記憶體是相當可觀的),這種處理方法也有它的道理。

現在再來看看析構時的情況:

destructor TComponent.Destroy;

begin

  Destroying;

  if FFreeNotifies <> nil then

  begin

  while Assigned(FFreeNotifies) and (FFreeNotifies.Count > 0) do

  TComponent(FFreeNotifies[FFreeNotifies.Count - 1]).Notification(Self, opRemove);

  FreeAndNil(FFreeNotifies);

  end;

  DestroyComponents;

  if FOwner <> nil then FOwner.RemoveComponent(Self);

  inherited Destroy;

end;

這裡有兩個重要的地方:(1)DestroyComponents一句,顯然是刪除其所擁有的Components;(2)FOwner.RemoveComponents(Self),看來是把自身從父構件的FComponents列表中移走。

procedure TComponent.DestroyComponents;

var

  Instance: TComponent;

begin

  while FComponents <> nil do

  begin

  Instance := FComponents.Last;

  if (creeNotification in Instance.FComponentState)

  or (FComponentState * [csDesigning, csInline] = [csDesigning, csInline]) then

  RemoveComponent(Instance)

  else

  Remove(Instance);

  Instance.Destroy;

  end;

end;

 

procedure TComponent.RemoveComponent(AComponent: TComponent);

begin

  ValidateRename(AComponent, AComponent.FName, ');

  Notification(AComponent, opRemove);

  AComponent.SetReference(False);

  Remove(AComponent);

end;

RemoveComponent內部又呼叫了Remove,再看看這個函式:

procedure TComponent.Remove(AComponent: TComponent);

begin

  AComponent.FOwner := nil;

  FComponents.Remove(AComponent);

  if FComponents.Count = 0 then

  begin

  FComponents.Free;

  FComponents := nil;

  end;

end;

 

很簡單,是麼?透過這一番遊歷,相信“構件在何種情況下會被自動清除和如何被清除”這個問題應該有了明確的答案,以後寫起程式碼來也應該放心多了。

 

附記:透過上面的例子,我才體會到Delphi中的導航鍵是多麼方便!不論是自己的單元還是VCL內部單元,用Ctrl+點選和Ctrl+Shift+Up/Down,以及Code Editor中的Back/For按鈕,三兩下就可以定位到任何地方。在Visual C++中就沒有這樣的方便,不論是用Trace還是用 Browser,都必須先編譯透過才行,即使用Find In Files也不見得快多少。相比起來,在Delphi中跟蹤要方便多了。雖說Delphi的啟動速度確實慢了點,不過它在其他方面提供的快捷方便還是應該肯定的。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990616/,如需轉載,請註明出處,否則將追究法律責任。

相關文章