Home/Patches, Uncategorized/Plugging a couple of leaks in Firemonkey in Delphi XE5

Plugging a couple of leaks in Firemonkey in Delphi XE5

Views:
46

NOTE: If you have read this post before, it has been updated recently to include further changes that remove all leaks generated by the demo project.

Recently there was a posting in the Embarcadero forums about problems with creating a secondary form, showing it modally, then destroying it; namely that the app exits without warning after showing the form a number of times. There were also no crash logs for the app. This is a classic symptom that the app suffers from a memory leak.

I created a test application, attached Instruments to the app, and sure enough, the app was leaking memory. I had also hooked in a handler for the application events so I could see if the app is receiving any low memory warnings. Please refer to the attached project that demos the problem, and also serves as an example of how to hook into application events.

Instruments is a companion application that comes with Xcode that helps debug your iOS and OS X apps. To run the application, click on the Xcode menu item, click Open Developer Tool, then click Instruments:

When Instruments starts, it prompts for a template to use. For checking Leaks in your app, select the Leaks template:

and click the Choose button. Click the Choose Target dropdown, and make sure your device is checked. To select your app, click Choose Target again. If your app is already running, you can click Attach To Process and select your app from the list of running apps:

otherwise select your app from the Choose Target submenu. In this case, your app obviously need to have been deployed to your device.

Select the Leaks category from the list of Instruments on the left, and click the red record button at the top left. I tend to uncheck Automatic Snapshotting, so that I can examine leaks on an ad-hoc basis. To turn it off, simply uncheck the checkbox:

If you have it turned off, and want to check a status of any leaks, click the Snapshot Now button. The following image is the leak status of the test project, after I’ve clicked Button 1 to open the modal window, then clicked Button 2 to close it, twice:

As can be seen, there’s a bunch of leaks, and given the count of each leak, they occur each time the form is “destroyed”. Thanks to a post by Cristian Peta in the thread mentioned earlier, to plug the TSettingsFont leak involves making a copy of the FMX.Graphics unit, placing it somewhere in the compiler search path, and making the following modification:

type
  TSettingsFont = class (TFont)
  private
    [Weak] FTextSettings: TTextSettings;  // Add the [Weak] directive on this line
  protected
    procedure DoChanged; override;

I’ve also been alerted by William Brookfield to a workaround in this QC report. Thanks also to Babak Yaghoobi for his help regarding this leak. This time make a copy of the FMX.Platform.iOS unit (or modify a copy you might have made from another patch), and make the following mods. Firstly in TPlatformCocoaTouch.ShowWindowModal:

  finally
    BackView.removeFromSuperview;
    BackView.release; // Add this line
  end;
  Result := AForm.ModalResult;
end;

..and in TPlatformCocoaTouch.DestroyWindow:

procedure TPlatformCocoaTouch.DestroyWindow(const AForm: TCommonCustomForm);
begin
  if Assigned(AForm.Handle) then
  begin
    WindowHandleToPlatform(AForm.Handle).View.removeFromSuperview;
    WindowHandleToPlatform(AForm.Handle).View.release;  // Add this line
  end;
end;

Rebuilding the app, attaching Instruments to it and recording as before results in no more leaks! I hope this has been of use, either to fix leaks in your app or just as a guide to using Instruments.

 

By | 2017-02-16T18:02:36+00:00 November 4, 2013 1:33 pm|Patches, Uncategorized|7 Comments

About the Author:

