《MonoTouch开发实践指南》一2.2 通过示例将Object-C与MonoTouch进行对比-阿里云开发者社区

开发者社区> 华章出版社> 正文
登录阅读全文

《MonoTouch开发实践指南》一2.2 通过示例将Object-C与MonoTouch进行对比

简介: 本节书摘来自华章出版社《MonoTouch开发实践指南》一 书中的第2章,第2.2节,作者:(美)Michael Bluestein,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.2 通过示例将Object-C与MonoTouch进行对比

下面通过示例来说明上面讲到的一些概念。前面提到的UIActionSheet将会在示例中使用。通过Objective-C与C#的对比,将有助于清楚地了解如何使用MonoTouch开发应用程序。
注意 通常,使用MonoTouch开发应用程序不需要Xcode或Objective-C,这里这样做的目的是作为基础知识辅助说明MonoTouch的设计。如果有兴趣想了解更多的相关技术,推荐阅读Stephen G. Kochan写的《Programming in Objective-C 2.0》。
示例将会如图2-2所示,允许改变应用程序的背景图片。

screenshot

2.2.1 从Xcode开始编写应用程序

首先使用Xcode创建示例应用程序。在开发时,可以借鉴之前使用MonoDevelop开发应用程序时的步骤,并对比它们之间的不同。打开Xcode并创建一个名为LMT2-1的基于窗口的应用程序。在IB中双击MainWindow.xib文件打开它。如图2-3所示,在IB中将Library窗口中的UIImageView拖到该窗口。UIImageView将用来显示背景图片,因为要在代码中改变背景图片,所以需要为它创建一个outlet。

screenshot

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中。

screenshot

接着,需要连接的是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所示的日志信息。

screenshot

现在已经通过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类:

partial void changePicture (MonoTouch.UIKit.UIButton sender){
    Console.WriteLine("changePicture called in MonoTouch");
}

运行应用程序并触碰按钮,将会看到写在MonoDevelop中的Application Output标签页中的文本信息(如图2-6所示)。

screenshot

提示 使用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文件,会看到以下代码:
@interface LMT2_1AppDelegate : NSObject
这是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:。
注意 在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这两个图片。

将图片从Finder拖放到Group & files下的LMT2-1工程的根目录下,Xcode自动将它们打包到应用程序发布包。松开鼠标后,选择Copy Items into Destination Group抯 Folder以便将它们复制到与Xcode工程相同的位置。
在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生成应用程序时把图片打包到应用程序。
注意 如果在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如何创建这些并让它们工作。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

华章出版社

官方博客
官网链接