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.
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?
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!
Oh great!
Thanks!
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.
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”.
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;
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