【问题标题】:Why does MonoTouch cause lots of memory leaks (as reported by Instruments) even with very simple Apps?为什么即使使用非常简单的应用程序,MonoTouch 也会导致大量内存泄漏(由 Instruments 报告)?
【发布时间】:2011-06-27 13:37:25
【问题描述】:

我正在使用 Instruments 运行一个非常基本的测试应用程序,它发现了很多内存泄漏!由于我知道 Apple 人员在将应用程序提交到 iTunes 时会检查内存泄漏,所以我想调查一下这个问题。

我的环境:Mac OS X 10.6.6 上的 MonoDevelop 2.4.2 和 MonoTouch 3.2.4,目标是运行 iOS 4.2.1 的 iPad。

我的测试应用只显示了一个 TableView,其中填充了 50 个字符串的列表,并按它们的起始字母分组。

重现问题的步骤:使用 MonoDevelop 创建一个新的“基于 iPad 窗口的项目”,使用 Interface Builder 打开 MainWindow.xib 文件,在窗口上放置一个新的 TableView 并创建其出口(名为“tview”)以AppDelegate 类。 然后在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();
                    }
                }
            }
        }
    }

我在设备上进行了以下测试:

  1. 注释掉

    public override string TitleForHeader(UITableView tableView, int section)
    

    程序,从 Instruments 内启动 App:发现单个泄漏;似乎这种泄漏总是存在,即使在测试一个空的应用程序时也是如此!

    Test 1 Instruments screenshot

  2. 未注释

    public override string TitleForHeader(UITableView tableView, int section)
    

    过程,从 Instruments 中启动应用程序:发现大量泄漏,并且当上下滚动表格和/或选择任何行时,泄漏的数量会增加。

    Test 2 Instruments screenshot

  3. 替换

    return _sectionTitles[section];
    

    声明

    public override string TitleForHeader(UITableView tableView, int section)
    

    过程

    return "Test Header…";
    

    (因此使用常量字符串):与测试 n.2 中的相同!

是 MonoTouch 出了问题还是我忘记了一些重要的东西?如果即使是这样一个简单的应用程序在运行几分钟后也会产生数百次内存泄漏,那么一个真正的(并且更复杂的)应用程序会发生什么?

我已经广泛搜索了网络,但我没有找到任何关于这个问题的重要帖子......任何贡献都将不胜感激。

【问题讨论】:

  • 仅供参考;您的屏幕截图无效

标签: memory-leaks xamarin.ios instruments


【解决方案1】:

仪器尚未完全理解 monotouch 和 mono vm 分配和管理内存的方式。苹果没有拒绝任何申请。如果您有一个您认为是泄漏的特定错误,请在http://monotouch.net/Supporthttp://monotouch.net/Support 的 monotouch bugtracker 中提出问题

话虽如此,我查看了您的屏幕截图并确实发现了 1 个可能发生的 16 字节泄漏,以及您的第二个屏幕截图中的泄漏,并为即将推出的 monotouch 4 修复了该问题。

【讨论】:

  • 感谢您的回答,Geoff,但是……您所说的“mono vm”到底是什么意思?据我所知,MonoTouch 应用程序是提前编译的(Apple 的 SDK 许可和使用 iPhone/iPad 进行开发不允许应用程序具有解释或动态编译的代码)。我认为 MonoDevelop/MonoTouch 编译的代码与 XCode/Objective-C 编译的代码“无法区分”:两种方式都应该生成 ARM 本机代码...(继续)
  • 我还认为 C# 代码(我正在简化)映射到 iOS 本机 API 的调用,并且编译过程符合 iOS 本机内存管理模型(引用计数等)。从您的回复看来,从 C# 源代码编译的二进制代码管理内存的方式与从 Objective-C 源代码编译的二进制代码不同。这是真的吗?我很困惑......我在哪里可以找到这个主题的详尽解释?在此先感谢...
  • 您在一个很小的评论部分中塞满了许多问题。 Mono 虚拟机仍然存在于每个应用程序中,但它只是与 AOT 的汇编代码一起使用的 C 代码。托管对象有自己的对象堆,这是正确的垃圾回收所必需的。这是一个高级解释,请查看monotouch.net 了解更多信息。
  • 正如我所说,您发现了 2 个有效(尽管很小,16 字节)泄漏,我已在即将发布的版本中修复。
  • 没有人完全理解单声道触控管理内存的方式。任何在一段时间后显示“Hello world”应用程序之外的任何应用程序都会以低内存和崩溃告终。制作一个不泄漏的应用程序所花费的时间比制作几个本机应用程序要多,而且在本机程序中,您最终会得到更小、更快、耗电更少的应用程序。
猜你喜欢
  • 1970-01-01
  • 2012-02-09
  • 2010-12-16
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
  • 2020-06-28
  • 1970-01-01
相关资源
最近更新 更多