绑定类型参考指南

本文档介绍可用于批注 API 协定文件以驱动绑定和生成的代码的特性列表

Xamarin.iOS 和 Xamarin.Mac API 协定主要以 C# 编写,作为接口定义来定义 Objective-C 代码呈现到 C# 的方式。 此过程涉及接口声明以及 API 协定可能需要的一些基本类型定义。 有关绑定类型的简介,请参阅我们的配套指南绑定 Objective-C 库

类型定义

语法:

[BaseType (typeof (BTYPE))
interface MyType : [Protocol1, Protocol2] {
     IntPtr Constructor (string foo);
}

协定定义中具有 [BaseType] 特性的每个接口都声明生成的对象的基类型。 在上面的声明中,将生成一个 MyType 类 C# 类型,该类型绑定到名为 MyType 的 Objective-C 类型。

如果使用接口继承语法在类型名称(上面示例中的 Protocol1Protocol2)后指定任何类型,则这些接口的内容将内联,就好像它们已是 MyType 的协定的一部分一样。 Xamarin.iOS 呈现某个类型采用某个协议的方式是,将该协议中声明的所有方法和属性内联到类型本身中。

下面显示了如何在 Xamarin.iOS 协定中定义 UITextField 的 Objective-C 声明:

@interface UITextField : UIControl <UITextInput> {

}

作为 C# API 协定时,应按下面所示编写:

[BaseType (typeof (UIControl))]
interface UITextField : UITextInput {
}

可以通过将其他特性应用于接口以及配置 [BaseType] 特性来控制代码生成的其他许多方面。

生成事件

Xamarin.iOS 和 Xamarin.Mac API 设计的一个功能是将 Objective-C 委托类映射为 C# 事件和回调。 用户可以在每实例的基础上选择是否要采用 Objective-C 编程模式,方法是将实现 Objective-C 运行时调用的各种方法的类的实例分配给诸如 Delegate 这样的属性,或者选择 C# 样式的事件和属性。

让我们看一个有关如何使用 Objective-C 模型的示例:

bool MakeDecision ()
{
    return true;
}

void Setup ()
{
     var scrollView = new UIScrollView (myRect);
     scrollView.Delegate = new MyScrollViewDelegate ();
     ...
}

class MyScrollViewDelegate : UIScrollViewDelegate {
    public override void Scrolled (UIScrollView scrollView)
    {
        Console.WriteLine ("Scrolled");
    }

    public override bool ShouldScrollToTop (UIScrollView scrollView)
    {
        return MakeDecision ();
    }
}

在上面的示例中,可以看到我们已选择覆盖两个方法,第一个是发生滚动事件的通知,第二个是回调,该回调应返回一个布尔值,指示 scrollView 是否应滚动到顶部。

C# 模型允许库的用户使用 C# 事件语法或属性语法侦听通知,以挂接预期返回值的回调。

使用 Lambda 实现相同功能的 C# 代码如下所示:

void Setup ()
{
    var scrollview = new UIScrollView (myRect);
    // Event connection, use += and multiple events can be connected
    scrollView.Scrolled += (sender, eventArgs) { Console.WriteLine ("Scrolled"); }

    // Property connection, use = only a single callback can be used
    scrollView.ShouldScrollToTop = (sv) => MakeDecision ();
}

由于事件不返回值(它们具有 void 返回类型),因此你可以连接多个副本。 ShouldScrollToTop 不是事件,而是具有以下签名的 UIScrollViewCondition 类型的属性:

public delegate bool UIScrollViewCondition (UIScrollView scrollView);

它返回一个 bool 值,在本例中,Lambda 语法允许我们仅仅从 MakeDecision 函数返回值。

绑定生成器支持生成用于将类(如 UIScrollView)与其 UIScrollViewDelegate(我们将这些称为 Model 类)链接的事件和属性,这是通过使用 EventsDelegates 参数(如下所述)批注 [BaseType] 定义来完成的。 除了使用这些参数批注 [BaseType] 外,还需要向生成器通知更多组成部分。

对于采用多个参数的事件(在 Objective-C 约定中,委托类中的第一个参数是发送方对象的实例),必须为生成的 EventArgs 类提供你希望其具有的名称。 这是通过 Model 类中的方法声明上的 [EventArgs] 特性来完成的。 例如:

[BaseType (typeof (UINavigationControllerDelegate))]
[Model][Protocol]
public interface UIImagePickerControllerDelegate {
    [Export ("imagePickerController:didFinishPickingImage:editingInfo:"), EventArgs ("UIImagePickerImagePicked")]
    void FinishedPickingImage (UIImagePickerController picker, UIImage image, NSDictionary editingInfo);
}

上述声明将生成一个 UIImagePickerImagePickedEventArgs 类,该类派生自 EventArgs 并将两个参数(UIImageNSDictionary)打包。 生成器生成以下内容:

public partial class UIImagePickerImagePickedEventArgs : EventArgs {
    public UIImagePickerImagePickedEventArgs (UIImage image, NSDictionary editingInfo);
    public UIImage Image { get; set; }
    public NSDictionary EditingInfo { get; set; }
}

然后,它会在 UIImagePickerController 类中公开以下内容:

public event EventHandler<UIImagePickerImagePickedEventArgs> FinishedPickingImage { add; remove; }

用于返回某个值的模型方法是以其他方式绑定的。 它们既需要已生成的 C# 委托的名称(方法的签名),还需要一个要在用户不提供实现时返回的默认值。 例如,ShouldScrollToTop 定义为:

[BaseType (typeof (NSObject))]
[Model][Protocol]
public interface UIScrollViewDelegate {
    [Export ("scrollViewShouldScrollToTop:"), DelegateName ("UIScrollViewCondition"), DefaultValue ("true")]
    bool ShouldScrollToTop (UIScrollView scrollView);
}

上述内容将创建一个具有上面所示签名的 UIScrollViewCondition 委托,如果用户不提供实现,则返回值将为 true。

除了 [DefaultValue] 特性之外,还可以使用 [DefaultValueFromArgument] 特性(向生成器指示在调用中返回指定参数的值)或 [NoDefaultValue] 参数(向生成器指示没有默认值)。

BaseTypeAttribute

语法:

public class BaseTypeAttribute : Attribute {
        public BaseTypeAttribute (Type t);

        // Properties
        public Type BaseType { get; set; }
        public string Name { get; set; }
        public Type [] Events { get; set; }
        public string [] Delegates { get; set; }
        public string KeepRefUntil { get; set; }
}

BaseType.Name

可以使用 Name 属性来控制此类型将在 Objective-C 世界中绑定到的名称。 这通常用于为 C# 类型提供一个符合 .NET Framework 设计准则的名称,而此名称会在 Objective-C 中映射到一个不遵循该约定的名称。

例如,在下例中,我们将 Objective-CNSURLConnection 类型映射到 NSUrlConnection,因为 .NET Framework 设计准则使用“Url”而不是“URL”:

[BaseType (typeof (NSObject), Name="NSURLConnection")]
interface NSUrlConnection {
}

指定的名称用作绑定中生成的 [Register] 特性的值。 如果未指定 Name,则类型的短名称将在生成的输出中用作 [Register] 特性的值。

BaseType.Events 和 BaseType.Delegates

这些属性用于驱动生成的类中 C# 样式事件的生成。 它们用于将给定类与其 Objective-C 委托类链接。 在很多情况下,类使用委托类发送通知和事件。 例如,一个 BarcodeScanner 将有一个配套的 BardodeScannerDelegate 类。 BarcodeScanner 类通常有一个 Delegate 属性,你可以将 BarcodeScannerDelegate 的实例分配给该属性,虽然这可行,但你可能希望向用户公开类似 C# 样式的事件接口,在这种情况下,可以使用 [BaseType] 特性的 EventsDelegates 属性。

这些属性始终是一起设置的,并且必须具有相同数量的元素并保持同步。Delegates 数组为你想要包装的每个弱类型委托包含一个字符串,而 Events 数组为你想要与之关联的每个类型包含一个类型。

[BaseType (typeof (NSObject),
           Delegates=new string [] { "WeakDelegate" },
           Events=new Type [] {typeof(UIAccelerometerDelegate)})]
public interface UIAccelerometer {
}

[BaseType (typeof (NSObject))]
[Model][Protocol]
public interface UIAccelerometerDelegate {
}

BaseType.KeepRefUntil

如果在创建此类的新实例时应用此特性,则该对象的实例将一直保留,直到已调用 KeepRefUntil 引用的方法为止。 此特性对于提高 API 的可用性很有用(在你不希望用户保留对某个对象的引用以使用你的代码时)。 此属性的值是 Delegate 类中一个方法的名称,因此还必须将其与 EventsDelegates 属性结合使用。

以下示例演示了在 Xamarin.iOS 中,UIActionSheet 如何使用它:

[BaseType (typeof (NSObject), KeepRefUntil="Dismissed")]
[BaseType (typeof (UIView),
           KeepRefUntil="Dismissed",
           Delegates=new string [] { "WeakDelegate" },
           Events=new Type [] {typeof(UIActionSheetDelegate)})]
public interface UIActionSheet {
}

[BaseType (typeof (NSObject))]
[Model][Protocol]
public interface UIActionSheetDelegate {
    [Export ("actionSheet:didDismissWithButtonIndex:"), EventArgs ("UIButton")]
    void Dismissed (UIActionSheet actionSheet, nint buttonIndex);
}

DesignatedDefaultCtorAttribute

当此特性应用于接口定义时,它将在默认(生成的)构造函数上生成一个 [DesignatedInitializer] 特性,该特性映射到 init 选择器。

DisableDefaultCtorAttribute

当此特性应用于接口定义时,它将阻止生成器生成默认构造函数。

当需要使用类中的其他某个构造函数来初始化对象时,请使用此特性。

PrivateDefaultCtorAttribute

当此特性应用于接口定义时,它会将默认构造函数标记为专用。 这意味着你仍然可以从扩展文件内部实例化此类的对象,但你的类的用户将无法访问它。

CategoryAttribute

在类型定义上使用此特性可绑定 Objective-C 类别并将其公开为 C# 扩展方法,以反映 Objective-C 公开该功能的方式。

类别是一种 Objective-C 机制,用于扩展类中可用的方法和属性集。 实际上,它们用来在链接特定框架(例如 UIKit)时扩展基类(例如 NSObject)的功能,使其方法可用,但前提是链接了该新框架。 在其他某些情况下,它们用于按作用组织类中的功能。 它们在本质上与 C# 扩展方法类似。

Objective-C 中的类别如下所示:

@interface UIView (MyUIViewExtension)
-(void) makeBackgroundRed;
@end

上述示例是在使用方法 makeBackgroundRed 扩展 UIView 的实例的库中找到的。

若要绑定这些,可以在接口定义上使用 [Category] 特性。 使用 [Category] 特性时,[BaseType] 特性的意义从用于指定要扩展的基类更改为成为要扩展的类型。

下面展示了如何绑定 UIView 扩展并将其转换为 C# 扩展方法:

[BaseType (typeof (UIView))]
[Category]
interface MyUIViewExtension {
    [Export ("makeBackgroundRed")]
    void MakeBackgroundRed ();
}

上面的代码将创建包含 MakeBackgroundRed 扩展方法的 MyUIViewExtension 类。 这意味着现在可以在任何 UIView 子类上调用 MakeBackgroundRed,从而获得你可在 Objective-C 上获得的相同功能。

在某些情况下,你会在类别内找到静态成员,如下例所示:

@interface FooObject (MyFooObjectExtension)
+ (BOOL)boolMethod:(NSRange *)range;
@end

这将导致类别 C# 接口定义不正确

[Category]
[BaseType (typeof (FooObject))]
interface FooObject_Extensions {

    // Incorrect Interface definition
    [Static]
    [Export ("boolMethod:")]
    bool BoolMethod (NSRange range);
}

此接口定义不正确是因为,若要使用 BoolMethod 扩展,就需要 FooObject 的实例,但你所绑定的是一个 ObjC 静态 扩展,这是由于 C# 扩展方法的实现方式而产生的副作用。

使用上述定义的唯一方法是使用以下糟糕的代码:

(null as FooObject).BoolMethod (range);

避免这种情况的建议是将 BoolMethod 定义内联到 FooObject 接口定义本身中,这样便可以调用此扩展,就像它是预期的 FooObject.BoolMethod (range) 那样。

[BaseType (typeof (NSObject))]
interface FooObject {

    [Static]
    [Export ("boolMethod:")]
    bool BoolMethod (NSRange range);
}

每当我们在 [Category] 定义中发现 [Static] 成员时,我们都会发出警告 (BI1117)。 如果确实希望在 [Category] 定义中包含 [Static] 成员,则可以通过以下方法消除该警告:使用 [Category (allowStaticMembers: true)],或者使用 [Internal] 修饰你的成员或 [Category] 接口定义。

StaticAttribute

将此特性应用于类时,它只会生成一个静态类,该静态类不是从 NSObject 派生的,因此 [BaseType] 特性将被忽略。 静态类用于托管你要公开的 C 公共变量。

例如:

[Static]
interface CBAdvertisement {
    [Field ("CBAdvertisementDataServiceUUIDsKey")]
    NSString DataServiceUUIDsKey { get; }

将使用以下 API 生成 C# 类:

public partial class CBAdvertisement  {
    public static NSString DataServiceUUIDsKey { get; }
}

协议/模型定义

模型通常由协议实现使用。 它们的不同之处在于,运行时只会向 Objective-C 注册实际已覆盖的方法。 否则,将不会注册该方法。

这通常意味着,当为已使用 ModelAttribute 标记了的类创建子类时,不应调用基方法。 调用该方法将引发以下异常:Foundation.You_Should_Not_Call_base_In_This_Method。 应该为你重写的任何方法在子类上实现整个行为。

AbstractAttribute

默认情况下,作为协议的一部分的成员不是必需的。 因此,用户只需从 C# 中的类派生并仅重写他们关心的方法,即可创建 Model 对象的子类。 有时,Objective-C 协定要求用户为此方法提供实现(这些方法在 Objective-C 中使用 @required 指令进行标记)。 在这些情况下,应使用 [Abstract] 特性标记这些方法。

[Abstract] 特性可以应用于方法或属性,使生成器将生成的成员标记为抽象成员,而类则标记为抽象类。

以下内容取自 Xamarin.iOS:

[BaseType (typeof (NSObject))]
[Model][Protocol]
public interface UITableViewDataSource {
    [Export ("tableView:numberOfRowsInSection:")]
    [Abstract]
    nint RowsInSection (UITableView tableView, nint section);
}

DefaultValueAttribute

指定要由模型方法返回的默认值(如果用户未在 Model 对象中为此特定方法提供方法)

语法:

public class DefaultValueAttribute : Attribute {
        public DefaultValueAttribute (object o);
        public object Default { get; set; }
}

例如,在 Camera 类的以下虚构委托类中,我们提供了一个 ShouldUploadToServer,它将作为 Camera 类上的属性公开。 如果 Camera 类的用户未将值显式设置为可响应 true 或 false 的 Lambda,则在这种情况下返回的默认值将为 false,即我们在 DefaultValue 特性中指定的值:

[BaseType (typeof (NSObject))]
[Model][Protocol]
interface CameraDelegate {
    [Export ("camera:shouldPromptForAction:"), DefaultValue (false)]
    bool ShouldUploadToServer (Camera camera, CameraAction action);
}

如果用户在该虚构类中设置了处理程序,则将忽略此值:

var camera = new Camera ();
camera.ShouldUploadToServer = (camera, action) => return SomeDecision ();

另请参阅:[NoDefaultValue][DefaultValueFromArgument]

DefaultValueFromArgumentAttribute

语法:

public class DefaultValueFromArgumentAttribute : Attribute {
    public DefaultValueFromArgumentAttribute (string argument);
    public string Argument { get; }
}

当在某个模型类上返回值的方法提供了此特性时,如果用户未提供自己的方法或 Lambda,则此特性将指示生成器返回指定参数的值。

示例:

[BaseType (typeof (NSObject))]
[Model][Protocol]
public interface NSAnimationDelegate {
    [Export ("animation:valueForProgress:"), DelegateName ("NSAnimationProgress"), DefaultValueFromArgumentAttribute ("progress")]
    float ComputeAnimationCurve (NSAnimation animation, nfloat progress);
}

在上述情况下,如果 NSAnimation 类的用户选择使用任何 C# 事件/属性,并且未将 NSAnimation.ComputeAnimationCurve 设置为方法或 Lambda,则返回值会是在进度参数中传递的值。

另请参阅:[NoDefaultValue][DefaultValue]

IgnoredInDelegateAttribute

有时,不将 Model 类中的事件或委托属性公开到宿主类中是有意义的,因此,添加此特性将指示生成器避免生成任何使用它修饰的方法。

[BaseType (typeof (UINavigationControllerDelegate))]
[Model][Protocol]
public interface UIImagePickerControllerDelegate {
    [Export ("imagePickerController:didFinishPickingImage:editingInfo:"), EventArgs ("UIImagePickerImagePicked")]
    void FinishedPickingImage (UIImagePickerController picker, UIImage image, NSDictionary editingInfo);

    [Export ("imagePickerController:didFinishPickingImage:"), IgnoredInDelegate)] // No event generated for this method
    void FinishedPickingImage (UIImagePickerController picker, UIImage image);
}

DelegateNameAttribute

此特性在返回值的 Model 方法中用来设置要使用的委托签名的名称。

示例:

[BaseType (typeof (NSObject))]
[Model][Protocol]
public interface NSAnimationDelegate {
    [Export ("animation:valueForProgress:"), DelegateName ("NSAnimationProgress"), DefaultValueFromArgumentAttribute ("progress")]
    float ComputeAnimationCurve (NSAnimation animation, float progress);
}

根据上述定义,生成器将生成以下公共声明:

public delegate float NSAnimationProgress (MonoMac.AppKit.NSAnimation animation, float progress);

DelegateApiNameAttribute

此特性用于允许生成器更改宿主类中生成的属性的名称。 有时,此特性在以下情况下非常有用:FooDelegate 类方法的名称对于 Delegate 类有意义,但作为属性出现在宿主类中会显得奇怪。

此外,在以下情况下,此特性也十分有用(并且是必需的):你有两个或多个重载方法,让这些方法按照 FooDelegate 类中的原样命名很有意义,但你希望在宿主类中使用更好的给定名称来公开它们。

示例:

[BaseType (typeof (NSObject))]
[Model][Protocol]
public interface NSAnimationDelegate {
    [Export ("animation:valueForProgress:"), DelegateApiName ("ComputeAnimationCurve"), DelegateName ("Func<NSAnimation, float, float>"), DefaultValueFromArgument ("progress")]
    float GetValueForProgress (NSAnimation animation, float progress);
}

根据上述定义,生成器将在宿主类中生成以下公共声明:

public Func<NSAnimation, float, float> ComputeAnimationCurve { get; set; }

EventArgsAttribute

对于采用多个参数的事件(在 Objective-C 约定中,委托类中的第一个参数是发送方对象的实例),必须为生成的 EventArgs 类提供你希望其具有的名称。 这是通过 Model 类中的方法声明上的 [EventArgs] 特性来完成的。

例如:

[BaseType (typeof (UINavigationControllerDelegate))]
[Model][Protocol]
public interface UIImagePickerControllerDelegate {
    [Export ("imagePickerController:didFinishPickingImage:editingInfo:"), EventArgs ("UIImagePickerImagePicked")]
    void FinishedPickingImage (UIImagePickerController picker, UIImage image, NSDictionary editingInfo);
}

上述声明将生成一个 UIImagePickerImagePickedEventArgs 类,该类派生自 EventArgs 并将两个参数(UIImageNSDictionary)打包。 生成器生成以下内容:

public partial class UIImagePickerImagePickedEventArgs : EventArgs {
    public UIImagePickerImagePickedEventArgs (UIImage image, NSDictionary editingInfo);
    public UIImage Image { get; set; }
    public NSDictionary EditingInfo { get; set; }
}

然后,它会在 UIImagePickerController 类中公开以下内容:

public event EventHandler<UIImagePickerImagePickedEventArgs> FinishedPickingImage { add; remove; }

EventNameAttribute

此特性用于允许生成器更改类中生成的事件或属性的名称。 有时,此特性在以下情况下非常有用:Model 类方法的名称对于 Model 类有意义,但作为事件或属性出现在源类中会显得奇怪。

例如,UIWebView 使用 UIWebViewDelegate 中的以下位:

[Export ("webViewDidFinishLoad:"), EventArgs ("UIWebView"), EventName ("LoadFinished")]
void LoadingFinished (UIWebView webView);

上面的代码将 LoadingFinished 作为 UIWebViewDelegate 中的方法公开,但将 LoadFinished 作为要在 UIWebView 中挂接到的事件公开:

var webView = new UIWebView (...);
webView.LoadFinished += delegate { Console.WriteLine ("done!"); }

ModelAttribute

[Model] 特性应用于协定 API 中的类型定义时,运行时将生成特殊代码,如果用户覆盖了类中的方法,则该代码只会呈现对类中方法的调用。 此特性通常应用于包装 Objective-C 委托类的所有 API。

运行时还将生成与相应协议的名称匹配的 Objective-C 类。

可以通过两种方式自定义 Objective-C 类的名称:

  1. 设置 AutoGeneratedName = true

    [Model (AutoGeneratedName = true)]
    

    这会使运行时为 Objective-C 类型生成唯一的名称。 该名称当前基于程序集名称和模型类型的全名(将来可能会更改)。

  2. 显式指定名称:

    [Model (Name = "CustomName")]
    

建议使用 AutoGeneratedName = true。 在 .NET 中,始终生成该名称(除非它按上面的 2. 被显式指定),并且 AutoGeneratedName 该属性不再存在。

NoDefaultValueAttribute

指定该模型上的方法不提供默认返回值。

它与 Objective-C 运行时配合使用,通过对 Objective-C 运行时请求响应 false 来确定指定的选择器是否是在此类中实现的。

[BaseType (typeof (NSObject))]
[Model][Protocol]
interface CameraDelegate {
    [Export ("shouldDisplayPopup"), NoDefaultValue]
    bool ShouldUploadToServer ();
}

另请参阅:[DefaultValue][DefaultValueFromArgument]

协议

C# 中并不真正存在 Objective-C 协议概念。 协议类似于 C# 接口,但它们的不同之处在于,并非协议中声明的所有方法和属性都必须由采用它的类实现。 某些方法和属性是可选的。

某些协议通常用作 Model 类,这些协议应使用 [Model] 特性进行绑定。

[BaseType (typeof (NSObject))]
[Model, Protocol]
interface MyProtocol {
    // Use [Abstract] when the method is defined in the @required section
    // of the protocol definition in Objective-C
    [Abstract]
    [Export ("say:")]
    void Say (string msg);

    [Export ("listen")]
    void Listen ();
}

从 Xamarin.iOS 7.0 开始,已包含了一项新的且已改进的协议绑定功能。 包含 [Protocol] 特性的任何定义实际上都会生成三个支持类,这些类极大地改善了使用协议的方式:

// Full method implementation, contains all methods
class MyProtocol : IMyProtocol {
    public void Say (string msg);
    public void Listen (string msg);
}

// Interface that contains only the required methods
interface IMyProtocol: INativeObject, IDisposable {
    [Export ("say:")]
    void Say (string msg);
}

// Extension methods
static class IMyProtocol_Extensions {
    public static void Optional (this IMyProtocol this, string msg);
    }
}

