本章将介绍MonoTouch如何将iOS
SDK抽象化,从而可以使用C#的原生类进行开发,还介绍如何使用outlet,并将CocoaTouch Delegate模型和C#的事件模型进行比较,讲解如何在C#中使用这两种模式。本章还将介绍Objective-C的内存管理与MonoTouch的垃圾回收机制之间的异同。
下面通过示例来说明上面讲到的一些概念。前面提到的UIActionSheet将会在示例中使用。通过Objective-C与C#的对比,将有助于清楚地了解如何使用MonoTouch开发应用程序。
注意 通常,使用MonoTouch开发应用程序不需要Xcode或Objective-C,这里这样做的目的是作为基础知识辅助说明MonoTouch的设计。如果有兴趣想了解更多的相关技术,推荐阅读Stephen
G. Kochan写的《Programming in Objective-C 2.0》。
示例将会如图2-2所示,允许改变应用程序的背景图片。
图2-2 应用程序运行时改变它的背景图片
2.2.1 从Xcode开始编写应用程序
首先使用Xcode创建示例应用程序。在开发时,可以借鉴之前使用MonoDevelop开发应用程序时的步骤,并对比它们之间的不同。打开Xcode并创建一个名为LMT2-1的基于窗口的应用程序。在IB中双击MainWindow.xib文件打开它。如图2-3所示,在IB中将Library窗口中的UIImageView拖到该窗口。UIImageView将用来显示背景图片,因为要在代码中改变背景图片,所以需要为它创建一个outlet。
图2-3 在IB中添加UIImageView
IB中要连接的outlet在Objective-C的头文件中定义,稍后要在Xcode中进行添加。不过,在添加之前,先要在IB中完成界面设计,将一个按钮拖放到图片视图的顶部。
注意 按钮通常与UIBarButtonItem一起放在UIToolbar内。
按钮的作用是打开UIActionSheet,然后在UIActionSheet中切换图片视图中的图片。要将响应按钮触碰动作的处理代码与按钮连接起来,需要在头文件定义一个操作。在Xcode定义好这些之后,返回IB中进行连接。现在,回到Xcode。
在Xcode中,选择AppDelegate的头文件(LMT2_1AppDelegate.h),然后在文件里添加代码清单2-1中的代码。
代码清单2-1 Objective-C头文件中的Outlet和Action
#import <UIKit/UIKit.h> @interface LMT2_1AppDelegate : NSObject <UIApplicationDelegate> { UIWindow *window; UIImageView *imageView; } @property (nonatomic, retain) IBOutlet UIWindow *window; @property (nonatomic, retain) IBOutlet UIImageView *imageView; -(IBAction) changePicture: (id) sender; @end |
代码中为UIImageView增加了一个称为imageView的实例变量,并使用@property指令声明它是IBOutlet,这样在xib文件中,IB就可以使用这个实例变量来连接图片视图。
注意 IBOutlet只是一个空的宏,不需要为它编写代码,它的主要作用是与IB集成。同样,IBAction也不会有返回值,它只是作为IB的一个提示。
还需要在头文件中定义动作,以便在IB中连接按钮的触碰事件。此外,在括号中定义的可选参数(nonatomic和retain),会让编译器根据内存管理器和线程来生成实际的属性。可以看到,这里确实没有属性,它只是围绕getter和setter方法的“糖衣语法”(syntactic
sugar)。完成Xcode的代码后,转到实现文件(LMT2_1AppDelegate.m)添加属性和动作方法的代码。
另一半的Objective-C属性需要使用@synthesize指令,在实现文件添加该指令后,编译器会在头文件中生成属性的定义。稍后在使用MonoTouch实现此过程的时候,会发现MonoTouch更简单、更便捷。现在开始添加属性和方法。
在LMT2_1AppDelegate.m文件中,在Xcode模板已有的窗口语句下添加imageView的Synthesize语句。此外,添加changePicture:方法,并在方法内添加如代码清单2-2中所列出的记录字符串的存根实现。要记住,对编译器来说,IBAction是没有返回值的。在介绍UIActionSheet的时候,还要回到这里的实现操作。现在,保存Xcode中的文件后,切换回IB中的MainWindow.xib。
代码清单2-2 LMT2_AppDelegate.m
#import "LMT2_1AppDelegate.h" @implementation @synthesize window; @synthesize imageView; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch [window makeKeyAndVisible]; return YES; } -(IBAction) changePicture: (id) sender{ NSLog(@"placeholder for UIActionSheet code"); } - (void)dealloc { [window release]; [imageView release]; [super dealloc]; } |
在IB中,现在要将outlet连接到imageView,这样才可以在AppDelegate类中通过代码改变背景图片。还要设置Target-Action,这样才能在触碰按钮的时候调用changePicture:方法。现在开始完成这些步骤,要注意区分Xcode和Objective-C与MonoTouch之间的不同。
首先,将AppDelegate的imageView的outlet连接到xib中的UIImageView。如图2-4所示,在MainWindow.xib和Connections
Inspector中选择AppDelegate,然后将imageView outlet拖到窗口的UIImageView中。
图2-4 将imageView的outlet连接到UIImageView
接着,需要连接的是AppDelegate代码中创建的changePicture:方法。这是前面讲过的Target-Action模式的一个例子。当在MonoTouch中实现这些功能时,会看到在C#中是如何使用动作及如何将代码连接到C#风格的事件的。要将Objective-C的处理代码连接到按钮上的事件,先在MainWindow.xib中选择该按钮,然后在Connections
Inspector中将TouchUpInside事件拖到AppDelegate。当松开鼠标按钮,IB会弹出一个包含所有使用IBAction声明的方法菜单。此时这个菜单中只有一个changePicture:方法。选择changePicture,注意在Connections
Inspector中,按钮的TouchUpInside事件与目标类AppDelegate中的changePicture:动作建立了连接。保存后,在Xcode中运行应用程序。
应用程序运行后,从Xcode运行调试器控制台(Run→Console)。在触碰应用程序的按钮后,会在控制台看到如图2-5所示的日志信息。
图2-5 调试器控制台显示的changePicture:方法中的日志代码
现在已经通过Objective-C的Target-Action模式连接了事件,下一步要做的是使用MonoDevelop实现同样的功能。
2.2.2 在MonoTouch中实现相同的功能
现在,留意一下MonoTouch是如何使用C#的方式实现事件响应的Target-Action模式的。打开MonoDevelop并创建一个名为LMT2-2的基于iPhone窗口的新工程。在IB中打开MainWindow.xib,添加一个UIImageView和一个按钮,修改按钮上的文本为“Change
Image”。现在要为图片视图添加一个outlet以便代码可以访问到它,因此在此立即创建这个outlet。因为是由AppDelegate的代码访问这个outlet,所以要在AppDelegate中添加这个outlet。在Library窗口和IB的标签页内再次选择AppDelegate,在Outlets区域内添加一个名为imageView的outlet,修改它的类型为UIImageView。
注意 MonoTouch中的UIKit版本和基础架构分别包含在命名空间MonoTouch.UIKit和MonoTouch.Foundation中。
在Objective-C中,是在AppDelegate的头文件中添加outlet的,而在MonoTouch中,一旦xib文件中的对象(如UIImageView实例)和outlet之间建立连接,就会通过在局部类中生成的属性将outlet连接到代码。属性用ConnectAttribute进行声明,它的作用与Objective-C中的IBOutlet一样,即与IB集成。
现在,在MainWindow.xib窗口中选择AppDelegate,如之前做过的一样,将imageView的outlet连接到在窗口中的UIImageView。如果在IB中保存文件后,切换到MonoDevelop中查看MainWindow.xib.designer.cs文件,就会看到局部类和刚才提及的属性。现在,连接按钮。
第1章演示了如何使用C#风格的事件来连接UIButton事件。现在可以试着使用与刚才在Objective-C中使用的Target-Aciton模式来达到相同的效果。要记住,目标是AppDelegate,要在AppDelegate中创建动作方法来响应按钮的TouchUpInside事件。利用MonoDevelop与IB集成,可以达到相同的效果。在IB的Library窗口中,选择AppDelegate的Actions标签(在Outlets标签页的右边第一个),用“changePicture:”选择器添加一个动作。然后,选择窗口中的按钮,将Connections
Inspector中的Touch Up Inside事件拖到MainWindow.xib中的AppDelegate上进行连接。松开鼠标,弹出一个之前在Xcode和IB中出现过的changePicture:选择器菜单。选择changePicture:选择器,并在IB中保存所有修改。如果回头看看在MainWindow.xib.designer.cs文件中的局部类,就会看到新代码已添加,ExportAttribute声明的作用是将IB中添加的动作处理代码挂接到changePicture选择器。现在添加实现代码(类似占位符的代码,后面将会替换为UIActionSheet)。在Main.cs文件中,添加以下代码到AppDelegate类:
运行应用程序并触碰按钮,将会看到写在MonoDevelop中的Application
Output标签页中的文本信息(如图2-6所示)。
图2-6 MonoDevelop中Application
Output标签页显示的按钮事件的运行结果
提示 使用C#事件可以避免不必要的动作。就如第1章所看到的,这样做的好处是,可以使用C#风格的事件来实现,从而避免使用动作方法来实现。只需要为按钮创建一个outlet就可通过程序访问注册的事件。MonoTouch支持创建C#事件委托的显式回调方法,以及匿名方法和lambda表达式。
正如所看到的,MonoTouch可以使用与Objective-C相同的模式实现事件处理。此外,MonoTouch提供了更灵活的C#风格的事件以供选择。
现在要做的是为UIActionSheet编码以改变图片。还是先通过Xcode实现,然后再通过MonoTouch实现,以比较两者在实现Objective-C委托模式之间的不同。不过,在实现之前,要先花点时间了解MonoTouch是如何实现AppDelegate和如何处理它的。
2.2.3 AppDelegate实现的比较
在本书前几章的简单示例中,还不能对应用程序进行结构化设计,这将在第3章进行讲述。然而,在开发应用程序中总会用到AppDelegate。在目前的示例中,所有的应用程序代码都是在它内部添加的,实际上它的作用是通过其他类来处理应用程序逻辑。AppDelegate是一个在应用程序的生命周期内用来响应UIApplication各类操作并进行处理的类,这些处理包括应用程序终止、应用程序启动完成、接收系统发出的内存警告等。在Objective-C中,AppDelegate定义为一个名为UIApplicationDelegate的协议。记住Objective-C协议基本类似于C#接口,除了一些方法指定为可选的外。如果打开Xcode中的LMT2_1AppDelegate.h文件,会看到以下代码:
这是Objective-C在相关LMT2_1AppDelegate.m文件中实现LMT2_1AppDelegate类定义的方式,它派生于NSObject(CocoaTouch中其他类的基类,类似于.NET中的Object类)并遵循UIApplicationDelegate协议。这意味着实现将包含协议的所有请求方法以及可选方法。这是在LMT2_AppDelegate.m文件中实现的方法application:didFinishLaunchingWithOptions:,应用程序完成启动后会调用该方法。与MonoTouch进行比较,就会发现AppDelegate是作为一个类实现的。如果作为接口实现,那会工作不了,因为它不能给可选方法附加协议类型,因而它必须实现为一个如代码清单2-3所示的包含各种虚函数的类。这就是在MonoTouch中看到的模式,将Objective-C协议转换为派生于基类的类,并通过重写虚函数插入实现代码。
代码清单2-3 MonoTouch AppDelegate类
[Register("UIApplicationDelegate")] [Model()] public class UIApplicationDelegate : NSObject { // Constructors public UIApplicationDelegate(); public UIApplicationDelegate(NSCoder coder); public UIApplicationDelegate(NSObjectFlag t); public UIApplicationDelegate(IntPtr handle); // Methods public virtual void FinishedLaunching(UIApplication application); public virtual bool FinishedLaunching(UIApplication application, NSDictionary launchOptions); public virtual void OnActivated(UIApplication application); public virtual void OnResignActivation( UIApplication application); public virtual void HandleOpenURL(UIApplication application, NSUrl url); public virtual void ReceiveMemoryWarning( UIApplication application); public virtual void WillTerminate(UIApplication application); public virtual void ApplicationSignificantTimeChange( UIApplication application); public virtual void WillChangeStatusBarOrientation( UIApplication application, UIInterfaceOrientation newStatusBarOrientation, double duration); public virtual void DidChangeStatusBarOrientation( UIApplication application, UIInterfaceOrientation oldStatusBarOrientation); public virtual void WillChangeStatusBarFrame( UIApplication application, RectangleF newStatusBarFrame); public virtual void ChangedStatusBarFrame( UIApplication application, RectangleF oldStatusBarFrame); public virtual void RegisteredForRemoteNotifications( UIApplication application, NSData deviceToken); public virtual void FailedToRegisterForRemoteNotifications( UIApplication application, NSError error); public virtual void ReceivedRemoteNotification( UIApplication application, NSDictionary userInfo); ... } |
注意 所有有ExportAttribute定义的方法,在重写虚函数时,不需要知道实际的选择器或提供属性。
正如前面所看到的,在MonoTouch的实现中,MonoDevelop和IB之间相结合产生的所有代码都结束在一个局部类定义中,这为实现应用程序代码提供了很大的自由度,无须使用工具接口。继承的层次结构可由开发人员在局部类定义中自行设置。在AppDelegate的情况下,MonoDevelop代码模板从UIApplicationDelegate派生出AppDelegate,在Main.cs文件中,代码如下:
public partial class AppDelegate : UIApplicationDelegate |
模板还重写了FinishedLaunching方法,这里可以实现之前在Objective-C实现的(就像前面在Xcode应用程序中看到的)application:didFinishLaunchin-gWithOptions:。
注意 在http://tirania.org/tmp/rosetta.html中,可以找到一个非常有用的名为“MonoTouch
Rosetta Stone”的文档,它列出了MonoTouch中与Objective-C选择器对应的C#实现。
AppDelegate是CocoaTouch中使用的Delegation模式的一个示例,MonoTouch完全支持。现在要做的是在应用程序中使用UIActionSheet,这是iOS
SDK中另一种Delegation例子。
2.2.4 通过Xcode实现UIActionSheet
现在回到Xcode(听起来像电影)创建UIActionSheet并设置不同按钮以便改变imageView中的图片。目前,示例一直是在设计窗口和AppDelegate中完成的,不算复杂。如前所述,这不是太规范的设计,具体会在第3章中讲述。现在需要一个视图来作为UI的容器,一直以来,为了简单起见都直接跳过了,没有讨论。这个视图就是早已存在的imageView,将用来显示ActionSheet。在LMT2_1AppDelegate.m文件中,添加如代码清单2-4所示的代码到changPicture:方法来创建ActionSheet。
代码清单2-4 创建UIActionSheet的代码
-(IBAction) changePicture: (id) sender{ UIActionSheet *changeImageSheet = [[UIActionSheet alloc] initWithTitle:@"Change Image" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:NULL otherButtonTitles:@"Image 1", @"Image 2", NULL]; [changeImageSheet showInView:imageView]; [changeImageSheet release]; } |
代码将创建并显示一个ActionSheet,它内部除了包含"Image 1"和"Image
2"两个按钮外,还包含一个"Cancel"(取消)按钮。使用otherButtonTitles定义的按钮的索引分别是0和1,而取消按钮的索引是2。按钮的单击操作将使用索引进行区分,这样的处理方式是Objective-C
Delegation(委托)的又一个例子。注意代码中创建ActionSheet时设置的Delegation(委托)属性为self,说明它将引用当前对象,与C#中的this一样。也就是说,传递给ActionSheet的对象就是LMT2_1AppDelegate实例,因为当前类就是这个。因此,LMT2_1AppDelegate需要遵循UIActionSheetDelegate协议。现在设置UIActionSheetDelegate协议,打开对应的头文件并在尖括号内UIApplicationDelegate协议定义的后面加入协议。现在要实现UIActionSheetDelegate协议的带有选择器的actionSheet:
clickedButtonAtIndex:方法以便接收UIActionSheet的按钮单击后的回调处理。要完成这个,在@end:前的头文件添加以下代码:
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex; |
当UIActionSheet的按钮单击后,实现方法将执行并返回单击按钮的索引。处理代码可根据索引改变图片。
在LMT2_1AppDelegate.m文件中添加代码清单2-5所示的代码完成实现。
代码清单2-5 actionSheet:clickedButtonAtIndex:方法的实现代码
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{ switch (buttonIndex) { case 0: imageView.image = [UIImage imageNamed: @"image1.jpg"]; break; case 1: imageView.image = [UIImage imageNamed: @"image2.jpg"]; break; case 2: NSLog(@"cancel"); break; default: break; } } |
代码清单2-5引用了image1.jpg和iamge2.jpg这两个图片。
注意 本书的示例图片和代码可以到作者在Github的空间https://github.com/mikebluestein下载。
如图2-7所示,将图片从Finder拖放到Group & files下的LMT2-1工程的根目录下,Xcode自动将它们打包到应用程序发布包。松开鼠标后,选择Copy
Items into Destination Group抯 Folder以便将它们复制到与Xcode工程相同的位置。
图2-7 添加到XCode的Group &&
Files下的图片文件
在Xcode中生成并运行应用程序,然后单击按钮打开ActionSheet并从中选择一个按钮来改变图片。当选择ActionSheet的按钮时,ActionSheet的委托被调用,从而处理按钮单击的代码也会调用。可以看到,Objective-C中的委托设计模式非常类似于其他语言的回调接口,譬如C#。不过,由于可选方法需要支持协议,所以MonoTouch通过类来实现。如以上看到的,在Objective-C中,AppDelegate的单一实现在示例中要遵循名为UIApplicationDelegate和UIActionSheetDelegate的协议。在MonoTouch中,要处理这个可能会不知所措,无从下手,因而需要使用类来实现,但不允许多重继承。现在回到MonoDevelop并完成实现。
2.2.5 在MonoTouch中实现UIActionSheet
在MonoTouch中,会在构造函数中映射代码清单2-4中Objective-C的initWithTitle:
delegate:cancelButtonTitle:destructiveButtonTitle:otherB-uttonTitles:方法。此外,在C#中显示UIActionSheet仍然可以调用ShowInView方法。不过,这时候委托指针指向的不是当前实例,因为它已经是UIApplicationDelegate的子类,而是从UIActionSheetDelegate派生的嵌套类,这样就可以保证委托实现封装在类(AppDelegate)内,以便创建的类实例可以进行委托处理(UIActionSheet)。其实,也不必非得这样嵌入委托类,还可以在这个类的外部创建它。不过,像嵌入委托这样的处理方式在MonoTouch中是经常用到的,而且它表现得很好。根据以上说明,就可实现如代码清单2-6所示的changePicture方法。
代码清单2-6 在MonoTouch中显示UIActionSheet
UIActionSheet _changePictureSheet; ... partial void changePicture (MonoTouch.UIKit.UIButton sender){ _changePictureSheet = new UIActionSheet( "Change Picture", new ChangePictureActionSheetDelegate(this), "Cancel", null, "Image 1", "Image 2"); _changePictureSheet.ShowInView(imageView); } |
ChangePictureActionSheetDelegate就是刚才讲到的嵌套类。在Objective-C中,使用actionSheet:clickedButtonAtIndex:方法来处理UIActionSheet按钮的单击操作。在MonoTouch中,前面已经提过,要通过重写虚函数来实现。实现方法的定义代码如下:
public virtual void Clicked (UIActionSheet actionSheet, int buttonIndex) |
与Objective-C示例一样,实现将使用单击按钮的索引来切换图片视图的图片。在当前版本的MonoTouch中要注意,Cancel(取消)按钮的索引是最后一个索引,而不是Objective-C中的第一个索引。如果需要,也可以通过UIActionSheet的CancelButtonIndex属性来修改Cancel(取消)按钮的索引值。为了简单起见,这里将使用默认值,ChangePictureActionSheetDelegate类的实现代码如代码清单2-7所示。
注意 下一版本的MonoTouch会固定Cancel(取消)按钮的位置。
代码清单2-7 在ChangePictureActionSheetDelegate类实现单击操作
class ChangePictureActionSheetDelegate : UIActionSheetDelegate { AppDelegate _appDel; public ChangePictureActionSheetDelegate (AppDelegate appDel) { _appDel = appDel; } public override void Clicked (UIActionSheet actionSheet, int buttonIndex) { switch (buttonIndex) { case 1: appDel.imageView.Image = UIImage.FromFile("image1.jpg"); break; case 2: _appDel.imageView.Image = UIImage.FromFile("image2.jpg"); break; } } } |
现在重复在Xcode中的操作,将图片复制到工程目录下。在Finder中将图片拖到MonoDevelop解决方案树下,然后单击如图2-8所示的对话框中的Copy按钮。在解决方案树中的图片上右击(如果使用的单按钮鼠标,请按下Ctrl键再单击),然后设置为Build
Action to Content,这样就可以在MonoDevelop生成应用程序时把图片打包到应用程序。
图2-8 MonoDevelop的复制文件对话框
注意 如果在MonoDevelop中创建文件夹,等同于在磁盘上创建文件夹,这与在Xcode中在磁盘根目录下默认为虚拟目录不同。
最后完成如代码清单2-8所示的Main.cs文件。如果现在生成并运行应用程序,切换图片,那么效果与在Objective-C中看到的一样。
代码清单2-8 Main.cs的最后版本
using System; using System.Collections.Generic; using System.Linq; using MonoTouch.Foundation; using MonoTouch.UIKit; namespace LMT22 { public class Application { static void Main (string[] args) { UIApplication.Main (args); } } // The name AppDelegate is referenced in the MainWindow.xib file. public partial class AppDelegate : UIApplicationDelegate { UIActionSheet _changePictureSheet; // This method is invoked when the application has // loaded its UI and its ready to run public override bool FinishedLaunching (UIApplication app, NSDictionary options) { window.MakeKeyAndVisible (); return true; } class ChangePictureActionSheetDelegate : UIActionSheetDelegate { AppDelegate _appDel; public ChangePictureActionSheetDelegate ( AppDelegate appDel) { _appDel = appDel; } public override void Clicked (UIActionSheet actionSheet, int buttonIndex) { switch (buttonIndex) { case 1: _appDel.imageView.Image = UIImage.FromFile ("image1.jpg"); break; case 2: _appDel.imageView.Image = UIImage.FromFile ("image2.jpg"); break; } } } partial void changePicture (MonoTouch.UIKit.UIButton sender) { _changePictureSheet = new UIActionSheet ( "Change Picture", new ChangePictureActionSheetDelegate (this), "Cancel", null, "Image 1", "Image 2"); _changePictureSheet.ShowInView (imageView); } // This method is required in iPhoneOS 3.0 public override void OnActivated (UIApplication application) { } } } |
现在,已经了解了如何在MonoTouch中使用C#开发应用程序,并与在Xcode中使用Objective-C开发iPhone应用程序的核心设计模式做了比较。下面要了解的是MonoTouch如何创建这些并让它们工作。
|