Xamarin 中的事件、协议和委托Events, Protocols and Delegates in Xamarin.iOS

Xamarin 使用控件来公开大多数用户交互事件。Xamarin.iOS uses controls to expose events for most user interactions. Xamarin iOS 应用程序使用这些事件的方式与使用传统 .NET 应用程序的方式几乎相同。Xamarin.iOS applications consume these events in much the same way as do traditional .NET applications. 例如,Xamarin UIButton 类有一个名为 TouchUpInside 的事件,并使用此事件,就像此类和事件位于 .NET 应用中一样。For example, the Xamarin.iOS UIButton class has an event called TouchUpInside and consumes this event just as if this class and event were in a .NET app.

除了 .NET 方法之外,Xamarin 还公开了另一个可用于更复杂的交互和数据绑定的模型。Besides this .NET approach, Xamarin.iOS exposes another model that can be used for more complex interaction and data binding. 此方法使用 Apple 调用委托和协议。This methodology uses what Apple calls delegates and protocols. 委托在概念上类似于中C#的委托,但并不是定义和调用单个方法,而是在目标-C 中的委托是符合协议的整个类。Delegates are similar in concept to delegates in C#, but instead of defining and calling a single method, a delegate in Objective-C is an entire class that conforms to a protocol. 协议类似于中C#的接口,只不过其方法可以是可选的。A protocol is similar to an interface in C#, except that its methods can be optional. 例如,若要在 UITableView 中填充数据,则需要创建一个委托类,该类实现在 UITableViewDataSource 协议中定义的方法,UITableView 将调用该方法来填充自身。So for example, to populate a UITableView with data, you would create a delegate class that implements the methods defined in the UITableViewDataSource protocol that the UITableView would call to populate itself.

在本文中,你将了解所有这些主题,为你提供在 Xamarin 中处理回调方案的坚实基础,其中包括:In this article you’ll learn about all these topics, giving you a solid foundation for handling callback scenarios in Xamarin.iOS, including:

  • 事件–使用带有 UIKit 控件的 .net 事件。Events – Using .NET events with UIKit controls.
  • 协议–了解哪些协议是什么以及如何使用它们,以及如何创建一个为地图批注提供数据的示例。Protocols – Learning what protocols are and how they are used, and creating an example that provides data for a map annotation.
  • 委托-通过扩展映射示例来了解目标-C 委托,以处理包含批注的用户交互,然后了解强和弱委托之间的差异,以及何时使用这些委托。Delegates – Learning about Objective-C delegates by extending the map example to handle user interaction that includes an annotation, then learning the difference between strong and weak delegates and when to use each of these.

为了阐明协议和委托,我们将构建一个简单的地图应用程序,用于将批注添加到地图中,如下所示:To illustrate protocols and delegates, we’ll build a simple map application that adds an annotation to a map as shown here:

在处理此应用之前,让我们先了解一下 UIKit 下的 .NET 事件。Before tackling this app, let’s get started by looking at .NET events under the UIKit.

具有 UIKit 的 .NET 事件.NET Events with UIKit

Xamarin 在 UIKit 控件上公开 .NET 事件。Xamarin.iOS exposes .NET events on UIKit controls. 例如,UIButton 有一个 TouchUpInside 事件,该事件在 .NET 中照常处理,如以下使用C# lambda 表达式的代码所示:For example, UIButton has a TouchUpInside event, which you handle as you normally would in .NET, as shown in the following code that uses a C# lambda expression:

aButton.TouchUpInside += (o,s) => {
    Console.WriteLine("button touched");
};

你还可以使用如下所示C#的2.0 样式匿名方法实现此操作:You could also implement this with a C# 2.0-style anonymous method like this one:

aButton.TouchUpInside += delegate {
    Console.WriteLine ("button touched");
};