“类实现”提供了一个完整的抽象类,你可以重写其各个方法并获得完整类型安全性。 但是,由于 C# 不支持多个继承,因此在某些情况下,你可能需要一个不同的基类,但仍希望实现接口。

这就是生成的接口定义的用武之地。 它是一个接口,其中包含协议中的所有必需方法。 这使想要实现协议的开发人员只需实现该接口即可。 运行时会自动将该类型注册为采用协议。

请注意,接口仅列出必需的方法,并公开可选方法。 这意味着采用协议的类将会获得对必需方法的完整类型检查,但对于可选协议方法,则必须求助于弱类型化(手动使用 Export 特性并匹配签名)。

为了便于使用一个使用协议的 API,绑定工具还将生成一个扩展方法类,该类公开所有可选方法。 这意味着,只要使用 API,就可以将协议视为具有所有方法。

如果要在 API 中使用协议定义,则需要在 API 定义中编写主干空接口。 如果要在 API 中使用 MyProtocol,则需要执行以下操作:

[BaseType (typeof (NSObject))]
[Model, Protocol]
interface MyProtocol {
    // Use [Abstract] when the method is defined in the @required section
    // of the protocol definition in Objective-C
    [Abstract]
    [Export ("say:")]
    void Say (string msg);

    [Export ("listen")]
    void Listen ();
}

