简析MonoTouch工作原理

ARM 146浏览

MonoTouch使用静态编译方式将代码编译为ARM二进制代码。使用MonoTouch创建的每一个应用程序都是独立的,也就是说,应用程序所需要的东西都要打包,之所以这样,是因为iPhone不允许使用共享库。MonoTouch通过绑定方式向C#公开iPhone的原生库,因而不需要在语言之间做转换。通过静态编译(Ahead-Of-Time,AOT)生成ARM二进制代码,MonoTouch应用程序就可满足发布应用程序到App Store的所有必需条件。

注意 在写本书(《MonoTouch应用开发实践指南:使用C#和.NET开发iOS应用》)的时候,许多使用MonoTouch开发的应用程序已经发布到App Store。在网站monotouch.info里有这些应用程序的完整列表,并有大量相关资源的链接。现在还可以在http://monotouch.net/Apps查看应用程序的展示。

然而,由于苹果政策和内核的限制,JIT编译的代码是不允许的。这就限制了.NET的某些领域(如CodeDOM、Reflection-Emit(尽管映射可以使用)、虚拟泛型方法、非确定性泛型和动态语言运行库(Dynamic Language Runtime,DLR))的使用。在一般情况下,大多数非UI的C#代码都可以稍做修改或不做修改就能移植到MonoTouch中。

对于刚才在MonoTouch中创建的应用程序,现在来看看它们在MonoDevelop中生成时都做了什么事情(示例在虚拟器生成一个发布版本)。

设置配置为Release | iPhoneSimulator,然后生成解决方案。生成后,在Finder中浏览工程目录。

提示 在MonoDevelop解决方案树中右击该工程,并选择Open Containing Folder就可在Finder中打开工程目录。

在Finder的工程目录依次打开以下目录:bin→iPhoneSimulator→Release。在目录中,可以看到生成的应用程序包。如果生成的是debug(调试)版本,就可在Debug目录中找到它,它在iPhone目录中而不是iPhoneSimulator目录中。在所有情况下,目标目录结构就是编译应用时生成应用程序包的形式。有一个扩展名为.app的特殊文件夹,它的作用是使应用程序看起来像单一的文件,便于部署。对于当前的应用程序,会看到文件的名字为LMT2-2.app。如果在Finder中右击该文件并选择Show
Package Contents,将看到如下图所示的编译后的执行文件和应用程序所依赖的文件。

应用程序包的内容

各种DLL文件和EXE文件是剩余的元数据。此外,两个图片文件用前面的Build Action of Content,以及info.plist文件和MainWindow.nib标记。文件info.plist是一个属性列表文件,它是XML文件,包含了应用程序不同元数据构成的字典。文件MainWindow.nib是MainWindow.xib文件的二进制版本。

注意 在部署应用程序到App Store的时候,需要将.app文件压缩为zip文件和一些图片文件一起上传。

在应用程序包中,最重要的文件是LMT2-2,这是由AOT编译的,包含项目中所有的依赖项,包含MonoTouch提供的用于应用程序内管理内存的工具,是可在Unix上执行的文件。MonoTouch的内存管理与iPhone上的Objective-C不同,它是Mono垃圾收集器的一个分支版本,负责应用程序代码中所有的内存管理工作。

注意 MAC OS X中的Objective-C 2.0并没有垃圾收集器。此外,在模拟器上,MonoTouch使用JIT编译而不是AOT。

回头看看Objective-C版本的应用程序,会注意到dealloc方法会发送一个释放信息给imageView;而在MonoTouch版本中,则不需要。其原因就是因为MonoTouch有垃圾收集器,而在iPhone的Objective-C中没有。它依赖于基于引用计数(retain count)的内存管理模型。如果熟悉的话,会发现这有点儿类似于COM,至少表面上是这样。但是,UIImageView并不是一个.NET的类,怎样才能收集它呢?下面将讲述这问题。

内存管理

要了解MonoTouch如何使用原生类管理内存,首先要了解Objective-C管理内存的知识。在Objective-C中,内存管理是通过引用计数来实现的。每当创建对象时,就会向Objective-C发送一个alloc信息,然后返回对象(实际上是对象的指针,这里把引用作为对象)和它的引用计数。每发送一个引用信息给对象,对象的引用计数就会加1;而每当一个释放信息发送给对象,引用计数就会减1,当对象的引用计数为0时,内存就会释放。

注意 Objective-C的alloc和init版本的组合类似于C#中的构造函数。

此外,在许多情况下,工厂方法(factory method)模式的类只返回一个对象。在这种情况下,如果分配内存后,工厂方法在内部释放对象,那么在调用者有机会使用它之前,该对象将销毁,这时,如果工厂方法对这视而不见,并返回了该分配对象(包含引用计数),那么该对象将永远不会销毁。通过由NSAutoreleasePool类实现的自动释放池(autorelease pool),可以解决这个问题。基本上,NSAutoreleasePool会延迟对象的销毁,这样调用者就能在它销毁前使用它。在iPhone的引用计数世界中,对象会添加到自动释放池直到释放池自身发出一个遗漏(drain)信息(或释放信息)时,才会发送释放信息。只要释放池中的对象不明确地发送一个保留信息,对象就会销毁。

当任何带有引用计数为1的对象释放时,就会调用dealloc方法,这时就可以调用对象的释放方法来清理对象(即释放对象的指针)。

基于以上信息,现在探讨MonoTouch如何与原生代码交互。对于Objective-C对象,MonoTouch在主线程创建一个自动释放池和线程池线程。因此,每当线程使用Objective-C类时,MonoTouch开发人员就要创建一次自动释放池,示例,使用以下代码创建线程:

void DoSomethingOnAnotherThread ()
{
      Thread t = new Thread (DoSomething);
      t.Start ();
}

在“DoSomething”方法内要使用Objective-C对象(也就是说,从NSObject派生的任何类),都需要将代码包含在自动释放池内,如以下代码:

void DoSomething()
{
      using(var pool = new NSAutoreleasePool())
      {
           //code that touched Objective-C objects ...
      }
}

提示 如果在运行时出现“autoreleased with no pool in place—just leaking,”这样的警告,如之前所示那样将代码包含到NSAutoreleasePool中即可。

除此之外,我们发现MonoTouch在内存管理方面做得相当出色。当然,.NET对象会通过MonoTouch GC自身进行管理。需要内存管理的其他地方,可能还需要采取更精细的内存控制,或者至少清楚NSObject基类的IDisposable实现。在MonoTouch端,会发送释放信息到NSObject对象子类的实例(附带一些内部数据结构的清理),如果要销毁的是占用大量内存的对象,这非常有用,而且宜早不宜迟。对于NSAutoreleasePool,当释放信息发送到释放池时,会将释放信息发送到它内部所有的自动释放对象。

-----------------------------

本文节选自《MonoTouch应用开发实践指南:使用C#和.NET开发iOS应用》第2.3节“MonoTouch的工作原理”

原书名:Learning MonoTouch: A Hands-On Guide to Building iOS Applications with C# and .NET

作者:(美)Michael Bluestein   著

译者:黄灯桥 黄浩宇 译

本书系统讲解了利用MonoTouch开发iOS应用的技术和方法,并包含大量精心设计的案例,可操作性极强。它是有效指导有经验的.NET开发者利用已掌握的.NET技术快速开发iOS应用的参考书,为.NET开发者低成本地向iOS开发者过渡提供了捷径。

阅读本书PDF样章,请访问:http://vdisk.weibo.com/s/j8IFO

豆瓣收藏本书,请访问:http://book.douban.com/subject/20394299/