前面的代码在 UIViewController 的 ViewDidLoad 方法中连接。The preceding code is wired up in the ViewDidLoad method of the UIViewController. aButton 变量引用一个按钮,你可以在 iOS 设计器中或使用代码添加该按钮。The aButton variable references a button, which you could add either in the iOS Designer or with code. 下图显示了已在 iOS 设计器中添加的按钮:The following figure shows a button that has been added in the iOS Designer:

Xamarin 还支持将代码连接到与控件发生的交互的目标操作样式。Xamarin.iOS also supports the target-action style of connecting your code to an interaction that occurs with a control. 若要为 " Hello " 按钮创建目标操作,请在 IOS 设计器中双击它。To create a target-action for the Hello button, double click it in the iOS Designer. 将显示 UIViewController 的代码隐藏文件,并要求开发人员选择要插入连接方法的位置:The UIViewController's code-behind file will be displayed and the developer will be asked to select a location to insert the connecting method:

选择某个位置后,将创建一个新方法,并将其连接到该控件。After a location is selected, a new method is created and wired-up to the control. 在下面的示例中,单击按钮时,会将一条消息写入控制台:In the following example, a message will be written to the console when the button is clicked:

有关 iOS 目标-操作模式的详细信息,请参阅 Apple 的 iOS 开发人员库中的适用于 ios 的核心应用程序能力的目标操作部分。For more details about the iOS target-action pattern, see the Target-Action section of Core Application Competencies for iOS in Apple’s iOS Developer Library.

有关如何将 iOS 设计器与 Xamarin 一起使用的详细信息,请参阅Ios 设计器概述文档。For more information about how to use the iOS Designer with Xamarin.iOS, see the iOS Designer Overview documentation.

事件Events

如果要从 UIControl 截获事件,可以使用一系列选项:从使用C# lambda 和委托函数到使用低级别目标-C api。If you want to intercept events from UIControl, you have a range of options: from using the C# lambdas and delegate functions to using the low-level Objective-C APIs.

以下部分说明如何捕获按钮上的 System.windows.uielement.touchdown> 事件,具体取决于所需的控制程度。The following section shows how you would capture the TouchDown event on a button, depending on how much control you need.

C#方式C# Style

使用委托语法:Using the delegate syntax:

UIButton button = MakeTheButton ();
button.TouchDown += delegate {
    Console.WriteLine ("Touched");
};

如果你想要改用 lambda:If you like lambdas instead:

button.TouchDown += () => {
   Console.WriteLine ("Touched");
};

如果希望有多个按钮,请使用同一处理程序来共享同一代码:If you want to have multiple buttons use the same handler to share the same code:

void handler (object sender, EventArgs args)
{
   if (sender == button1)
      Console.WriteLine ("button1");
   else
      Console.WriteLine ("some other button");
}

button1.TouchDown += handler;
button2.TouchDown += handler;

监视多种类型的事件Monitoring more than one kind of Event

UIControlEvent C#标志的事件对于单个标志具有一对一的映射。The C# events for UIControlEvent flags have a one-to-one mapping to individual flags. 如果要让相同的代码段处理两个或更多事件,请使用 UIControl.AddTarget 方法:When you want to have the same piece of code handle two or more events, use the UIControl.AddTarget method:

button.AddTarget (handler, UIControlEvent.TouchDown | UIControlEvent.TouchCancel);

使用 lambda 语法:Using the lambda syntax:

button.AddTarget ((sender, event)=> Console.WriteLine ("An event happened"), UIControlEvent.TouchDown | UIControlEvent.TouchCancel);

如果需要使用目标 C 的低级别功能,如挂钩到特定对象实例和调用特定的选择器:If you need to use low-level features of Objective-C, like hooking up to a particular object instance and invoking a particular selector:

[Export ("MySelector")]
void MyObjectiveCHandler ()
{
    Console.WriteLine ("Hello!");
}

// In some other place:

button.AddTarget (this, new Selector ("MySelector"), UIControlEvent.TouchDown);

请注意,如果在继承的基类中实现实例方法,则该方法必须是公共方法。Please note, if you implement the instance method in an inherited base class, it must be a public method.