interface IMyProtocol {}

[BaseType (typeof(NSObject))]
interface MyTool {
    [Export ("getProtocol")]
    IMyProtocol GetProtocol ();
}

之所以需要上述内容,是因为在绑定时 IMyProtocol 不存在,这就是需要提供空接口的原因。

采用协议生成的接口

每当你实现为协议生成的某一接口(如下所示)时:

class MyDelegate : NSObject, IUITableViewDelegate {
    nint IUITableViewDelegate.GetRowHeight (nint row) {
        return 1;
    }
}

必需接口方法的实现将使用正确的名称导出,因此它等效于:

class MyDelegate : NSObject, IUITableViewDelegate {
    [Export ("getRowHeight:")]
    nint IUITableViewDelegate.GetRowHeight (nint row) {
        return 1;
    }
}

这将适用于所有必需的协议成员,但有一种特殊情况需要注意:存在可选的选择器。

使用基类时,可选协议成员的处理方式相同:

public class UrlSessionDelegate : NSUrlSessionDownloadDelegate {
	public override void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)

但在使用协议接口时,需要添加 [Export]。 当你以 override 为开头添加它时,IDE 将通过自动完成添加它。

public class UrlSessionDelegate : NSObject, INSUrlSessionDownloadDelegate {
	[Export ("URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:")]
	public void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)

两者在运行时的行为略有不同:

  • 基类(例如 NSUrlSessionDownloadDelegate)的用户提供所有必需选择器和可选选择器,并返回合理的默认值。
  • 接口(例如 INSUrlSessionDownloadDelegate)的用户仅响应已提供的确切选择器。

一些罕见的类在这里的行为可能有所不同。 但在几乎所有情况下,使用其中之一都是安全的。

协议内联

当你绑定已声明为采用协议的现有 Objective-C 类型时,需要直接内联该协议。 为此,只需将该协议声明为一个不带任何 [BaseType] 特性的接口,并在接口的基接口列表中列出该协议。

示例:

interface SpeakProtocol {
    [Export ("say:")]
    void Say (string msg);
}

[BaseType (typeof (NSObject))]
interface Robot : SpeakProtocol {
    [Export ("awake")]
    bool Awake { get; set; }
}

成员定义

本部分中的特性应用于类型的各个成员:属性和方法声明。

AlignAttribute

