Why does MonoTouch cause lots of memory leaks (as reported by Instruments) even with very simple Apps?
I'm running a very basic test App with Instruments, and it finds a lot of memory leaks! Since I know that Apple guys perform checks for leaked memory when an App is submitted to iTunes, I would like to investigate the problem.
My environment: MonoDevelop 2.4.2 with MonoTouch 3.2.4 on Mac OS X 10.6.6, targeting an iPad running iOS 4.2.1.
My test App simply shows a TableView populated with a list of 50 strings, grouping them by their starting letter.
Steps to reproduce the problem: create a new "iPad Window-based Project" with MonoDevelop, open the MainWindow.xib file with Interface Builder, place a new TableView onto the window and create its outlet (named "tview") to the AppDelegate class. Then enter the following code in Main.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace SimpleTable
{
public class Application
{
static void Main (string[] args)
{
UIApplication.Main (args);
}
}
public partial class AppDelegate : UIApplicationDelegate
{
private List<string> _names;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
_names = new List<string> { "Smith", "Jones", "Williams", "Brown", "Taylor",
"Davies", "Wilson", "Evans", "Thomas", "Johnson",
"Roberts", "Walker", "Wright", "Robinson", "Thompson",
"White", "Hughes", "Edwards", "Green", "Hall",
"Wood", "Harris", "Lewis", "Martin", "Jackson",
开发者_运维技巧 "Clarke", "Clark", "Turner", "Hill", "Scott",
"Cooper", "Morris", "Ward", "Moore", "King",
"Watson", "Baker", "Harrison", "Morgan", "Patel",
"Young", "Allen", "Mitchell", "James", "Anderson",
"Phillips", "Lee", "Bell", "Parker", "Davis" };
tview.Source = new MyTableViewSource(_names);
window.MakeKeyAndVisible ();
return true;
}
private class MyTableViewSource : UITableViewSource
{
private List<string> _sectionTitles;
private SortedDictionary<int, List<string>> _sectionElements = new SortedDictionary<int, List<string>>();
public MyTableViewSource(List<string> list)
{
// Use LINQ to find the distinct set of alphabet characters required.
_sectionTitles = (from c in list select c.Substring(0, 1)).Distinct().ToList();
// Sort the list alphabetically.
_sectionTitles.Sort();
// Add each element to the List<string> according to the letter it starts with
// in the SortedDictionary<int, List<string>>.
foreach (string element in list)
{
int sectionNum = _sectionTitles.IndexOf(element.Substring(0, 1));
if (_sectionElements.ContainsKey(sectionNum))
{
// SortedDictionary already contains a List<string> for that letter.
_sectionElements[sectionNum].Add(element);
}
else
{
// First time that letter has appeared, create new List<string> in the SortedDictionary.
_sectionElements.Add(sectionNum, new List<string> { element });
}
}
}
public override int NumberOfSections(UITableView tableView)
{
return _sectionTitles.Count;
}
public override string TitleForHeader(UITableView tableView, int section)
{
return _sectionTitles[section];
}
public override int RowsInSection(UITableView tableview, int section)
{
return _sectionElements[section].Count;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
string kCellIdentifier = "mycell";
UITableViewCell cell = tableView.DequeueReusableCell(kCellIdentifier);
if (cell == null)
{
// No re-usable cell found, create a new one.
cell = new UITableViewCell(UITableViewCellStyle.Default, kCellIdentifier);
}
string display = _sectionElements[indexPath.Section][indexPath.Row];
cell.TextLabel.Text = display;
return cell;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
string display = _sectionElements[indexPath.Section][indexPath.Row];
showAlert("RowSelected", "You selected: \"" + display + "\"");
// Prevent the blue 'selection indicator' remaining.
tableView.DeselectRow(indexPath, true);
}
private void showAlert(string title, string message)
{
using (var alert = new UIAlertView(title, message, null, "OK", null))
{
alert.Show();
}
}
}
}
}
I have performed the following tests on the device:
commented out
public override string TitleForHeader(UITableView tableView, int section)
procedure, launched App from within Instruments: a single leak is found; it seems that this leak is ALWAYS present, even when testing an empty App!
Test 1 Instruments screenshot
uncommented
public override string TitleForHeader(UITableView tableView, int section)
procedure, launched App from within Instruments: a lot of leaks are found, and their number grows up when scrolling the table up and down and/or selecting any row.
Test 2 Instruments screenshot
substituted the
return _sectionTitles[section];
statement in
public override string TitleForHeader(UITableView tableView, int section)
procedure with
return "Test Header…";
(thus using a constant string): the same as in test n.2!
Is MonoTouch bugged or am I forgetting something essential? If even such a simple App generates hundreds of memory leaks when running for a few minutes, what could happen with a real (and more complex) App?
I have searched the web extensively but I haven't found any significant post about this problem... any contribution will be greatly appreciated.
The way monotouch and the mono vm allocates and manages memory isn't fully understood by instruments. Apple has not rejected any applications for this. If you have a specific bug that you think is a leak please file an issue in the monotouch bugtracker at http://monotouch.net/Support
That being said, I looked at your screenshots and did find 1 16-byte leak that could happen, and the leak in your second screenshot, and fixed that for the upcoming monotouch 4.
精彩评论