协议Protocols

协议是一种提供方法声明列表的目标 C 语言功能。A protocol is an Objective-C language feature that provides a list of method declarations. 它的作用类似于中C#的接口,主要区别在于协议可以具有可选方法。It serves a similar purpose to an interface in C#, the main difference being that a protocol can have optional methods. 如果采用协议的类未实现可选方法,则不会调用这些方法。Optional methods are not called if the class that adopts a protocol does not implement them. 此外,目标中的单个类可以实现多个协议,就像C#类可以实现多个接口一样。Also, a single class in Objective-C can implement multiple protocols, just as a C# class can implement multiple interfaces.

Apple 在 iOS 中使用协议来定义类要采用的协定,同时从调用方抽象出实现类,从而像C#接口一样操作。Apple uses protocols throughout iOS to define contracts for classes to adopt, while abstracting away the implementing class from the caller, thus operating just like a C# interface. 协议既可用于非委托方案(如下面所示的 MKAnnotation 示例),也可用于委托(如本文档后面的 "委托" 部分中所述)。Protocols are used both in non-delegate scenarios (such as with the MKAnnotation example shown next), and with delegates (as presented later in this document, in the Delegates section).

采用 Xamarin 的协议Protocols with Xamarin.ios

我们来看一个示例,其中使用了 Xamarin 中的目标-C 协议。Let’s take a look at an example using an Objective-C protocol from Xamarin.iOS. 在此示例中,我们将使用 MKAnnotation 协议,该协议是 MapKit 框架的一部分。For this example, we’ll use the MKAnnotation protocol, which is part of the MapKit framework. MKAnnotation 是一种协议,该协议允许使用它来提供有关可添加到地图的批注的信息。MKAnnotation is a protocol that allows any object that adopts it to provide information about an annotation that can be added to a map. 例如,实现 MKAnnotation 的对象提供批注的位置以及与其关联的标题。For example, an object implementing MKAnnotation provides the location of the annotation and the title associated with it.

这样,就可以使用 MKAnnotation 协议来提供批注随附的相关数据。In this way, the MKAnnotation protocol is used to provide pertinent data that accompanies an annotation. 批注本身的实际视图是从采用 MKAnnotation 协议的对象中的数据生成的。The actual view for the annotation itself is built from the data in the object that adopts the MKAnnotation protocol. 例如,当用户点击批注时显示的标注文本(如下面的屏幕截图所示)来自实现协议的类中的 Title 属性:For example, the text for the callout that appears when the user taps on the annotation (as shown in the screenshot below) comes from the Title property in the class that implements the protocol:

如以下部分中所述,协议深入探讨,Xamarin 将协议绑定到抽象类。As described in the next section, Protocols Deep Dive, Xamarin.iOS binds protocols to abstract classes. 对于 MKAnnotation 协议,绑定C#类命名为 MKAnnotation 以模仿协议名称,并且它是 NSObject 的子类(CocoaTouch 的根基类)。For the MKAnnotation protocol, the bound C# class is named MKAnnotation to mimic the name of the protocol, and it is a subclass of NSObject, the root base class for CocoaTouch. 协议需要为该坐标实现 getter 和 setter;但标题和副标题是可选的。The protocol requires a getter and setter to be implemented for the coordinate; however, a title and subtitle are optional. 因此,在 MKAnnotation 类中,Coordinate 属性是抽象的,需要实现它,TitleSubtitle 属性被标记为 "虚拟",使其成为可选属性,如下所示:Therefore, in the MKAnnotation class, the Coordinate property is abstract, requiring it to be implemented and the Title and Subtitle properties are marked virtual, making them optional, as shown below:

[Register ("MKAnnotation"), Model ]
public abstract class MKAnnotation : NSObject
{
    public abstract CLLocationCoordinate2D Coordinate
    {
        [Export ("coordinate")]
        get;
        [Export ("setCoordinate:")]
        set;
    }