用于指定属性返回类型的对齐值。 某些属性采用指向必须在特定边界对齐的地址的指针(例如,在 Xamarin.iOS 中,某些必须按 16 字节对齐的 GLKBaseEffect 属性会发生这种情况)。 可以使用此属性来修饰 Getter,并使用对齐值。 与 Objective-C API 集成时,此特性通常与 OpenTK.Vector4OpenTK.Matrix4 类型一起使用。

示例:

public interface GLKBaseEffect {
    [Export ("constantColor")]
    Vector4 ConstantColor { [Align (16)] get; set;  }
}

AppearanceAttribute

[Appearance] 特性仅限于引入了外观管理器的 iOS 5。

[Appearance] 特性可以应用于参与 UIAppearance 框架的任何方法或属性。 当此特性应用于类中的方法或属性时,它将指示绑定生成器创建一个强类型外观类,用于设置此类的所有实例或匹配特定条件的实例的样式。

示例:

public interface UIToolbar {
    [Export ("setBackgroundImage:forToolbarPosition:barMetrics:")]
    [Appearance]
    void SetBackgroundImage (UIImage backgroundImage, UIToolbarPosition position, UIBarMetrics barMetrics);

    [Export ("backgroundImageForToolbarPosition:barMetrics:")]
    [Appearance]
    UIImage GetBackgroundImage (UIToolbarPosition position, UIBarMetrics barMetrics);
}

上述代码将在 UIToolbar 中生成以下代码:

public partial class UIToolbar {
    public partial class UIToolbarAppearance : UIView.UIViewAppearance {
        public virtual void SetBackgroundImage (UIImage backgroundImage, UIToolbarPosition position, UIBarMetrics barMetrics);
        public virtual UIImage GetBackgroundImage (UIToolbarPosition position, UIBarMetrics barMetrics)
    }
    public static new UIToolbarAppearance Appearance { get; }
    public static new UIToolbarAppearance AppearanceWhenContainedIn (params Type [] containers);
}

AutoReleaseAttribute (Xamarin.iOS 5.4)

在方法和属性上使用 [AutoReleaseAttribute] 可将方法调用包装到 NSAutoReleasePool 中的方法。

在 Objective-C 中,有一些方法会返回已添加到默认 NSAutoReleasePool 中的值。 默认情况下,这些将会去往你的线程 NSAutoReleasePool,但由于 Xamarin.iOS 也会保留对你的对象的引用(只要该托管对象存在),你可能不希望在 NSAutoReleasePool 中保留额外的引用(在该线程将控制权返回到下一个线程或你返回到主循环之前,该线程只会被消耗)。

例如,此特性应用于返回已添加到默认 NSAutoReleasePool 的对象的重属性(例如 UIImage.FromFile)。 如果没有此特性,那么只要你的线程未将控制权返回到主循环,图像就会保留。 如果你的线程是某种始终处于活动状态并等待工作的后台下载器,则图像将永远不会被释放。

ForcedTypeAttribute

[ForcedTypeAttribute] 用于强制创建托管类型(即使返回的非托管对象与绑定定义中所述的类型不匹配)。

这在标头中描述的类型与本机方法的返回类型不匹配时非常有用,例如,从 NSURLSession 中获取以下 Objective-C 定义:

- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request

它明确指出它将返回一个 NSURLSessionDownloadTask 实例,但它返回一个 NSURLSessionTask,这是一个超类,因此不能转换为 NSURLSessionDownloadTask 由于我们处于类型安全的上下文中,因此会发生 InvalidCastException

为了符合标头描述并避免出现 InvalidCastException,我们使用了 [ForcedTypeAttribute]

[BaseType (typeof (NSObject), Name="NSURLSession")]
interface NSUrlSession {

    [Export ("downloadTaskWithRequest:")]
    [return: ForcedType]
    NSUrlSessionDownloadTask CreateDownloadTask (NSUrlRequest request);
}

