Memory on mobile devices is certainly not an unlimited commodity. Because of this, memory usage in your application can be much more important than on desktop applications. At times, you might find the need to use a memory profiler, or improve your code to use memory more efficiently.
The following are the most common memory pitfalls:
Let's take a look at the first problem where the GC cannot keep up. Let's say, we have a Xamarin.iOS application with a button for sharing an image on Twitter as follows:
twitterShare.TouchUpInside += (sender, e) => { var image = UImage.FromFile("YourLargeImage.png"); //Share to Twitter };
Now let's assume the image is a 10 MB image from the user's camera roll. If the user clicks on the button and cancels the Twitter post rapidly, there could be the possibility of your application running out of memory. iOS will commonly force close apps using too much memory, and you don't want users to experience this with your app.
The best solution is to call Dispose
on the image when you are finished with it as follows:
var image = UImage.FromFile("YourLargeImage.png"); //Share to Twitter image.Dispose();
An even better approach would be to take advantage of the C# using
statement as follows:
using(var image = UImage.FromFile("YourLargeImage.png")) { //Share to Twitter }
The C# using
statement will automatically call Dispose
in a try-finally
block, so the object will get disposed even if an exception is thrown. I recommend taking advantage of the using
statement for any IDisposable
class where possible. It is not always necessary for small objects such as NSString
, but is always a good idea for larger, more heavyweight UIKit
objects.
Memory leaks are the next potential issues. C# being a managed, garbage-collected language prevents a lot of memory leaks, but not all of them. The most common leaks in C# are caused by events.
Let's assume we have a static class with an event as follows:
static class MyStatic { public static event EventHandler MyEvent; }
Now, let's say we need to subscribe to the event from an iOS controller as follows:
public override void ViewDidLoad() { base.ViewDidLoad(); MyStatic.MyEvent += (sender, e) => { //Do something }; }
The problem here is that the static class will hold a reference to the controller until the event is unsubscribed. This is a situation that a lot of developers might miss. To fix this issue on iOS, I would subscribe to the event in ViewWillAppear
and unsubscribe ViewWillDisappear
. On Android, use OnStart
and OnStop
, or OnPause
and OnResume
.
You would correctly implement this event as follows:
public override void ViewWillAppear() { base.ViewWillAppear(); MyStatic.MyEvent += OnMyEvent; } public override void ViewWillDisappear() { base.ViewWillDisappear (); MyStatic.MyEvent += OnMyEvent; }
However, an event is not a surefire cause of a memory leak. Subscribing to the TouchUpInside
event on a button inside the ViewDidLoad
method, for example, is just fine. Since the button lives in memory just as long as the controller, everything can get garbage collected without problem.
For the final issue, the garbage collector can sometimes remove a C# object; later, an Objective-C object attempts to access it.
The following is an example of adding a button to UITableViewCell
:
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { var cell = tableView.DequeueReusableCell("MyCell"); //Remaining cell setup here var button = UIButton.FromType(UIButtonType.InfoDark); button.TouchUpInside += (sender, e) => { //Do something }; cell.AccessoryView = button; return cell; }
We add the built-in info button as an accessory view to the cell. The problem here is that the button will get garbage collected, but its Objective-C counterpart will remain in use as it is displayed on the screen. If you click on the button after a period of time, you will get a crash that looks something like the following:
mono-rt: Stacktrace: mono-rt: at <unknown> mono-rt: at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication.UIApplicationMain (int,string[],intptr,intptr) mono-rt: at MonoTouch.UIKit.UIApplication.Main (string[],string,string) ... Continued ... ================================================================= Got a SIGSEGV while executing native code. This usually indicates a fatal error in the mono runtime or one of the native libraries used by your application. ================================================================
It is not the most descriptive error message, but in general, you know that something went wrong in the native Objective-C code. To resolve the issue, create a custom subclass of UITableViewCell
and create a dedicated member variable for the button as follows:
public class MyCell : UITableViewCell { UIButton button; public MyCell() { button = UIButton.FromType(UIButtonType.InfoDark); button.TouchUpInside += (sender, e) => { //Do something }; AccessoryView = button; } }
Now, your GetCell
implementation will look something like the following:
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) { var cell = tableView.DequeueReusableCell("MyCell") as MyCell; //Remaining cell setup here return cell; }
Since the button is not a local variable, it will no longer get garbage collected very soon. A crash is avoided, and in some ways this code is a bit cleaner. Similar situations can happen on Android with the interaction between C# and Java; however, it is less likely since both are garbage-collected languages.
18.220.178.207