    public virtual string Title
    {
        [Export ("title")]
        get
        {
            throw new ModelNotImplementedException ();
        }
    }

    public virtual string Subtitle
    {
        [Export ("subtitle")]
        get
        {
            throw new ModelNotImplementedException ();
        }
    }
...
}

只要至少实现了 Coordinate 属性,任何类都可以通过仅从 MKAnnotation 派生来提供批注数据。Any class can provide annotation data by simply deriving from MKAnnotation, as long as at least the Coordinate property is implemented. 例如,下面是一个在构造函数中采用坐标并返回标题字符串的示例类:For example, here’s a sample class that takes the coordinate in the constructor and returns a string for the title:

/// <summary>
/// Annotation class that subclasses MKAnnotation abstract class
/// MKAnnotation is bound by Xamarin.iOS to the MKAnnotation protocol
/// </summary>
public class SampleMapAnnotation : MKAnnotation
{
    string title;

    public SampleMapAnnotation (CLLocationCoordinate2D coordinate)
    {
        Coordinate = coordinate;
        title = "Sample";
    }

    public override CLLocationCoordinate2D Coordinate { get; set; }

    public override string Title {
        get {
            return title;
        }
    }
}

通过它绑定到的协议,子类 MKAnnotation 的任何类都可以提供将在创建批注视图时由地图使用的相关数据。Through the protocol it is bound to, any class that subclasses MKAnnotation can provide relevant data that will be used by the map when it creates the annotation’s view. 若要将批注添加到地图,只需调用 MKMapView 实例的 AddAnnotation 方法,如以下代码所示:To add an annotation to a map, simply call the AddAnnotation method of an MKMapView instance, as shown in the following code:

//an arbitrary coordinate used for demonstration here
var sampleCoordinate =
    new CLLocationCoordinate2D (42.3467512, -71.0969456); // Boston

//create an annotation and add it to the map
map.AddAnnotation (new SampleMapAnnotation (sampleCoordinate));

此处的地图变量是 MKMapView 的实例,它是表示地图本身的类。The map variable here is an instance of an MKMapView, which is the class that represents the map itself. MKMapView 将使用从 SampleMapAnnotation 实例派生的 Coordinate 数据将批注视图定位到地图上。The MKMapView will use the Coordinate data derived from the SampleMapAnnotation instance to position the annotation view on the map.

MKAnnotation 协议跨实现该协议的任何对象提供一组已知功能,无需使用者(在本例中为 map)需要了解有关实现的详细信息。The MKAnnotation protocol provides a known set of capabilities across any objects that implement it, without the consumer (the map in this case) needing to know about implementation details. 这简化了将各种可能的批注添加到地图中的工作。This streamlines adding a variety of possible annotations to a map.

深层协议Protocols Deep Dive

由于C#接口不支持可选方法,因此,Xamarin 会将协议映射到抽象类。Since C# interfaces don’t support optional methods, Xamarin.iOS maps protocols to abstract classes. 因此,在 Ansi-c 中采用的协议是在 Xamarin 中通过从绑定到协议和实现所需方法的抽象类派生而来的。Therefore, adopting a protocol in Objective-C is accomplished in Xamarin.iOS by deriving from the abstract class that is bound to the protocol and implementing the required methods. 这些方法将公开为类中的抽象方法。These methods will be exposed as abstract methods in the class. 协议中的可选方法将绑定到C#类的虚方法。Optional methods from the protocol will be bound to virtual methods of the C# class.

例如,下面是 Xamarin 中的 UITableViewDataSource 协议的一部分:For example, here is a portion of the UITableViewDataSource protocol as bound in Xamarin.iOS:

public abstract class UITableViewDataSource : NSObject
{
    [Export ("tableView:cellForRowAtIndexPath:")]
    public abstract UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath);
    [Export ("numberOfSectionsInTableView:")]
    public virtual int NumberOfSections (UITableView tableView){...}
...
}