[ForcedTypeAttribute] 还接受名为 Owns 的布尔值(默认情况下为 false[ForcedType (owns: true)]。 owns 参数用于遵循 Core Foundation 对象的所有权策略

[ForcedTypeAttribute] 仅对参数、属性和返回值有效。

BindAsAttribute

[BindAsAttribute] 允许将 NSNumberNSValueNSString(enums) 绑定到更准确的 C# 类型。 该特性可用于创建比本机 API 更好、更准确的 .NET API。

可以使用 BindAs 来修饰方法(针对返回值)、参数和属性。 唯一的限制是成员不得位于 [Protocol][Model] 接口内。

例如:

[return: BindAs (typeof (bool?))]
[Export ("shouldDrawAt:")]
NSNumber ShouldDraw ([BindAs (typeof (CGRect))] NSValue rect);

将会输出:

[Export ("shouldDrawAt:")]
bool? ShouldDraw (CGRect rect) { ... }

在内部,我们将执行 bool?<->NSNumberCGRect<->NSValue 转换。

当前支持的封装类型包括:

  • NSValue
  • NSNumber
  • NSString

NSValue

以下 C# 数据类型受支持,可以封装自/到 NSValue

  • CGAffineTransform
  • NSRange
  • CGVector
  • SCNMatrix4
  • CLLocationCoordinate2D
  • SCNVector3
  • SCNVector4
  • CGPoint/PointF
  • CGRect/RectangleF
  • CGSize/SizeF
  • UIEdgeInsets
  • UIOffset
  • MKCoordinateSpan
  • CMTimeRange
  • CMTime
  • CMTimeMapping
  • CATransform3D

NSNumber

以下 C# 数据类型受支持,可以封装自/到 NSNumber

  • 布尔
  • 字节
  • double
  • FLOAT
  • short
  • int
  • long
  • sbyte
  • ushort
  • uint
  • ulong
  • nfloat
  • nint
  • nuint
  • 枚举

NSString

[BindAs] 可以与 NSString 常量支持的枚举结合使用,以便你创建更好的 .NET API,例如:

[BindAs (typeof (CAScroll))]
[Export ("supportedScrollMode")]
NSString SupportedScrollMode { get; set; }

将会输出:

[Export ("supportedScrollMode")]
CAScroll SupportedScrollMode { get; set; }

仅当向 [BindAs] 提供的枚举类型受 NSString 常量支持时,我们才会处理 enum<->NSString 转换。

阵 列

[BindAs] 还支持任何受支持类型的数组,你可以将以下 API 定义作为一个示例:

[return: BindAs (typeof (CAScroll []))]
[Export ("getScrollModesAt:")]
NSString [] GetScrollModes ([BindAs (typeof (CGRect []))] NSValue [] rects);

将会输出:

[Export ("getScrollModesAt:")]
CAScroll? [] GetScrollModes (CGRect [] rects) { ... }

rects 参数将会封装到一个 NSArray 中,其中为每个 CGRect 包含了一个 NSValue,作为回报,你将获得一个 CAScroll? 数组,该数组是使用返回的 NSArray(包含 NSStrings)的值创建的。

BindAttribute

[Bind] 特性有两种用途,一种用于方法或属性声明,另一种用于属性中的单个 getter 或 setter。

当用于方法或属性时,[Bind] 特性的作用是生成一个用于调用指定选择器的方法。 但这样生成的方法未使用 [Export] 特性进行修饰,这意味着它不能参与方法重写。 它通常与 [Target] 特性结合使用,用来实现 Objective-C 扩展方法。

例如:

public interface UIView {
    [Bind ("drawAtPoint:withFont:")]
    SizeF DrawString ([Target] string str, CGPoint point, UIFont font);
}

在 getter 或 setter 中使用时,[Bind] 特性用于更改代码生成器在为属性生成 getter 和 setter Objective-C 选择器名称时推断的默认值。 默认情况下,当你使用名称 fooBar 标记某个属性时,生成器将为 getter 生成 fooBar 导出,并为 setter 生成 setFooBar:。 在少数情况下,Objective-C 不遵循此约定,通常会将 getter 名称更改为 isFooBar。 可以使用此特性向生成器通知该信息。

例如:

// Default behavior
[Export ("active")]
bool Active { get; set; }

// Custom naming with the Bind attribute
[Export ("visible")]
bool Visible { [Bind ("isVisible")] get; set; }

AsyncAttribute

仅适用于 Xamarin.iOS 6.3 及更高版本。

此特性可以应用于采用完成处理程序作为其最后一个参数的方法。

可以在最后一个参数是回调的方法上使用 [Async] 特性。 将此特性应用于方法时,绑定生成器将生成带有后缀 Async 的该方法的版本。 如果回调不采用任何参数,返回值将是 Task,如果回调采用参数,则结果将是 Task<T>

[Export ("upload:complete:")]
[Async]
void LoadFile (string file, NSAction complete)

以下代码将生成此异步方法:

Task LoadFileAsync (string file);

如果回调采用多个参数,则你应设置 ResultTypeResultTypeName 以指定将保存所有属性的已生成类型的所需名称。

delegate void OnComplete (string [] files, nint byteCount);

[Export ("upload:complete:")]
[Async (ResultTypeName="FileLoading")]
void LoadFiles (string file, OnComplete complete)

以下代码将生成此异步方法,其中 FileLoading 包含用于访问 filesbyteCount 的属性:

Task<FileLoading> LoadFile (string file);

如果回调的最后一个参数是 NSError,则生成的 Async 方法将检查该值是否不为 null,如果不为 null,则生成的异步方法将设置任务异常。

[Export ("upload:onComplete:")]
[Async]
void Upload (string file, Action<string,NSError> onComplete);

上述代码生成以下异步方法:

Task<string> UploadAsync (string file);

如果出现错误,生成的 Task 会将异常设置为 NSErrorException(用于包装生成的 NSError)。

AsyncAttribute.ResultType

使用此属性可指定返回的 Task 对象的值。 此参数采用现有类型,因此需要在你的某一核心 API 定义中定义它。

AsyncAttribute.ResultTypeName

使用此属性可指定返回的 Task 对象的值。 此参数采用你的所需类型名称的名称,生成器将生成一系列属性,每个属性对应于回调采用的每个参数。

AsyncAttribute.MethodName

使用此属性可自定义生成的异步方法的名称。 默认名称是使用方法的名称并追加文本“Async”生成的,你可以使用此属性来更改该默认名称。

DesignatedInitializerAttribute

当此特性应用于构造函数时,它将在最终平台程序集内生成相同的 [DesignatedInitializer]。 这有助于 IDE 指示应在子类中使用哪个构造函数。

这应该映射到 Objective-C/clang 对 __attribute__((objc_designated_initializer)) 的使用。

DisableZeroCopyAttribute

此特性应用于字符串参数或字符串属性,并指示代码生成器不要对此参数使用零复制字符串封送处理,而是从 C# 字符串创建一个新的 NSString 实例。 仅当你使用 --zero-copy 命令行选项或设置程序集级特性 ZeroCopyStringsAttribute 来指示生成器使用零复制字符串封送处理时,此特性对字符串而言才是必需的。

如果属性在 Objective-C 中声明为 retainassign 属性而不是 copy 属性,则此特性是必需的。 这些通常发生在由开发人员错误地“优化”了的第三方库中。 一般来说,retainassignNSString 属性不正确,因为 NSMutableStringNSString 的用户派生类可能会在不了解库代码的情况下更改字符串的内容,从而以不易为人察觉的方式破坏应用程序。 通常,这是由于过早优化而发生的。

下面显示了 Objective-C 中的两个这样的属性:

@property(nonatomic,retain) NSString *name;
@property(nonatomic,assign) NSString *name2;

DisposeAttribute

[DisposeAttribute] 应用于类时,你需要提供一个将要添加到该类的 Dispose() 方法实现中的代码片段。

由于 Dispose 方法是由 bgen 工具自动生成的,因此你需要使用 [Dispose] 特性在生成的 Dispose 方法实现中注入一些代码。

例如:

[BaseType (typeof (NSObject))]
[Dispose ("if (OpenConnections > 0) CloseAllConnections ();")]
interface DatabaseConnection {
}

ExportAttribute

[Export] 特性用于标记要向 Objective-C 运行时公开的方法或属性。 此特性在绑定工具与实际的 Xamarin.iOS 和 Xamarin.Mac 运行时之间共享。 对于方法,参数会逐字传递到生成的代码,对于属性,会根据基声明生成 getter 和 setter Export(有关如何更改绑定工具的行为的信息,请参阅有关 [BindAttribute] 的部分)。

语法:

public enum ArgumentSemantic {
    None, Assign, Copy, Retain.
}

[AttributeUsage (AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)]
public class ExportAttribute : Attribute {
    public ExportAttribute();
    public ExportAttribute (string selector);
    public ExportAttribute (string selector, ArgumentSemantic semantic);
    public string Selector { get; set; }
    public ArgumentSemantic ArgumentSemantic { get; set; }
}

选择器表示要绑定的基础 Objective-C 方法或属性的名称。

ExportAttribute.ArgumentSemantic

FieldAttribute

此特性用于将一个 C 全局变量公开为一个按需加载并向 C# 代码公开的字段。 通常,需要使用此特性才能获取 C 或 Objective-C 中定义的常量值,以及下面这样的常量值:可能是某些 API 中使用的令牌,或者其值是不透明的,必须由用户代码按原样使用。

语法:

public class FieldAttribute : Attribute {
    public FieldAttribute (string symbolName);
    public FieldAttribute (string symbolName, string libraryName);
    public string SymbolName { get; set; }
    public string LibraryName { get; set; }
}

symbolName 是要与之链接的 C 符号。 默认情况下,这将从一个库中加载,该库的名称是从定义类型的命名空间推断出来的。 如果这不是要从中查找该符号的库,则应传递参数 libraryName。 如果要链接静态库,请使用 __Internal 作为 libraryName 参数。

生成的属性始终是静态的。

使用 Field 特性标记的属性可以是以下类型的属性:

  • NSString
  • NSArray
  • nint / int / long
  • nuint / uint / ulong
  • nfloat / float
  • double
  • CGSize
  • System.IntPtr
  • 枚举

NSString 常量支持的枚举不支持 Setter,但 Setter 可以手动进行绑定(如果需要)。

示例:

[Static]
interface CameraEffects {
     [Field ("kCameraEffectsZoomFactorKey", "CameraLibrary")]
     NSString ZoomFactorKey { get; }
}

InternalAttribute

[Internal] 特性可应用于方法或属性,它的作用是使用 internal C# 关键字标记生成的代码,从而使该代码只能由生成的程序集内的代码访问。 这通常用于隐藏级别太低的 API,或者用于提供你想要改进的次优公共 API,或者用于生成器不支持且需要一些手动编码的 API。

设计绑定时,通常会使用此特性隐藏方法或属性,并为方法或属性提供不同的名称,然后在 C# 补充支持文件上添加一个用于公开基础功能的强类型包装器。

例如:

[Internal]
[Export ("setValue:forKey:")]
void _SetValueForKey (NSObject value, NSObject key);

[Internal]
[Export ("getValueForKey:")]
NSObject _GetValueForKey (NSObject key);

然后,在支持文件中,可以包含一些代码,如下所示:

public NSObject this [NSObject idx] {
    get {
        return _GetValueForKey (idx);
    }
    set {
        _SetValueForKey (value, idx);
    }
}

IsThreadStaticAttribute

此特性标记要使用 .NET [ThreadStatic] 特性进行批注的属性的支持字段。 这在字段是线程静态变量时非常有用。

MarshalNativeExceptions (Xamarin.iOS 6.0.6)

此特性将使方法支持本机 (Objective-C) 异常。 该调用不是直接调用 objc_msgSend,而是通过一个自定义蹦床来捕获 ObjectiveC 异常并将其封送到托管异常中。

目前仅支持少数 objc_msgSend 签名(当使用该绑定的应用程序的本机链接因缺少 xamarin__objc_msgSend 符号而失败时,你会发现该签名是否不受支持),但可以应要求添加更多签名。

NewAttribute

此特性应用于方法和属性,以使生成器在声明前面生成 new 关键字。

它用于在基类中已存在的子类中引入了相同的方法或属性名称时避免编译器警告。

NotificationAttribute

可以将此特性应用于字段,以使生成器生成强类型帮助程序 Notifications 类。

对于不带有效负载的通知,该特性可以在不带参数的情况下使用;或者,你可以指定一个 System.Type,其中引用了 API 定义中的另一个接口,通常其名称以“EventArgs”结尾。 生成器会将接口转换为 EventArgs 的子类,并将包含其中列出的所有属性。 应在 EventArgs 类中使用 [Export] 特性来列出用于查找 Objective-C 字典(以便提取值)的键的名称。

例如:

interface MyClass {
    [Notification]
    [Field ("MyClassDidStartNotification")]
    NSString DidStartNotification { get; }
}

上述代码将使用以下方法生成嵌套类 MyClass.Notifications

public class MyClass {
   [..]
   public Notifications {
      public static NSObject ObserveDidStart (EventHandler<NSNotificationEventArgs> handler)
      public static NSObject ObserveDidStart (NSObject objectToObserve, EventHandler<NSNotificationEventArgs> handler)
   }
}

然后,代码的用户可以使用如下所示的代码轻松订阅发布到 NSDefaultCenter 的通知:

var token = MyClass.Notifications.ObserverDidStart ((notification) => {
    Console.WriteLine ("Observed the 'DidStart' event!");
});

或者设置要观察的特定对象。 如果将 null 传递给 objectToObserve,则此方法的行为将与它的其他对等方法一样。

var token = MyClass.Notifications.ObserverDidStart (objectToObserve, (notification) => {
    Console.WriteLine ("Observed the 'DidStart' event on objectToObserve!");
});

ObserveDidStart 返回的值可用于轻松停止接收通知,如下所示:

token.Dispose ();

或者可以调用 NSNotification.DefaultCenter.RemoveObserver 并传递令牌。 如果通知包含参数,则应指定帮助程序 EventArgs 接口,如下所示:

interface MyClass {
    [Notification (typeof (MyScreenChangedEventArgs)]
    [Field ("MyClassScreenChangedNotification")]
    NSString ScreenChangedNotification { get; }
}

// The helper EventArgs declaration
interface MyScreenChangedEventArgs {
    [Export ("ScreenXKey")]
    nint ScreenX { get; set; }

    [Export ("ScreenYKey")]
    nint ScreenY { get; set; }

    [Export ("DidGoOffKey")]
    [ProbePresence]
    bool DidGoOff { get; }
}

上述代码将生成一个具有 ScreenXScreenY 属性的 MyScreenChangedEventArgs 类,这些属性将分别使用键 ScreenXKeyScreenYKeyNSNotification.UserInfo 字典中提取数据,并应用适当的转换。 [ProbePresence] 特性用于让生成器探测是否在 UserInfo 中设置了该键,而不是尝试提取值。 这用于键的存在就是值(通常是布尔值)的情况。

这让你可以编写如下所示的代码:

var token = MyClass.NotificationsObserveScreenChanged ((notification) => {
    Console.WriteLine ("The new screen dimensions are {0},{1}", notification.ScreenX, notification.ScreenY);
});

在某些情况下,没有任何与字典中传递的值关联的常量。 Apple 有时使用公共符号常量,有时使用字符串常量。 默认情况下,你提供的 EventArgs 类中的 [Export] 特性将使用指定的名称作为要在运行时查找的公共符号。 如果情况并非如此,而是应该将其作为字符串常量进行查找,则将 ArgumentSemantic.Assign 值传递给 Export 特性。

Xamarin.iOS 8.4 中的新增内容

有时,通知将在没有任何参数的情况下开始生命,因此使用不带参数的 [Notification] 是可以接受的。 但有时,将会引入通知的参数。 若要支持此方案,可以多次应用该特性。

如果要开发绑定,并且想要避免破坏现有用户代码,则可以通过使用以下代码:

interface MyClass {
    [Notification]
    [Field ("MyClassScreenChangedNotification")]
    NSString ScreenChangedNotification { get; }
}

将通知转换到一个版本,该版本列出通知特性两次,如下所示:

interface MyClass {
    [Notification]
    [Notification (typeof (MyScreenChangedEventArgs)]
    [Field ("MyClassScreenChangedNotification")]
    NSString ScreenChangedNotification { get; }
}

NullAllowedAttribute

当此特性应用于某个属性时,它会将该属性标记为允许向其分配值 null。 此特性仅对引用类型有效。

当此特性应用于方法签名中的参数时,它指示指定的参数可以为 null,并且不应针对传递 null 值执行任何检查。

如果引用类型没有此特性,则绑定工具会在将所分配的值传递给 Objective-C 之前生成对该值的检查,并且如果分配的值为 null,则会生成一个将要引发 ArgumentNullException 的检查。

例如:

// In properties

[NullAllowed]
UIImage IconFile { get; set; }

// In methods
void SetImage ([NullAllowed] UIImage image, State forState);

OverrideAttribute

使用此特性可指示绑定生成器应使用 override 关键字标记此特定方法的绑定。

PreSnippetAttribute

可以使用此特性注入一些要在输入的参数已经过验证之后但在代码调用 Objective-C 之前插入的代码。

示例:

[Export ("demo")]
[PreSnippet ("var old = ViewController;")]
void Demo ();

PrologueSnippetAttribute

可以使用此特性注入一些要在生成的方法中验证任何参数之前插入的代码。

示例:

[Export ("demo")]
[Prologue ("Trace.Entry ();")]
void Demo ();

PostGetAttribute

指示绑定生成器从此类调用指定的属性以从中获取值。

此属性通常用于刷新指向引用对象(用于保持对象图被引用)的缓存。 通常它出现在具有添加/删除等操作的代码中。 此方法用于在添加或删除元素之后更新内部缓存,以确保我们保留对实际正在使用的对象的托管引用。 这是可能的,因为绑定工具为给定绑定中的所有引用对象生成支持字段。

示例:

[BaseType (typeof (NSObject))]
public interface NSOperation {
    [Export ("addDependency:")][PostGet ("Dependencies")]
    void AddDependency (NSOperation op);

    [Export ("removeDependency:")][PostGet ("Dependencies")]
    void RemoveDependency (NSOperation op);

    [Export ("dependencies")]
    NSOperation [] Dependencies { get; }
}

在本例中,在添加或删除 NSOperation 对象的依赖项后,将调用 Dependencies 属性,以确保我们有一个表示实际已加载对象的图,从而防止内存泄漏和内存损坏。

PostSnippetAttribute

可以使用此特性注入一些要在代码调用基础 Objective-C 方法后插入的 C# 源代码

示例:

[Export ("demo")]
[PostSnippet ("if (old != null) old.DemoComplete ();")]
void Demo ();

ProxyAttribute

此特性应用于返回值,以将它们标记为代理对象。 某些 Objective-C API 会返回无法与用户绑定区分开的代理对象。 此特性的作用是将对象标记为 DirectBinding 对象。 对于 Xamarin.Mac 中的方案,可以查看有关此 bug 的讨论

ReleaseAttribute (Xamarin.iOS 6.0)

这可以应用于返回类型,以指示生成器在返回对象之前应调用该对象上的 Release。 仅当某个方法为你提供保留的对象(而不是自动释放的对象,这是最常见的情况)时才需要此特性

示例:

[Export ("getAndRetainObject")]
[return: Release ()]
NSObject GetAndRetainObject ();

此外,此特性会传播到生成的代码,以便 Xamarin.iOS 运行时知道它必须在从这样的函数返回到 Objective-C 时保留该对象。

SealedAttribute

指示生成器将生成的方法标记为密封的。 如果未指定此特性,则默认会生成虚拟方法(虚拟方法、抽象方法或重写,具体取决于其他特性是如何使用的)。

StaticAttribute

[Static] 特性应用于方法或属性时,这将生成静态方法或属性。 如果未指定此特性,则生成器将生成实例方法或属性。

TransientAttribute

使用此特性可标记其值为瞬态值的属性,即由 iOS 临时创建但生存期不长的对象。 当此特性应用于属性时,生成器不会为该属性创建支持字段,这意味着托管类不保留对对象的引用。

WrapAttribute

在 Xamarin.iOS/Xamarin.Mac 绑定的设计中,[Wrap] 特性用于使用强类型对象包装弱类型对象。 这主要用于通常声明为属于 idNSObject 类型的 Objective-C 委托对象。 Xamarin.iOS 和 Xamarin.Mac 使用的约定是将这些委托或数据源公开为属于类型 NSObject,并使用约定“Weak”+ 公开的名称来命名。 Objective-C 中的 id delegate 属性将在 API 协定文件中公开为 NSObject WeakDelegate { get; set; } 属性。

但分配给此委托的值通常是强类型的,因此我们呈现强类型并应用 [Wrap] 特性,这意味着用户可以选择使用弱类型(如果需要一些精细控制或者需要求助于底层技巧),也可以对其大部分工作使用强类型属性。

示例:

[BaseType (typeof (NSObject))]
interface Demo {
     [Export ("delegate"), NullAllowed]
     NSObject WeakDelegate { get; set; }

     [Wrap ("WeakDelegate")]
     DemoDelegate Delegate { get; set; }
}

[BaseType (typeof (NSObject))]
[Model][Protocol]
interface DemoDelegate {
    [Export ("doDemo")]
    void DoDemo ();
}

这是用户如何使用 Delegate 的弱类型版本:

// The weak case, user has to roll his own
class SomeObject : NSObject {
    [Export ("doDemo")]
    void CallbackForDoDemo () {}

}

var demo = new Demo ();
demo.WeakDelegate = new SomeObject ();

这是用户使用强类型版本的方式,请注意,用户利用了 C# 的类型系统,并使用 override 关键字来声明其意图,并且不必使用 [Export] 手动修饰方法,因为我们在绑定中为用户完成了这项工作:

// This is the strong case,
class MyDelegate : DemoDelegate {
   override void Demo DoDemo () {}
}

var strongDemo = new Demo ();
demo.Delegate = new MyDelegate ();

[Wrap] 特性的另一个用途是支持方法的强类型版本。 例如:

[BaseType (typeof (NSObject))]
interface XyzPanel {
    [Export ("playback:withOptions:")]
    void Playback (string fileName, [NullAllowed] NSDictionary options);

    [Wrap ("Playback (fileName, options?.Dictionary")]
    void Playback (string fileName, XyzOptions options);
}

[Wrap] 特性应用于使用 [Category] 特性修饰的类型内的方法时,你需要包含 This 作为第一个参数,因为将要生成一个扩展方法。 例如:

[Wrap ("Write (This, image, options?.Dictionary, out error)")]
bool Write (CIImage image, CIImageRepresentationOptions options, out NSError error);

默认情况下,[Wrap] 生成的成员并非 virtual,如果你需要 virtual 成员,可以将可选的 isVirtual 参数设置为 true

[BaseType (typeof (NSObject))]
interface FooExplorer {
    [Export ("fooWithContentsOfURL:")]
    void FromUrl (NSUrl url);

    [Wrap ("FromUrl (NSUrl.FromString (url))", isVirtual: true)]
    void FromUrl (string url);
}

[Wrap] 还可以直接在属性 getter 和 setter 中使用。 这样就可以完全控制它们,并根据需要调整代码。 例如,考虑以下使用智能枚举的 API 定义:

// Smart enum.
enum PersonRelationship {
        [Field (null)]
        None,

        [Field ("FMFather", "__Internal")]
        Father,

        [Field ("FMMother", "__Internal")]
        Mother
}

接口定义:

// Property definition.

[Export ("presenceType")]
NSString _PresenceType { get; set; }

PersonRelationship PresenceType {
    [Wrap ("PersonRelationshipExtensions.GetValue (_PresenceType)")]
    get;
    [Wrap ("_PresenceType = value.GetConstant ()")]
    set;
}

参数特性

本部分介绍可应用于方法定义中的参数的特性以及适用于整个属性的 [NullAttribute]

BlockCallback

此特性应用于 C# 委托声明中的参数类型,以向绑定器通知相关参数符合 Objective-C 块调用约定,并且应以这种方式对其进行封送。

这通常用于在 Objective-C 中定义的回调,如下所示:

typedef returnType (^SomeTypeDefinition) (int parameter1, NSString *parameter2);

另请参阅:CCallback

CCallback

此特性应用于 C# 委托声明中的参数类型,以向绑定器通知相关参数符合 C ABI 函数指针调用约定,并且应以这种方式对其进行封送。

这通常用于在 Objective-C 中定义的回调,如下所示:

typedef returnType (*SomeTypeDefinition) (int parameter1, NSString *parameter2);

另请参阅:BlockCallback

参数

可以在方法定义的最后一个数组参数上使用 [Params] 特性,从而让生成器在定义中注入“params”。 这使得绑定可以轻松地允许可选参数。

例如,以下定义:

[Export ("loadFiles:")]
void LoadFiles ([Params]NSUrl [] files);

允许编写以下代码:

foo.LoadFiles (new NSUrl (url));
foo.LoadFiles (new NSUrl (url1), new NSUrl (url2), new NSUrl (url3));

这还有一个额外的优点,即它不需要用户创建纯粹用于传递元素的数组。

PlainString

可以在字符串参数前面使用 [PlainString] 特性来指示绑定生成器将字符串作为 C 字符串传递,而不是将参数作为 NSString 传递。

大多数 Objective-C API 使用 NSString 参数,但少数 API 公开一个用于传递字符串的 char * API,而不是 NSString 变体。 在那些情况下,请使用 [PlainString]

例如,以下 Objective-C 声明:

- (void) setText: (NSString *) theText;
- (void) logMessage: (char *) message;

应该这样绑定:

[Export ("setText:")]
void SetText (string theText);

[Export ("logMessage:")]
void LogMessage ([PlainString] string theText);

RetainAttribute

指示生成器保留对指定参数的引用。 生成器将为此字段提供后备存储,或者你可以指定一个名称 (WrapName) 以存储该值。 这对于保存对作为参数传递给 Objective-C 的托管对象的引用(在你知道 Objective-C 将仅保留该对象的此副本的情况下)非常有用。 例如,像 SetDisplay (SomeObject) 这样的 API 将使用此特性,因为 SetDisplay 可能一次只能显示一个对象。 如果需要跟踪多个对象(例如,对于类似堆栈的 API),请使用 [RetainList] 特性。

语法:

public class RetainAttribute {
    public RetainAttribute ();
    public RetainAttribute (string wrapName);
    public string WrapName { get; }
}

TransientAttribute

此特性应用于参数,并且仅在从 Objective-C 转换到 C# 时使用。 在这些转换期间,各种 Objective-CNSObject 参数都将包装成对象的托管表示形式。

运行时将获取对本机对象的引用并保留该引用,直到对该对象的最后一个托管引用消失,并且 GC 有机会运行。

在某些情况下,C# 运行时不保留对本机对象的引用非常重要。 当基础本机代码将特殊行为附加到参数的生命周期时,有时会发生这种情况。 例如:参数的析构函数将执行一些清理操作,或释放一些宝贵的资源。

此特性通知运行时你希望在从覆盖的方法返回到 Objective-C 时尽可能释放该对象。

规则很简单:如果运行时必须从本机对象创建新的托管表示形式,则在函数末尾,将会删除本机对象的保留计数,并且将会清除托管对象的 Handle 属性。 这意味着,如果保留对托管对象的引用,该引用将变得无用(调用其上的方法将引发异常)。

如果未创建传递的对象,或者该对象已存在未完成的托管表示形式,则不会发生强制释放。

属性特性

NotImplementedAttribute

此特性用于支持 Objective-C 习惯用法,其中在一个基类中引入了具有 getter 的属性,而一个可变子类则引入了 setter。

由于 C# 不支持此模型,因此基类需要同时具有 setter 和 getter,而子类可以使用 OverrideAttribute

此特性仅在属性 setter 中使用,用于支持 Objective-C 中的可变习惯用法。

示例:

[BaseType (typeof (NSObject))]
interface MyString {
    [Export ("initWithValue:")]
    IntPtr Constructor (string value);

    [Export ("value")]
    string Value {
        get;

    [NotImplemented ("Not available on MyString, use MyMutableString to set")]
        set;
    }
}

[BaseType (typeof (MyString))]
interface MyMutableString {
    [Export ("value")]
    [Override]
    string Value { get; set; }
}

枚举特性

将常量映射到 NSString 枚举值是创建更好的 .NET API 的一种简单方法。 该方法:

  • 通过仅显示适用于该 API 的正确值,使代码完成更加有用;
  • 添加类型安全性,你不能在不正确的上下文中使用另一个 NSString 常量;以及
  • 允许隐藏某些常量,使代码完成显示较短的 API 列表,而不会丢失功能。

示例:

enum NSRunLoopMode {

    [DefaultEnumValue]
    [Field ("NSDefaultRunLoopMode")]
    Default,

    [Field ("NSRunLoopCommonModes")]
    Common,

    [Field (null)]
    Other = 1000
}

根据上述绑定定义,生成器将创建 enum 自身,并且还将创建一个 *Extensions 静态类型,其中包括枚举值与 NSString 常量之间的双向转换方法。 这意味着即使常量不是 API 的一部分,这些常量仍可供开发人员使用。

示例:

// using the NSString constant in a different API / framework / 3rd party code
CallApiRequiringAnNSString (NSRunLoopMode.Default.GetConstant ());
// converting the constants from a different API / framework / 3rd party code
var constant = CallApiReturningAnNSString ();
// back into an enum value
CallApiWithEnum (NSRunLoopModeExtensions.GetValue (constant));

DefaultEnumValueAttribute

可以使用此特性修饰一个枚举值。 如果枚举值未知,此特性将成为要返回的常量。

从上面的示例来看:

var x = (NSRunLoopMode) 99;
Call (x.GetConstant ()); // NSDefaultRunLoopMode will be used

如果未修饰任何枚举值,则将引发一个 NotSupportedException

ErrorDomainAttribute

错误代码是作为枚举值绑定的。 它们通常有一个错误域,但并非总是很容易发现适用的那个(或者是否存在一个)。

可以使用此特性将错误域与枚举本身相关联。

示例:

[Native]
[ErrorDomain ("AVKitErrorDomain")]
public enum AVKitError : nint {
    None = 0,
    Unknown = -1000,
    PictureInPictureStartFailed = -1001
}

然后,可以调用扩展方法 GetDomain 以获取任何错误的域常量。

FieldAttribute

这与用于类型内部常量的 [Field] 特性相同。 还可以在枚举内使用它来将值与特定常量映射。

如果指定了 nullNSString 常量,则可以使用 null 值来指定应返回哪个枚举值。

从上面的示例来看:

var constant = NSRunLoopMode.NewInWatchOS3; // will be null in watchOS 2.x
Call (NSRunLoopModeExtensions.GetValue (constant)); // will return 1000

如果不存在 null 值,则会引发 ArgumentNullException

全局属性

全局特性可以使用 [assembly:] 特性修饰符来应用(如 [LinkWithAttribute]),或者可以在任何地方使用(如可用性特性)。

LinkWithAttribute

这是一个程序集级特性,它使开发人员可以指定重用绑定库所需的链接标志,而无需强制库的使用者手动配置 gcc_flags 和传递给库的额外 mtouch 参数。

语法:

// In properties
[Flags]
public enum LinkTarget {
    Simulator    = 1,
    ArmV6    = 2,
    ArmV7    = 4,
    Thumb    = 8,
}

[AttributeUsage(AttributeTargets.Assembly, AllowMultiple=true)]
public class LinkWithAttribute : Attribute {
    public LinkWithAttribute ();
    public LinkWithAttribute (string libraryName);
    public LinkWithAttribute (string libraryName, LinkTarget target);
    public LinkWithAttribute (string libraryName, LinkTarget target, string linkerFlags);
    public bool ForceLoad { get; set; }
    public string Frameworks { get; set; }
    public bool IsCxx { get; set;  }
    public string LibraryName { get; }
    public string LinkerFlags { get; set; }
    public LinkTarget LinkTarget { get; set; }
    public bool NeedsGccExceptionHandling { get; set; }
    public bool SmartLink { get; set; }
    public string WeakFrameworks { get; set; }
}

此特性应用于程序集级别,例如,下面是 CorePlot 绑定使用的代码:

[assembly: LinkWith ("libCorePlot-CocoaTouch.a", LinkTarget.ArmV7 | LinkTarget.ArmV7s | LinkTarget.Simulator, Frameworks = "CoreGraphics QuartzCore", ForceLoad = true)]

使用 [LinkWith] 特性时,指定的 libraryName 会嵌入到生成的程序集内,从而允许用户传送单个 DLL,该 DLL 中包含非托管依赖项以及正确使用 Xamarin.iOS 中的库所需的命令行标志。

也可以不提供 libraryName,在这种情况下,LinkWith 特性可用于仅指定其他链接器标志:

[assembly: LinkWith (LinkerFlags = "-lsqlite3")]

LinkWithAttribute 构造函数

通过这些构造函数,可以指定要链接并嵌入到生成的程序集内的库、该库支持的受支持目标以及链接该库所需的任何可选库标志。

请注意,LinkTarget 参数由 Xamarin.iOS 推断,无需设置。

示例:

// Specify additional linker:
[assembly: LinkWith (LinkerFlags = "-sqlite3")]

// Specify library name for the constructor:
[assembly: LinkWith ("libDemo.a");

// Specify library name, and link target for the constructor:
[assembly: LinkWith ("libDemo.a", LinkTarget.Thumb | LinkTarget.Simulator);

// Specify only the library name, link target and linker flags for the constructor:
[assembly: LinkWith ("libDemo.a", LinkTarget.Thumb | LinkTarget.Simulator, SmartLink = true, ForceLoad = true, IsCxx = true);

LinkWithAttribute.ForceLoad

ForceLoad 属性用于确定 -force_load 链接标志是否用于链接本机库。 目前,这应始终为 true。

LinkWithAttribute.Frameworks

如果要绑定的库对任何框架(FoundationUIKit 除外)有硬性要求,则应将 Frameworks 属性设置为一个字符串,其中包含空格分隔的所需平台框架列表。 例如,如果要绑定一个需要 CoreGraphicsCoreText 的库,请将 Frameworks 属性设置为 "CoreGraphics CoreText"

LinkWithAttribute.IsCxx

如果需要使用 C++ 编译器而不是默认编译器(即 C 编译器)编译生成的可执行文件,请将此属性设置为 true。 如果要绑定的库是用 C++ 编写的,请使用此属性。

LinkWithAttribute.LibraryName

要捆绑的非托管库的名称。 这是一个扩展名为“.a”的文件,它可以包含多个平台的目标代码(例如,用于模拟器的 ARM 和 x86)。

早期版本的 Xamarin.iOS 会检查 LinkTarget 属性来确定库支持的平台,但现在这是自动检测的,并且会忽略 LinkTarget 属性。

LinkWithAttribute.LinkerFlags

LinkerFlags 字符串提供了一种方法,使绑定作者可以指定将本机库链接到应用程序时所需的任何其他链接器标志。

例如,如果本机库需要 libxml2 和 zlib,则可以将 LinkerFlags 字符串设置为 "-lxml2 -lz"

LinkWithAttribute.LinkTarget

早期版本的 Xamarin.iOS 会检查 LinkTarget 属性来确定库支持的平台,但现在这是自动检测的,并且会忽略 LinkTarget 属性。

LinkWithAttribute.NeedsGccExceptionHandling

如果要链接的库需要 GCC 异常处理库 (gcc_eh),请将此属性设置为 true

应将 SmartLink 属性设置为 true,以便 Xamarin.iOS 确定 ForceLoad 是否是必需的。

LinkWithAttribute.WeakFrameworks

WeakFrameworks 属性的工作方式与 Frameworks 属性相同,只不过在链接时,-weak_framework 说明符会传递给每个列出的框架的 gcc。

WeakFrameworks 使库和应用程序能够与平台框架进行弱链接,以便它们可以选择使用这些平台框架(如果可用),但不会对这些平台框架产生硬依赖关系,这在你的库要在更高的 iOS 版本上添加额外功能时非常有用。 有关弱链接的详细信息,请参阅 Apple 有关弱链接的文档。

用于弱链接的不错候选项是 Frameworks,如 Accounts、CoreBluetoothCoreImageGLKitNewsstandKitTwitter,因为它们仅在 iOS 5 中可用。

AdviceAttribute

使用此特性可以向开发人员提供有关可能更方便他们使用的其他 API 的提示。 例如,如果提供 API 的强类型版本,则可以在弱类型特性上使用此特性,以便指示开发人员使用更好的 API。

来自此特性的信息显示在文档中,并且可以开发工具来为用户提供有关如何进行改进的建议

RequiresSuperAttribute

这是 [Advice] 特性的专用子类,可用于提示开发人员重写一个方法需要调用基(被重写的)方法。

这对应于 clang__attribute__((objc_requires_super))

ZeroCopyStringsAttribute

仅在 Xamarin.iOS 5.4 及更高版本中可用。

此特性指示生成器该特定库(如果是使用 [assembly:] 应用的)或类型的绑定应使用快速零复制字符串封送处理。 此特性等效于将命令行选项 --zero-copy 传递给生成器。

对字符串使用零复制时,生成器可以有效地使用与 Objective-C 使用的字符串相同的 C# 字符串,而不会导致创建新的 NSString 对象,并且会避免将数据从 C# 字符串复制到 Objective-C 字符串。 使用零复制字符串的唯一缺点是,你必须确保你包装的任何碰巧标记为 retaincopy 的字符串属性都设置了 [DisableZeroCopy] 特性。 这是必需的,因为零复制字符串的句柄是在堆栈上分配的,在函数返回时无效。

示例:

[ZeroCopyStrings]
[BaseType (typeof (NSObject))]
interface MyBinding {
    [Export ("name")]
    string Name { get; set; }

    [Export ("domain"), NullAllowed]
    string Domain { get; set; }

    [DisablZeroCopy]
    [Export ("someRetainedNSString")]
    string RetainedProperty { get; set; }
}

还可以在程序集级别应用该特性,该特性将应用于该程序集的所有类型:

[assembly:ZeroCopyStrings]

强类型字典

在 Xamarin.iOS 8.0 中,我们引入了对轻松创建用于包装 NSDictionaries 的强类型类的支持。

虽然始终可以将 DictionaryContainer 数据类型与手动 API 一起使用,但现在做到这一点要简单得多。 有关详细信息,请参阅呈现强类型

StrongDictionary

当此特性应用于接口时,生成器将生成一个与从 DictionaryContainer 派生的接口同名的类,并将该接口中定义的每个属性转换为字典的强类型 getter 和 setter。

这会自动生成一个类,该类可以是从现有的 NSDictionary 实例化的,也可以是新创建的。

此特性采用一个参数(一个类的名称,该类包含用于访问字典上的元素的键)。 默认情况下,具有该特性的接口中的每个属性都会在指定类型中查找名称带有后缀“Key”的成员。

例如:

[StrongDictionary ("MyOptionKeys")]
interface MyOption {
    string Name { get; set; }
    nint    Age  { get; set; }
}

[Static]
interface MyOptionKeys {
    // In Objective-C this is "NSString *MYOptionNameKey;"
    [Field ("MYOptionNameKey")]
    NSString NameKey { get; }

    // In Objective-C this is "NSString *MYOptionAgeKey;"
    [Field ("MYOptionAgeKey")]
    NSString AgeKey { get; }
}

在上述情况下,MyOption 类将为 Name 生成一个字符串属性,该属性将使用 MyOptionKeys.NameKey 作为字典中的键来检索字符串。 并且将使用 MyOptionKeys.AgeKey 作为字典中的键来检索包含 int 的 NSNumber

如果要使用不同的键,可以使用属性上的 Export 特性,例如:

[StrongDictionary ("MyColoringKeys")]
interface MyColoringOptions {
    [Export ("TheName")]  // Override the default which would be NameKey
    string Name { get; set; }

    [Export ("TheAge")] // Override the default which would be AgeKey
    nint    Age  { get; set; }
}

[Static]
interface MyColoringKeys {
    // In Objective-C this is "NSString *MYColoringNameKey"
    [Field ("MYColoringNameKey")]
    NSString TheName { get; }

    // In Objective-C this is "NSString *MYColoringAgeKey"
    [Field ("MYColoringAgeKey")]
    NSString TheAge { get; }
}

强字典类型

StrongDictionary 定义支持以下数据类型:

C# 接口类型 NSDictionary 存储类型
bool 存储在 NSNumber 中的 Boolean
枚举值 存储在 NSNumber 中的整数
int 存储在 NSNumber 中的 32 位整数
uint 存储在 NSNumber 中的 32 位无符号整数
nint 存储在 NSNumber 中的 NSInteger
nuint 存储在 NSNumber 中的 NSUInteger
long 存储在 NSNumber 中的 64 位整数
float 存储为 NSNumber 的 32 位整数
double 存储为 NSNumber 的 64 位整数
NSObject 和子类 NSObject
NSDictionary NSDictionary
string NSString
NSString NSString
NSObject 的 C# Array NSArray
枚举的 C# Array 包含 NSNumber 值的 NSArray