7 Comments

  1. Makoto December 20, 2013 at 2:39 pm - Reply

    Hello.
    I use Delphi XE5 Update2 in japan.
    Your knowledge is very great.

    Font leak has been resolved.

    but TPlatformCocoaTouch.ShowWindowModal structure is different in update2?

    I have more leak problem.
    ComboBox and ComboEdit is leak.

    Do you know ComboBox leak?

    • admin December 20, 2013 at 2:42 pm - Reply

      Hi Makoto,

      I’m yet to look at the changes in Update 2. I plan to do that before the end of the weekend.

      Thanks!

      • Makoto December 20, 2013 at 3:27 pm - Reply

        Oh great!
        Thanks!

        • admin December 24, 2013 at 8:21 am - Reply

          I can see there is a leak in TComboBox (haven’t looked at TComboEdit yet). I’ll be looking into where the leak is occurring and come back with a solution.

  2. Makoto January 5, 2014 at 7:48 pm - Reply

    Hello.

    i got advice from Embarcadero Technologies Support Team in japan.

    “TForm ShowModal” has leak problem in Xe5 update2.
    use “TForm Show”.
    and “DisposeOf” in owner Form.Onidle event into “TQueue”.

  3. Makoto January 5, 2014 at 7:59 pm - Reply

    Leakage problem of TEdit that I think I can solve.
    this leak are memory for Gesture.

    FMX.Forms.pas
    constructor TCommonCustomForm.Create(AOwner: TComponent);

    InitializeNewForm;
    for I := Low(FGestureRecognizers) to High(FGestureRecognizers) do // add 2014.01.05
    FGestureRecognizers[I] := 0; // add 2014.01.05
    if (ClassType TCommonCustomForm) and not(csDesigning in ComponentState) then

    FStateChangeMessageId := TMessageManager.DefaultManager.SubscribeToMessage(TVKStateChangeMessage, VirtualKeyboardChangeHandler);
    // for I := Low(FGestureRecognizers) to High(FGestureRecognizers) do // remove 2014.01.05
    // FGestureRecognizers[I] := 0; // remove 2014.01.05
    TStyleManager.NeedScenes;
    end;

    destructor TCommonCustomForm.Destroy;
    var // add 2014.01.05
    recognizer: TInteractiveGesture; // add 2014.01.05
    begin
    for recognizer := Low(FGestureRecognizers) to High(FGestureRecognizers) do // add 2014.01.05
    while 0 < FGestureRecognizers[recognizer] do // add 2014.01.05
    RemoveRecognizer(recognizer); // add 2014.01.05
    SetMainMenu(nil);

    FMX.Platform.iOS.pas

    TFMXViewBase = class(TOCLocal, UIKeyInput, UITextInput, UITextInputTraits, UIGestureRecognizerDelegate)

    procedure AddRecognizer(const Gesture: TInteractiveGesture);
    procedure RemoveRecognizer(const Gesture: TInteractiveGesture); // add 2014.01.05
    procedure DoLMouseUp(const X, Y: Single; DoClick: Boolean = True);

    procedure TPlatformCocoaTouch.RemoveRecognizer(const ARec: TInteractiveGesture; const AForm: TCommonCustomForm);
    begin
    if Assigned(AForm) and Assigned(AForm.Handle) then // add 2014.01.05
    TFMXViewBase(WindowHandleToPlatform(AForm.Handle).Handle).RemoveRecognizer(ARec); // add 2014.01.05
    end;

    procedure TFMXViewBase.AddRecognizer(const Gesture: TInteractiveGesture);
    end;

    procedure TFMXViewBase.RemoveRecognizer(const Gesture: TInteractiveGesture); // add 2014.01.05
    var
    recognizers: NSArray;
    i: Integer;
    recognizer: UIGestureRecognizer;
    doRemove: Boolean;
    begin
    recognizers := UIView(Super).gestureRecognizers;
    for i := 0 to recognizers.count – 1 do
    begin
    recognizer := TUIGestureRecognizer.Wrap(recognizers.objectAtIndex(i));
    if recognizer.isKindOfClass(objc_getClass('UIPanGestureRecognizer')) then
    begin
    doRemove := Gesture = TInteractiveGesture.igPan;
    end
    else if recognizer.isKindOfClass(objc_getClass('UIRotationGestureRecognizer')) then
    begin
    doRemove := Gesture = TInteractiveGesture.igRotate;
    end
    else if recognizer.isKindOfClass(objc_getClass('UIPinchGestureRecognizer')) then
    begin
    doRemove := Gesture = TInteractiveGesture.igZoom;
    end
    else if recognizer.isKindOfClass(objc_getClass('UITapGestureRecognizer')) then
    begin
    doRemove := Gesture = TInteractiveGesture.igTwoFingerTap;
    end
    else if recognizer.isKindOfClass(objc_getClass('UILongPressGestureRecognizer')) then
    begin
    doRemove := Gesture = TInteractiveGesture.igLongTap;
    end
    else
    begin
    doRemove := false;
    end;

    if doRemove then
    begin
    UIView(Super).removeGestureRecognizer(recognizer);
    recognizer.release;
    end;
    end;
    end;

    function TFMXViewBase.autocapitalizationType: UITextAutocapitalizationType;

    • Makoto January 21, 2014 at 10:31 pm - Reply

      sorry.
      I detect range over exception.

      procedure TFMXViewBase.RemoveRecognizer(const Gesture: TInteractiveGesture); // add 2014.01.05
      var
      recognizers: NSArray;
      i: Integer;
      recognizer: UIGestureRecognizer;
      doRemove: Boolean;
      begin
      recognizers := UIView(Super).gestureRecognizers; // <- range over exception

Leave a Reply

Show Buttons
Hide Buttons