请注意,类是抽象类。Note that the class is abstract. Xamarin 使类成为抽象类,以便支持协议中的可选方法/所需的方法。Xamarin.iOS makes the class abstract to support optional/required methods in protocols. 但是,与目标 C 协议(或C#接口)不同, C#类不支持多重继承。However, unlike Objective-C protocols, (or C# interfaces), C# classes don’t support multiple inheritance. 这会影响使用协议C#的代码设计,并且通常会导致嵌套类。This affects the design of C# code that uses protocols, and typically leads to nested classes. 有关此问题的详细信息将在本文档后面的 "委托" 部分中介绍。More about this issue is covered later in this document, in the Delegates section.

GetCell(…) 是一种抽象方法,该方法绑定到目标 C选择器tableView:cellForRowAtIndexPath:,这是 UITableViewDataSource 协议所需的方法。GetCell(…) is an abstract method, bound to the Objective-C selector, tableView:cellForRowAtIndexPath:, which is a required method of the UITableViewDataSource protocol. 选择器是方法名称的目标-C 术语。Selector is the Objective-C term for method name. 若要根据需要强制执行方法,Xamarin 会将它声明为 abstract。To enforce the method as required, Xamarin.iOS declares it as abstract. 另一种方法 NumberOfSections(…) 绑定到 numberOfSectionsInTableview:The other method, NumberOfSections(…), is bound to numberOfSectionsInTableview:. 此方法在协议中是可选的,因此,Xamarin 会将它声明为虚拟的,使其在C#中是可选的。This method is optional in the protocol, so Xamarin.iOS declares it as virtual, making it optional to override in C#.

Xamarin 会为你处理所有 iOS 绑定。Xamarin.iOS takes care of all iOS binding for you. 但是,如果您需要从目标 C 手动绑定协议,则可以通过使用 ExportAttribute 修饰类来实现此目的。However, if you ever need to bind a protocol from Objective-C manually, you can do so by decorating a class with the ExportAttribute. 此方法与 Xamarin 本身使用的方法相同。This is the same method used by Xamarin.iOS itself.

有关如何在 Xamarin 中绑定目标 C 类型的详细信息,请参阅绑定目标 c 类型一文。For more information about how to bind Objective-C types in Xamarin.iOS, see the article Binding Objective-C Types.

不过,我们还不会使用协议。We’re not through with protocols yet, though. 它们还在 iOS 中用作目标-C 委托的基础,这是下一节的主题。They’re also used in iOS as the basis for Objective-C delegates, which is the topic of the next section.

委托Delegates

iOS 使用目标-C 委托实现委托模式,在该模式中,一个对象将工作传递到另一个对象。iOS uses Objective-C delegates to implement the delegation pattern, in which one object passes work off to another. 执行工作的对象是第一个对象的委托。The object doing the work is the delegate of the first object. 对象通过在发生某些事情后发送消息来通知其委托执行工作。An object tells its delegate to do work by sending it messages after certain things happen. 在 Ansi-c 中发送类似于此的消息在功能上等同于调用中C#的方法。Sending a message like this in Objective-C is functionally equivalent to calling a method in C#. 委托实现方法以响应这些调用,因此提供应用程序的功能。A delegate implements methods in response to these calls, and so provides functionality to the application.

委托使你可以扩展类的行为,而无需创建子类。Delegates enable you to extend the behavior of classes without needing to create subclasses. 如果某个类在发生重要操作后返回到另一个类,则 iOS 中的应用程序通常会使用委托。Applications in iOS often use delegates when one class calls back to another after an important action occurs. 例如,当用户在地图上点击批注时,MKMapView 类向其委托返回,使委托类的作者有机会在应用程序中进行响应。For example, the MKMapView class calls back to its delegate when the user taps an annotation on a map, giving the author of the delegate class the opportunity to respond within the application. 可以在本文后面的部分中完成此类委托用法的示例,例如,将委托与 Xamarin 配合使用。You can work through an example of this type of delegate usage later in this article, in Example Using a Delegate with Xamarin.iOS.

此时,你可能想知道类如何确定在其委托上调用的方法。At this point, you may be wondering how a class determines what methods to call on its delegate. 这是使用协议的另一个位置。This is another place where you use protocols. 通常,适用于委托的方法来自它们采用的协议。Usually, the methods available for a delegate come from the protocols they adopt.

如何将协议与委托一起使用How Protocols are used with Delegates

我们先前看到了如何使用协议来支持将批注添加到地图中。We saw earlier how protocols are used to support adding annotations to a map. 协议还用于为类提供一组已知的方法,以便在发生特定事件之后调用,如用户在地图上点击批注或选择表中的单元格之后。Protocols are also used to provide a known set of methods for classes to call after certain events occur, such as after the user taps an annotation on a map or selects a cell in a table. 实现这些方法的类称为调用它们的类的委托。The classes that implement these methods are known as the delegates of the classes that call them.

支持委托的类通过公开委托属性来实现此目的,实现委托的类将分配给该属性。Classes that support delegation do so by exposing a Delegate property, to which a class implementing the delegate is assigned. 为委托实现的方法将取决于特定委托采用的协议。The methods you implement for the delegate will depend upon the protocol that the particular delegate adopts. 对于 UITableView 方法,你可以实现 UITableViewDelegate 协议,对于 UIAccelerometer 方法,你应为要公开委托的所有 iOS 中的任何其他类实现 UIAccelerometerDelegateFor the UITableView method, you implement the UITableViewDelegate protocol, for the UIAccelerometer method, you would implement UIAccelerometerDelegate, and so on for any other classes throughout iOS for which you would want to expose a delegate.

我们在前面的示例中看到的 MKMapView 类还具有一个名为 Delegate 的属性,该属性将在各种事件发生后调用。The MKMapView class we saw in our earlier example also has a property called Delegate, which it will call after various events occur. MKMapView 的委托的类型为 MKMapViewDelegateThe Delegate for MKMapView is of type MKMapViewDelegate. 你将在示例中稍后使用此方法来响应选中的批注,但首先我们讨论强委托和弱委托之间的区别。You’ll use this shortly in an example to respond to the annotation after it is selected, but first let’s discuss the difference between strong and weak delegates.

强委托与弱委托Strong Delegates vs. Weak Delegates

到目前为止,我们查看的委托是强委托,这意味着它们是强类型的。The delegates we’ve looked at so far are strong delegates, meaning they are strongly typed. 对于 iOS 中的每个委托协议,Xamarin 绑定都附带一个强类型类。The Xamarin.iOS bindings ship with a strongly typed class for every delegate protocol in iOS. 但是,iOS 还具有弱委托的概念。However, iOS also has the concept of a weak delegate. IOS 还可让你选择在任何从 NSObject 派生的类中自行绑定协议方法,并使用 ExportAttribute 修饰方法,然后,而不是将绑定到特定委托的目标为 C 协议的类。提供适当的选择器。Instead of subclassing a class bound to the Objective-C protocol for a particular delegate, iOS also lets you choose to bind the protocol methods yourself in any class you like that derives from NSObject, decorating your methods with the ExportAttribute, and then supplying the appropriate selectors. 采用此方法时,请将类的实例分配给 WeakDelegate 属性,而不是委托属性。When you take this approach, you assign an instance of your class to the WeakDelegate property instead of to the Delegate property. 弱委托使你可以灵活地将委托类向下移动到不同的继承层次结构。A weak delegate offers you the flexibility to take your delegate class down a different inheritance hierarchy. 让我们看看使用强委托和弱委托的 Xamarin iOS 示例。Let’s look at a Xamarin.iOS example that uses both strong and weak delegates.

将委托与 Xamarin 一起使用的示例Example Using a Delegate with Xamarin.iOS

若要在我们的示例中执行代码以响应用户点击批注,可以 MKMapViewDelegate 子类,并将实例分配给 MKMapViewDelegate 属性。To execute code in response to the user tapping the annotation in our example, we can subclass MKMapViewDelegate and assign an instance to the MKMapView’s Delegate property. MKMapViewDelegate 协议仅包含可选方法。The MKMapViewDelegate protocol contains only optional methods. 因此,所有方法都是在 Xamarin MKMapViewDelegate 类中绑定到此协议的虚拟。Therefore, all the methods are virtual that are bound to this protocol in the Xamarin.iOS MKMapViewDelegate class. 当用户选择某一批注时,MKMapView 实例将向其委托发送 mapView:didSelectAnnotationView: 消息。When the user selects an annotation, the MKMapView instance will send the mapView:didSelectAnnotationView: message to its delegate. 若要在 Xamarin 中处理此情况,需要重写 MKMapViewDelegate 子类中的 DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView) 方法,如下所示:To handle this in Xamarin.iOS, we need to override the DidSelectAnnotationView (MKMapView mapView, MKAnnotationView annotationView) method in the MKMapViewDelegate subclass like this:

public class SampleMapDelegate : MKMapViewDelegate
{
    public override void DidSelectAnnotationView (
        MKMapView mapView, MKAnnotationView annotationView)
    {
        var sampleAnnotation =
            annotationView.Annotation as SampleMapAnnotation;

        if (sampleAnnotation != null) {

            //demo accessing the coordinate of the selected annotation to
            //zoom in on it
            mapView.Region = MKCoordinateRegion.FromDistance(
                sampleAnnotation.Coordinate, 500, 500);

            //demo accessing the title of the selected annotation
            Console.WriteLine ("{0} was tapped", sampleAnnotation.Title);
        }
    }
}

上面所示的 SampleMapDelegate 类在包含 MKMapView 实例的控制器中作为嵌套类实现。The SampleMapDelegate class shown above is implemented as a nested class in the controller that contains the MKMapView instance. 在目标-C 中,通常会看到控制器直接采用类中的多个协议。In Objective-C, you’ll often see the controller adopt multiple protocols directly within the class. 不过,由于协议绑定到 Xamarin 中的类,实现强类型委托的类通常作为嵌套类包括在内。However, since protocols are bound to classes in Xamarin.iOS, the classes that implement strongly typed delegates are usually included as nested classes.

委托类实现就绪后,只需在控制器中实例化委托实例,并将其分配给 MKMapViewDelegate 属性,如下所示:With the delegate class implementation in place, you only need to instantiate an instance of the delegate in the controller and assign it to the MKMapView’s Delegate property as shown here:

public partial class Protocols_Delegates_EventsViewController : UIViewController
{
    SampleMapDelegate _mapDelegate;
    ...
    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();

        //set the map's delegate
        _mapDelegate = new SampleMapDelegate ();
        map.Delegate = _mapDelegate;
        ...
    }
    class SampleMapDelegate : MKMapViewDelegate
    {
        ...
    }
}

若要使用弱委托来完成相同的操作,需要在任何派生自 NSObject 的类中自行绑定方法,并将其分配给 MKMapViewWeakDelegate 属性。To use a weak delegate to accomplish the same thing, you need to bind the method yourself in any class that derives from NSObject and assign it to the WeakDelegate property of the MKMapView. 由于 UIViewController 类最终派生自 NSObject (类似于 CocoaTouch 中的每个目标-C 类),因此,只需实现直接在控制器中的一个绑定到 mapView:didSelectAnnotationView: 的方法,并将控制器分配给 MKMapViewWeakDelegate,这就无需额外的嵌套类。Since the UIViewController class ultimately derives from NSObject (like every Objective-C class in CocoaTouch), we can simply implement a method bound to mapView:didSelectAnnotationView: directly in the controller and assign the controller to MKMapView’s WeakDelegate, avoiding the need for the extra nested class. 下面的代码演示了这种方法:The code below demonstrates this approach:

public partial class Protocols_Delegates_EventsViewController : UIViewController
{
    ...
    public override void ViewDidLoad ()
    {
        base.ViewDidLoad ();
        //assign the controller directly to the weak delegate
        map.WeakDelegate = this;
    }
    //bind to the Objective-C selector mapView:didSelectAnnotationView:
    [Export("mapView:didSelectAnnotationView:")]
    public void DidSelectAnnotationView (MKMapView mapView,
        MKAnnotationView annotationView)
    {
        ...
    }
}

运行此代码时,应用程序的行为与运行强类型委托版本时完全相同。When running this code, the application behaves exactly as it did when running the strongly typed delegate version. 此代码的优点在于弱委托不需要创建在使用强类型委托时创建的额外类。The benefit from this code is that the weak delegate doesn’t require the creation of the extra class that was created when we used the strongly typed delegate. 但是,这种情况下会产生安全类型。However, this comes at the expense of type safety. 如果在传递给 ExportAttribute 的选择器中犯了错误,则在运行时之前将不会发现。If you were to make a mistake in the selector that was passed to the ExportAttribute, you wouldn’t find out until runtime.

事件和委托Events and Delegates

委托用于 iOS 中的回调,与 .NET 使用事件的方式类似。Delegates are used for callbacks in iOS similarly to the way .NET uses events. 为了使 iOS Api 和它们使用目标-C 委托看起来更像 .NET,Xamarin,在 iOS 中使用委托的许多地方都公开了 .NET 事件。To make iOS APIs and the way they use Objective-C delegates seem more like .NET, Xamarin.iOS exposes .NET events in many places where delegates are used in iOS.

例如,在早期的实现中,还可以使用 .NET 事件在 Xamarin 中实现对选定批注的 MKMapViewDelegate 响应。For example, the earlier implementation where the MKMapViewDelegate responded to a selected annotation could also be implemented in Xamarin.iOS by using a .NET event. 在这种情况下,将在 MKMapView 中定义事件,并将其称为 DidSelectAnnotationViewIn that case, the event would be defined in MKMapView and called DidSelectAnnotationView. 它将具有类型 MKMapViewAnnotationEventsArgsEventArgs 子类。It would have an EventArgs subclass of type MKMapViewAnnotationEventsArgs. MKMapViewAnnotationEventsArgsView 属性会为你提供一个对批注视图的引用,你可以在该视图中继续使用之前介绍的相同实现,如下所示:The View property of MKMapViewAnnotationEventsArgs would give you a reference to the annotation view, from which you could proceed with the same implementation you had earlier, as illustrated here:

map.DidSelectAnnotationView += (s,e) => {
    var sampleAnnotation = e.View.Annotation as SampleMapAnnotation;
    if (sampleAnnotation != null) {
        //demo accessing the coordinate of the selected annotation to
        //zoom in on it
        mapView.Region = MKCoordinateRegion.FromDistance (
            sampleAnnotation.Coordinate, 500, 500);

        //demo accessing the title of the selected annotation
        Console.WriteLine ("{0} was tapped", sampleAnnotation.Title);
    }
};

总结Summary

本文介绍了如何在 Xamarin 中使用事件、协议和委托。This article covered how to use events, protocols, and delegates in Xamarin.iOS. 我们了解到,iOS 如何公开控件的普通 .NET 样式事件。We saw how Xamarin.iOS exposes normal .NET style events for controls. 接下来,我们了解了客观-C 协议,其中包括它们与C#接口以及 Xamarin 如何使用它们的不同之处。Next we learned about Objective-C protocols, including how they are different from C# interfaces and how Xamarin.iOS uses them. 最后,我们从 Xamarin 的一个角度检查了目标-C 委托。Finally, we examined Objective-C delegates from a Xamarin.iOS perspective. 我们了解了 Xamarin iOS 如何支持强类型和弱类型委托,以及如何将 .NET 事件绑定到委托方法。We saw how Xamarin.iOS supports both strongly and weakly typed delegates, and how to bind .NET events to delegate methods.